如何进行大型重构项目?

23
我即将开始规划我们代码库的重构工作,并希望得到一些意见和回答(我已经看到了许多类似主题的讨论,例如https://stackoverflow.com/questions/108141/how-do-i-work-effectively-with-very-messy-legacy-codeStrategy for large scale refactoring,但我有一些特定的问题(在底部):
我们正在开发一个复杂的应用程序。有大约25个开发人员在使用这个代码库。到目前为止,总共投入产品的人力年数大约为150。 当前的代码库是一个单一的项目,使用ant构建。我要开始的项目的高级目标是将代码库模块化为其各种基础设施和应用组件。 目前各个逻辑组件之间没有很好的分离,因此任何模块化工作都需要包括一些API定义和严肃的解耦以实现分离。 质量标准很低 - 几乎没有测试,肯定没有测试作为构建过程的一部分运行。
另一个非常重要的点是,这个项目需要与向客户交付版本的活动产品开发同时进行。
项目的目标:
  • 允许在不同项目中重用组件
  • 将应用程序与基础设施分离,并使它们可以独立演进
  • 通过创建API来提高可测试性
  • 简化开发人员的开发环境(少量代码签出和编译)
我的想法和问题:
  1. 你对项目目标有什么想法? 有什么需要改变的吗?
  2. 您有类似项目的经验吗? 有什么建议?
  3. 我非常关注测试的缺乏 - 因此我无法控制重构过程中是否会出现问题。 这是一个进退两难的局面,因为这个项目的目标之一是使我们的代码更易于测试...
  4. 我受到Michael Feathers' Working Effectively With Legacy Code的很大影响。 根据它,自下而上的方法是解决我的问题的方法-不要跳头进入代码库并尝试修复它,而是从小处开始,在新代码周围添加单元测试几个月,并观察代码(和团队)变得更好的程度,以至于抽象将出现,API将浮出水面等等,基本上 - 模块化将自动发生。 有人有这样一个方向的经验吗? 正如在这个主题上的许多其他问题中所看到的那样 - 主要问题在于管理上的不信任。“逐个测试类(并花费大量时间这样做)如何使我们达到稳定的系统?这是一个在现实生活中行不通的好理论。”有什么销售技巧吗?
3个回答

15

我想现在开始做比以后开始做要好,但你肯定面临着一项艰巨的任务。我曾经是一个由三人组成的团队的一员,负责重构一个类似规模的产品。虽然它是过程性代码,但我会描述一些我们遇到的问题,这些问题同样适用。

我们从底层开始,并通过选择本应高度可重复使用但却没有的函数来缓解状况。我们在现有代码上编写了一堆单元测试(根本没有!),但不久之后,我们面临了第一个大问题——现有代码存在潜在的漏洞。

我们应该修复它们吗?如果我们这样做,那么我们已经超出了重构的范畴。因此,我们记录了一个现有代码问题,希望能获得一个经过修复和新鲜测试的代码库,但当然管理层认为有更重要的优先事项需要处理。可以理解。

所以我们想尝试在新代码中修复错误。然后我们发现,原始代码中的这些错误使其他代码工作,因此真正是“概念上的错误”,而不是“功能性的错误”。也许吧。原始软件偶尔会出现未被跟踪的间歇性痉挛。

于是,我们改变了策略,决定保持错误不变,因为真正的重构应该这样做。无意中引入漏洞很容易,故意这样做要困难得多!

接下来的问题是代码太混乱了,我们编写的最初的单元测试必须大幅度地改变以适应重构。换句话说,两个移动的目标。不好。仅仅写测试就花费了很长时间,并且失去了我们对项目价值的信心。这真的是你想放弃的东西。

最后,我们发现如果要在本世纪完成这项工作,我们确实需要降低重构的程度,这意味着我们梦寐以求的代码库将无法实现。我们宣称,最可行的解决方案只是清理和简化代码,至少使其概念上更容易理解,以便未来的开发人员能够修改。

管理层认为受限重构所带来的效益不值得付出,而且由于在硬件平台(嵌入式项目)中发现了类似的可靠性问题,公司决定重新设计整个产品,采用全新的软件编写语言和对象。只有原始产品中广泛的系统测试规范才使这一计划有机会实现。


6

很明显,缺乏测试会让人们在尝试重构代码时感到紧张。任何人都会想知道你的重构是否会破坏应用程序,他们会从哪里获得信心呢?我认为,你会得到大多数答案是“这将非常困难,成功率不高”,这主要是因为你面临着一个巨大的手动任务,而且没有信心得到答案。

只有两种出路。

  • 建立一堆测试。不幸的是,这将花费很多时间,大多数经理看不到任何价值;毕竟,你到现在为止已经没有用到它们。回到信仰的问题并不能帮助你;在有用的事情发生之前,你仍然需要花费很多时间。如果他们让你建立测试,你将面临一个问题,那就是当你重构时如何改进测试;虽然它们可能不会改变功能,但是随着你构建新的 API,测试也必须相应地改变。这是超出了重新构建代码库的额外工作。

  • 自动化重构过程。如果你应用可靠的自动化转换,你可以争辩(通常不成功)说,重构后的代码保留了原始系统的功能。打败失败的论点的方法是编写这些测试(参见第一种方法),并将重构过程应用于应用程序和测试;随着应用程序更改结构,测试也必须进行更改。但从自动化机器的角度来看,它们只是应用程序代码。

很少有人做后者;你从哪里得到可以完成这些事情的工具呢?

实际上,这样的工具是存在的。它们被称为程序转换工具,用于对代码进行大规模的转换。 可以将其视为大规模重构的工具;由于规模较大,它们往往不是交互式的。

配置它们以完成手头的任务需要付出一定的努力;您必须编写自定义规则以实现所需的自定义结果。您可能无法在一周内完成此操作,但这比手动修改大型系统要少得多。您应该考虑到您已经投入了150人年的时间和精力来开发现有软件;制造混乱花费了那么长的时间。因此,花费“一些”相对较小的努力应该是可以接受的。

我只知道有三种工具可能适用于真实代码:TXLStratego/XT和我们的工具DMS软件重构工具包。前两者是学术产品(尽管TXL过去曾用于商业活动);DMS是商业产品。

DMS已被广泛用于大规模软件分析和海量转换任务。其中一项任务是 B-2隐形轰炸机语言自动翻译,另一项则更接近于您的重构问题,即将基于遗留专有RTOS组件的C++大型组件化系统实现自动架构转变为CORBA/RT,其中组件API必须从特定结构变为CORBA风格的 facet receptacle 接口,并使用CORBA / RT服务代替遗留RTOS服务。 (这些任务都是由聪明而熟练的DMS人员在1-2年时间内完成的实际工作量)。

仍然存在着测试构建问题(上述这两个示例已经有了很好的系统测试)... 在这里,我要冒一次险。我相信通过在运行代码中添加仪器来收集函数输入输出结果,可以获得自动化测试生成的工具。我们已经为源代码构建了各种仪器(显然,在仪器化之后还需要编译它),并且认为我们知道如何做到这一点。因人而异。

你可以做一些相对不那么雄心勃勃的事情:通过查找代码中已重用的部分来确定代码的可重用性。大多数软件系统包含大量克隆代码(我们的经验是10-20%[我对另一个答案中报告较小数字的PHP感到惊讶;我怀疑他们使用了弱克隆检测器])。克隆代码是应用软件中缺少抽象的提示。如果您可以找到克隆并查看它们的变化方式,则可以很容易地看出如何将其抽象成函数(或其他内容)以使其明确和可重用。

Salion Inc. 进行了克隆检测和抽象化。该论文并未探讨抽象化活动;实际上,Salion定期审查检测到的克隆,并手动修复那些极其严重或有意义的克隆,将它们转换为(通常是库)方法。最终结果是代码库实际上缩小了,并且程序员变得更加有效,因为他们拥有更好(“更可重用”)的库。

他们使用了我们的CloneDR,这是一种利用程序语法作为指南查找克隆的工具。CloneDR可以找到精确的克隆和近似匹配(替换标识符或语句),并提供特定的克隆位置和克隆参数列表,无论布局和注释如何。您可以在链接中查看多种语言的克隆报告。(我是CloneDR的发起人和作者,身兼多职)。

关于在另一个答案中讨论的 PHP 项目的“小克隆百分比”:我不知道使用了什么克隆检测器。我所知道的唯一专注于 PHP 的克隆检测器是 PHPCPD,但我认为它是一个糟糕的克隆检测器;如果我理解正确,它只能找到完全相同的克隆。请参考我们网站上的 PHP 示例进行比较。

2
这正是我们过去几年为web2project所做的事情...我们从一个现有系统(dotproject)分叉出来,该系统具有可怕的指标,例如高圆形复杂度(低:17,平均值:27,高:195M),大量重复代码(总体代码的8%)和零测试。
自分叉以来,我们减少了重复代码(总体上降至2.1%),减少了总代码量(从200kloc到155kloc),添加了近500个单元测试,并改善了圆形复杂度(低:1,平均值:11,高:145M)。 是的,我们还有很长的路要走。
我们的策略在这里详细介绍: http://caseysoftware.com/blog/phpbenelux-2011-recap - 项目分类和恢复; 以及这里: http://www.phparch.com/2010/11/codeworks-2010-slides/ - 单元测试策略; 还有像这样的各种帖子: http://caseysoftware.com/blog/technical-debt-doesn039t-disappear 只是提醒你...一开始可能不太有趣。一旦您的指标开始改善,它可以变得有趣和令人满意,但这需要一段时间。
祝好运。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接