我们的代码已被破坏了几个星期。 编译器错误,测试失败,错误行为困扰着我们的团队。 为什么? 因为我们被盲目蛙跳打了。 通过对关键组件进行多次并发更改以希望对其进行改进,我们已经从其丑陋但稳定且可工作的状态飞跃到了破碎的沼泽。 我们最好的意图给我们造成了严重破坏,一些工作日的预期工作使我们失去了一个多月的时间,直到改变最终得以恢复(暂时)。

经验教训:避免蛙跳。 相反,请遵循肯特·贝克(Kent Beck)的“ 冲刺C”的策略- 循序渐进 ,安全而又小巧地进行,不会破坏代码。 经常(最好是每天)将其部署到生产中,以迫使自己进行非常小的且非常安全的更改。 请勿同时更改多个不相关的内容。 不要以为您知道代码是如何工作的。 不要以为您要进行的更改是简单的更改。 进行全面测试(不要过于信任您的测试套件)。 通过运行测试,执行代码,运行生产中的代码,让计算机为您提供有关更改的反馈和确凿的事实。

发生了什么? 我们有批处理作业,其配置属性可以通过(1)命令行参数或(2)特定于作业或(3)文件中的共享条目来设置。 通过静态调用Configuration.get('my.property')访问它的作业。 由于全局的,自动加载的配置使得无法对具有不同配置的作业进行单元测试,因此我们想用传递过来的配置实例代替单例。 我将简要描述我们失败的重构,提出一种更好的方法,并讨论如何开发和重构没有此类失败的软件。

沼泽之路

我们试图产生这种情况:

大型重构失败

我们首先用三个可实例化的类替换了静态Configuration ,每个可实例化的类仅具有一个职责( SRP )。

然后,我们修改了每个作业以接受/创建所需的作业。

我们还将一些命令行参数和属性重命名为更易理解的名称,并进行了各种小的改进。

最后,我们用更好的方法代替了配置系统的(错误)用法,该配置系统用于存储有关作业上次完成工作的位置(“书签”)的信息。

作为副作用,还进行了其他一些更改,例如,不再从类路径上的某个位置加载默认配置,而是从相对文件系统路径中加载了默认配置,并且对命令行参数的处理几乎没有什么不同。

除一项测试外,其他所有测试均已通过,且一切正常。

它变得更加复杂(更改需要其他更改,原始设计过于简单等),因此花费的时间比预期的长,但是我们终于解决了这一问题。

但是,当我们尝试运行该应用程序时,它不起作用。 它找不到其配置文件(因为不再在类路径中查找它),一些用于尊重作业特定值的属性也不再这样做,并且我们在不知不觉中引入了一些不一致和缺陷。 事实证明,要找出各个配置属性之间的交互方式并确保在必要的所有位置以正确的顺序尊重所有配置源(命令行参数,共享属性和特定于作业的属性)非常困难。 我们修复它的尝试花了很长时间而且徒劳。 (首先修复您以前不了解的损坏的旧代码库,这绝对不容易。)

即使我们设法解决了所有问题,仍然会存在另一个问题。 更改不向后兼容。 为了能够将它们部署到生产中,我们需要停止所有操作,(正确地)更新所有配置和我们的cron作业。 潜在的许多错误。

有更好的方法吗?

这是否意味着改进软件的风险太大而无法获得回报? 否,如果我们更加谨慎,请以较小的,安全的,经过验证的步骤进行,并尽量减少或避免破坏性变化。 让我们看看如果遵循这些原则怎么办。

介绍配置实例

作为第一步(见图2),我们可以离开几乎一切,因为它是,只介绍一个临时ConfigurationInstance 1类相同的API(为了使下拉更换更容易)作为Configuration ,但是非静态和修改Configuration将所有呼叫转发到其自己的(单个) ConfigurationInstance实例2

一个小,简单,安全的更改。

然后,我们可以修改我们的工作,一个接一个,使用一个ConfigurationInstance通过获得Configuration.getInstance()默认情况下并在创建时也可以选择接受它的一个实例。

(请注意,在此过程中的任何时候,我们都可以并且应该将其部署到生产中。)

代码如下所示:

class Configuration {private static ConfigurationInstance instance = new ConfigurationInstance();public static ConfigurationInstance getInstance() { return instance; }public static void setTestInstance(ConfigurationInstance config) { instance = config; } // for tests onlypublic static String get(String property) { instance.get(property); }
}
class ConfigurationInstance {...public String get(String property) { /** some code ... */ }
}
class MyUpdatedJob {private ConfigurationInstance config;/** The new constructor */MyUpdatedJob(ConfigurationInstance config) { this.config = config; }/** @deprecated The old constructor kept for now for backwards-compatibility */MyUpdatedJob() { this.config = Configuration.getInstance(); }doTheJob() { ... config.get('my.property.xy') ... }
}
class OldNotUpdatedJob {doTheJob() { ... Configuration.get('my.property.xy') /* not updated yet, not testable */ ... }
}

对所有作业完成此操作后,我们可以更改作业的实例化以传递到ConfigurationInstance 。 接下来,我们可以删除对Configuration所有其余引用,将其删除,然后将ConfigurationInstance重命名为Configuration 。 我们可以定期将其部署到我们的测试/过渡环境中,最后部署到生产中,以确保一切仍然正常(对于更改而言应该最小)。

接下来,作为一项独立的更改,我们可以考虑并更改“书签”的存储方式。 其他改进(例如重命名配置属性)也应在以后独立完成。 (毫不奇怪,您所做的更改越多,出现问题的风险就越大。)

我们也可以/应该引入代码,以自动从旧配置迁移到新配置,例如,如果不存在新格式的书签,则以旧格式读取书签并将其存储在新配置中。 对于属性,我们可以添加代码以同时检查旧名称和新名称(并在仍然使用旧名称时发出警告)。 因此,部署更改将不需要我们与配置和执行更改同步。

-

1 )请注意,Java不允许我们使用相同名称的静态和非静态方法,因此我们需要在Configuration中使用不同名称创建实例方法,或者像我们一样创建另一个类。 我们希望保留相同的名称,以使从静态配置迁移到实例配置成为一个简单而安全的搜索和替换过程(在将字段configuration添加到目标类之后,将“ Configuration. ”替换为“ configuration. ”。) “ ConfigurationInstance”是一个丑陋的名称,但以后可以轻松安全地进行更改。

2 )迈克尔·费瑟斯(Michael Feathers)的开创性著作《有效地使用旧版代码 》中介绍的引入实例委托人重构

安全软件演进的原理

更改遗留的代码(结构不良,测试欠佳的代码)是有风险的,但是对于防止其进一步威慑,使其更好,从而降低其维护成本而言是必需的。 如果我们谨慎行事,并采取小而安全的步骤进行操作,同时定期(通过测试和部署到阶段/生产中)对变更进行定期验证,则很有可能将风险降到最低。

什么是安全的零钱?

这取决于。 但是一个很好的经验法则可能是这样的变化:(1)其他人每天都可以合并,然后(2)可以部署到阶段(例如,一天之后,进入生产)。 如果每天都有可能合并,则它必须相对较小且无损。 如果应每天进行部署,则它必须是安全的并且向后兼容(或自动迁移旧数据)。 如果您的更改大于或大于此更改,则说明更改太大/有风险。

安全成本与收益

以安全的方式更改软件并非易事,也不是“便宜”的事情。 它要求我们认真思考,有时使它变得难看,花费一些资源来维护(临时)向后兼容性。 在不顾及这种安全性的情况下更改代码似乎更加有效-但只有在您遇到意料之外的问题并花几天时间尝试修复问题,陷入困境时(即,队友不断更改的代码库),这才更有效率。 它与TDD类似-速度较慢,但​​由于您无需浪费时间调试和排除生产问题,因此可以得到回报。 (不幸的是,显示您*不要*放松团队或管理层的时间)。

冲刺C –系列微小变化

为了以小而安全的步骤进行更改,我们通常需要对其进行分解,并通过一系列小更改不断地朝着目标设计发展代码,每个小更改都在代码库的有限区域内并沿单个轴进行,仅一件事。 更改越小越安全,我们执行和验证它们的速度就越快,因此随着时间的推移会发生很多变化–这就是Kent Beck所说的Sprinting Centipede策略。

并行设计 :有时无法真正分解更改,例如用另一个替换数据存储。 在这种情况下,我们可以应用例如并行设计技术,即在保持旧代码不变的情况下发展新代码。 例如,我们可以首先只编写代码以将数据存储在新数据库中; 然后开始从中读取数据,同时还从旧数据库中读取数据,验证结果是否相同并返回旧数据库; 然后我们可以开始返回新结果(仍然保留旧代码以便能够切换回去); 最后,我们可以淘汰旧代码和存储。

先决条件

当然,只有在您可以构建,测试和部署并Swift收到有关可能出现的问题的警告时,才可以遵循Sprinting Centipede策略。 测试和部署(以及反馈)的周期越长,您必须执行的步骤就越长,否则您将花费​​大部分时间等待。

常问问题

我如何将不确定的变更部署到生产中?

代码,尤其是遗留代码,很少能进行真正的彻底测试,因此我们永远无法完全确定其正确性。 但是我们不能放弃改进代码,否则只会变得更糟。 恐惧和停滞不是解决方案,而是对代码库和流程的改进。 我们必须谨慎考虑风险并进行相应的测试。 更改越小,监视和回滚或快速修复的能力越好,则可能造成的缺陷影响就越小。 (如果有出现缺陷的机会,那么将会更早或更晚。)

结论

遵循Sprinting Cantipede软件演化策略似乎很慢,即以小规模,安全,很大程度上无中断的增量步骤进行,同时与主要开发部门定期合并,并经常通过运行测试和部署到生产环境来验证更改, 。 但是我们的经验表明,由于意外的原因,盲目青蛙的飞跃-希望驱动的大型更改或批量更改-可能实际上要慢得多,并且有时是不可行的,这是由于意外发生,但总是会出现复杂性和缺陷以及随之而来的延误,分支分散等。而且我相信这种情况经常发生。 因此,一次只更改一件事情,最好是在不进行其他更改(配置等)的情况下部署更改,收集反馈,确保您可以随时停止重构,同时从中获得尽可能多的价值。它(而不是将大量精力投入到大型更改中,并冒着将其完全放弃的风险)。 你有什么经验?

值得注意的是,类似的问题经常在项目级别上发生。 人们尝试一次生产太多产品,而不是根据反馈而不是相信来做最少的事情,部署和继续开发。

资源资源

  • 有限红色协会 ( 此处是一个很好的总结 )– Joshua Kerievsky讨论了在开发软件时减少“红色”时间段的需求(还介绍了Parallel Change,在上面我称为Parallel Design)
  • 肯特·贝克(Kent Beck)关于低功能延迟和高吞吐量的软件设计最佳实践的演讲摘要
  • Mikado方法一书 (在线)在“ mikado方法–失败的产物”部分(当前版本为1.2)中很好地描述了典型的失败重构。

致谢

我要感谢我的同事Anders,Morten和Jeanine的帮助和反馈。 对不起,安德斯,我不能再说短了。 你知道,每个字都是我的孩子。

参考: The Sprinting enti策略: The Holy Java博客上来自JCG合作伙伴 Jakub Holy的如何在不破坏软件的情况下改进软件 。

翻译自: https://www.javacodegeeks.com/2013/01/the-sprinting-centipede-strategy-how-to-improve-software-without-breaking-it.html

短跑enti策略:如何在不破坏软件的情况下改进软件相关推荐

  1. enti下载器_短跑enti策略:如何在不破坏软件的情况下改进软件

    enti下载器 我们的代码已被破坏了几个星期. 编译器错误,测试失败,行为错误困扰着我们的团队. 为什么? 因为我们被盲目蛙跳打了. 通过对关键组件进行多次并发更改以希望对其进行改进,我们已经从其丑陋 ...

  2. 如何在没有软件的情况下将 PDF 转换为 PPT(100% 免费)

    演示文稿由文字.图片.音频.动画等元素组成,通常用于会议.课堂或演讲中,展示演讲者想要表达的主要内容.如果您遇到重要文档以 PDF 格式存储,但现在需要转换为 PPT 格式的情况,请不要担心.我们本指 ...

  3. 高压缩比 压缩软件 linux,Linux下压缩软件对比

    Linux下压缩软件对比 发布时间:2007-02-15 00:44:03来源:红联作者:seais 最近在做系统备传输时发现压缩时间过常,于是简单做了一下Linux下的压缩软件的测试 源文件3591 ...

  4. HTG评论The Wink Hub:在不破坏资金的情况下为您的智能家居提供大脑

    Having a home packed with smart devices is great but managing them all in a smooth and unified fashi ...

  5. 制作apt-get本地源解决无网络情况下安装软件

    前言 有的时候在无网络的情况下,需要在机器上面使用apt-get命令来安装所需要的软件.那么我们事先需要在有网络的机器上面制作一个apt-get的本地源,然后将制作好的本地源通过scp或者FTP上传到 ...

  6. 计算机防止检查清理软件吗,解决方案:如何清理计算机垃圾-如何在不使用防病毒软件的情况下清理计算机文件垃圾?...

    计算机 我最近在和一个朋友聊天,他说如何安装某些防病毒软件,为什么他总是为我安装这么多东西?我说:"那只是卸载软件."我的朋友说如何清除计算机中的垃圾文件以及如何防止病毒入侵?我教 ...

  7. 不破坏背景的情况下在线ps替换文字

    在线ps打开需要修改图片 选中要修改的文字4 然后选择修补工具 选择完成后,鼠标左键按住选中的数字4并向后移动,鼠标移动到想要的数字5处 即可完成替换,背景也无变化

  8. linux下ddos软件,Linux 系统下ddos软件Zarp安装测试

    Zarp介绍: ZARP是采用PYTHON编写的一款网络 ZARP主要接口是一个CLI驱动的图形界面,如图显示: 以下是代码片段: bryan@devbox:~/zarp$ sudo ./zarp.p ...

  9. itunes 区域上架情况_在没有额外的膨胀软件的情况下安装iTunes的分步指南

    itunes 区域上架情况 Last week our friend Ed Bott wrote up an excellent article on how to install iTunes 10 ...

最新文章

  1. UE虚幻引擎:建筑可视化学习教程 Unreal Engine : Architectural Visualization
  2. iBatis自动生成的主键 (Oracle,MS Sql Server,MySQL)【转】
  3. HDU 2128 Tempter of the Bone II BFS
  4. Python Django related_name属性使用示例
  5. 【JavaWeb】石家庄地铁搭乘系统——第一版(功能尚未完善)
  6. 案例篇-HBase RowKey 设计指南
  7. LeetCode 167. 两数之和 II - 输入有序数组 思考分析
  8. HDOJ 1010 HDU 1010 Tempter of the Bone ACM 1010 IN HDU
  9. pandas 读表格_pandas电子表格的读取(pandas中的read_excel)
  10. 【Delphi】从内存读取或解压压缩文件(RAR、ZIP、TAR、GZIP等)(二)
  11. 手把手教你nginx下如何增加网站
  12. java 命名参数动态替换_使用Kettle的命名参数动态执行作业
  13. (三)图像的放大和缩小
  14. qq音乐android升级版,QQ音乐Android 4.8更新 温暖私享正版无损好音乐
  15. Python数据分析之数据聚合与分组运算
  16. navicat如何粘贴多行数据
  17. 学硕上几年学计算机,研究生一般要读几年毕业
  18. linux怎么移植安卓rom,【经验技巧】任意手机移植ROM 教程,超详细ROM 技术详细......
  19. 基于Python个人博客系统设计与实现 开题报告
  20. 玲珑杯 1032 A-B

热门文章

  1. 调用百度ocr接口识别表格
  2. KEIL工程中汇编文件添加条件编译宏
  3. 电脑知识:台式电脑应该选择品牌和组装,值得收藏
  4. 剪辑画中画视频,如何用视频实现画中画效果
  5. Go语言核心:Go的基本结构
  6. [iOS]判断ipa是否脱壳
  7. Ubuntu18.04无法连接Wifi解决方案
  8. 两种构造最小生成树的算法(普里姆算法,克鲁斯卡尔算法)
  9. 输出教师信息c语言作业,C语言教师管理系统代码(最新整理)
  10. 拼多多下单助手怎么一键采购、发货的?