对于将对提高质量产生最大影响的旧版代码库,我们该怎么办?
当我们在旧版代码库中工作时,随着时间的推移,什么将产生最大的影响,从而提高代码库的质量?
- 删除未使用的代码
- 删除重复的代码
- 添加单元测试以提高覆盖率低的测试覆盖率
- 在文件之间创建一致的格式
- 更新第三方软件
- 减少由静态分析工具(即Findbugs)生成的警告
多年来,许多开发人员已经编写了代码库,这些专家具有不同的专业知识水平,其中许多领域未经测试,有些领域未经测试,而没有花费大量时间编写测试。
解决方案
我要说的是"删除重复的代码",这意味着我们必须提取代码并对其进行抽象处理,以便可以在多个地方使用它,从理论上讲,这使错误更易于修复,因为我们只需修复一段代码,而不是许多代码,而是要修复其中的错误。
- 阅读迈克尔·费瑟(Michael Feather)的书"有效地使用旧版代码"
这是本好书。
如果我们不喜欢该答案,那么我能提供的最佳建议是:
- 首先,停止制作新的旧版代码[1]
[1]:旧版代码=没有单元测试的代码,因此未知
在没有自动测试套件的情况下更改遗留代码是危险且不负责任的。没有良好的单元测试覆盖率,我们可能无法知道这些更改将产生什么影响。 Feathers建议采用"束缚"方法,在其中隔离需要更改的代码区域,编写一些基本测试以验证基本假设,进行由单元测试支持的小更改,然后从那里进行工作。
注意:我并不是说我们需要停止一切并花数周的时间对所有内容进行测试。恰恰相反,只需在需要测试的区域周围进行测试并从那里进行锻炼即可。
吉米·博加德(Jimmy Bogard)和雷·休斯顿(Ray Houston)对与此非常相似的主题进行了有趣的屏幕投射:
http://www.lostechies.com/blogs/jimmy_bogard/archive/2008/05/06/pablotv-eliminate-static-dependencies-screencast.aspx
我可以将这个问题与目前我的"那些"旧学校代码库之一联系起来。它不是真正的遗留物,但肯定没有遵循多年的趋势。
我会告诉我们我每天都会打扰我的时候想要解决的问题:
- 记录输入和输出变量
- 重构变量名,以便它们实际上表示其他名称和一些匈牙利符号前缀,后跟三个字母的首字母缩写,含义有些晦涩难懂。 CammelCase是必经之路。
- 我很害怕更改任何代码,因为它会影响使用该软件的数百个客户,甚至有人会注意到甚至最晦涩的副作用。任何可重复的回归测试都将是一件好事,因为现在的数量为零。
其余的真的是花生。这些是遗留代码库的主要问题,它们确实占用了大量时间。
添加单元测试以提高测试覆盖率。拥有良好的测试覆盖范围将使我们无需担心就可以重构和改进功能。
CPPUnit的作者为此写了一本不错的书,《有效地使用旧版代码》。
从本质上讲,将测试添加到旧版代码比从头创建测试更具挑战性。我从书中带走的最有用的概念是"接缝"的概念,Feathers将其定义为
"a place where you can alter behavior in your program without editing in that place."
有时值得重构以创建接缝,这将使将来的测试更加容易(或者首先可能进行)。谷歌测试博客中有关于该主题的几篇有趣的文章,主要围绕依赖注入的过程进行。
我要说的是,这很大程度上取决于我们要对遗留代码执行的操作...
如果它将无限期地保持在维护模式下并且工作正常,那么最好什么都不做。 "如果它没有损坏,请不要修复它。"
如果无法正常工作,则删除未使用的代码并重构重复的代码将使调试变得更加容易。但是,我只会在错误代码上进行这些更改。
如果我们计划使用2.0版,请添加单元测试并清理将要提出的代码
好的文档。作为必须维护和扩展遗留代码的人,这是头号问题。更改我们不理解的代码很困难,即使不是十分危险。即使我们很幸运地获得了文档化的代码,我们如何确定该文档是正确的?它涵盖了原作者的所有隐性知识吗?可以说出所有的"技巧"和极端情况吗?
好的文档可以使原始作者以外的其他人理解,修复和扩展甚至不好的代码。我将在一周中的任何一天使用经过完善但文档记录深刻的经过破解的代码,使我能够理解这些代码。
我使用的是由约50位程序员编写和修改的旧版1M LOC应用程序。
* Remove unused code
几乎没用……只是忽略它。我们将不会从中获得很高的投资回报率(ROI)。
* Remove duplicated code
实际上,当我修复某些问题时,我总是会搜索重复项。如果找到一些通用函数,或者将所有出现的代码都注释掉以进行复制(有时,投入通用函数是不值得的)。主要思想是,我讨厌多次执行相同的操作。另一个原因是因为总是有人(可能是我)忘记检查其他情况了...
* Add unit tests to improve test coverage where coverage is low
自动化的单元测试非常棒……但是,如果积压工作量很大,除非我们遇到稳定性问题,否则任务本身很难推广。继续研究我们正在研究的部分,并希望在几年内我们获得不错的覆盖范围。
* Create consistent formatting across files
IMO格式上的差异是传统的一部分。它为我们提供了有关编写代码的人或者时间的提示。这可以为我们提供一些有关如何在代码的那部分表现的线索。进行重新格式化的工作并不是一件很有趣的事情,也不会给客户带来任何价值。
* Update 3rd party software
仅当有一个非常不错的新功能或者新操作系统不支持我们拥有的版本时,才执行此操作。
* Reduce warnings generated by static analysis tools
值得。有时警告可能会隐藏潜在的错误。
我对必须使用的遗留代码所做的最大的一件事情就是围绕它构建一个真正的API。我围绕着1970年代风格的COBOL API构建了.NET对象模型,因此所有不安全的代码都放在一个地方,API的本机数据类型和.NET数据类型之间的所有转换都在一个地方,主要方法返回并接受DataSet,依此类推。
这是很难做到的,我仍然知道其中仍然存在一些缺陷。随着所有的编组工作的进行,这也不是绝对有效的。但是另一方面,我可以构建一个DataGridView,将数据往返到一个已有15年历史的应用程序中,该应用程序将其数据在Btrieve(!)中保留大约半小时,并且可以正常工作。当客户来找我做项目时,我的估计是几天和几周而不是几个月和几年。
与乔什·塞加尔(Josh Segall)所说的类似,我想说点什么。我已经研究了几个非常大的遗留系统,这些系统遗忘在了我的腿上,我发现最大的问题是跟踪我已经了解的特定代码部分的知识。一旦开始放置便笺,包括"待办事项"便笺,我便不再重新确定已经确定的内容。然后,我将重点介绍这些代码段如何流动和交互。