重构一个有效的项目

时间:2009-12-08 23:16:54

标签: refactoring

假设你有一个写得很差的项目,包含很多代码气味,wtfs等等。而且,它的代码结构非常复杂,很难为它添加任何新功能。另一方面,该项目按预期工作。

您想重构项目,或许将其移至新框架,您将如何解决此问题?您是尝试从头开始构建一个新的技术还是使用某些技术(指定)将工作项目转换为新项目?


我想稍微澄清一下这个问题,因为在说“重构”时我的意思是混淆。

我将举一个关于汽车的例子,把它想象成一个软件项目。假设你已经建造了自己的汽车。它的构造非常奇怪:发动机是倒置的,因此所有的管道铺设不同,电线缠绕在一起,没有人知道它们从哪里开始或结束等。

然而,一切都运转正常:你可以轻松地将它带到商店,工作等等。但是,它的油耗有点太高了。此外,如果您曾经想要安装新的前灯,那么电线中的所有混乱都将是一场灾难。

你买不起新的,所以你必须以某种方式重构汽车:将发动机的位置改为正常,使电线整齐等等。你需要这样做,因为迟早你需要更换发动机,车头灯,安装新的立体声等。另一方面,你仍然需要一些东西来驱使你每天早上工作,所以你必须确保你不要搞砸了一切。

现在让我们回到这个项目。你如何重新设计像上面这辆车一样复杂的项目,同时不会打扰它的主要功能和目的。


我还想把它变成一个社区维基。请编辑。

到目前为止,主要趋势是:

链接:

8 个答案:

答案 0 :(得分:7)

答案 1 :(得分:6)

工作是您重构的唯一项目。如果您正在修复错误,那么您正在改变行为,并且重构明确是关于更改行为。但工作有不同的定义。好的 - 重构的有用之一 - 经过了良好的单元测试。如果你有良好的测试覆盖率(自动测试!),你就可以重构了。如果你不......

阅读Michael Feathers的有效使用旧版代码。蚕食代码。选择一个特别冒犯你的WTF,并通过自动单元测试测试它的地狱。然后用合理的东西替换WTF,确保测试继续通过。泡沫,冲洗,重复。

Refactor the low-hanging fruit.

答案 2 :(得分:5)

首先,创建一套自动单元测试,并确认您具有高代码覆盖率(70%或更高)。在重构期间,您将经常运行这些测试,以说服自己(和管理层)您没有破坏任何东西。

没有单元测试=没有重构。


让我改变主意。您不需要单元测试 - 当您更改代码时,您需要经常运行 的测试的高代码覆盖率。这些可以是自动化单元测试或自动功能测试。

我不相信手动测试是一个充分的替代品。在重构过程中,它们不太可能频繁运行。如果没有不断保证代码在修复代码的过程中没有被破坏,重构就不太可能成功。

答案 3 :(得分:4)

这将提供一个从头开始重写的观点:

http://www.joelonsoftware.com/articles/fog0000000069.html

答案 4 :(得分:2)

有很多文字回应这个问题非常接近文献,并且(对这些答案的海报没有冒犯),这让我想知道人们是否只是没有重新设计任何大规模的系统是真的和真正的搞砸了。

我在职业生涯中重写了三个重要的应用程序(重要:50kish LOC或更大,数据层,逻辑层,集成要求)。每个系统都要求我在某种程度上说好的做法。你知道我从中学到了什么吗?在某个时刻,它可以真的非常安全。当然,你需要考虑谨慎对待风的意义,但是你运输的重要性远远超过你跟随别人的良好实践观念。

让我举一个例子说明我在说什么:

我正在开发一个今天编写了六年的系统,该系统最初是从一个当时可能已有十年历史的应用程序转换为.NET语言的,并且是用DOS客户端编写的。逻辑。原始代码和大多数后续更新和转换都是由经验不足的程序员处理的。这是一个文档管理引擎,用于所有意图和目的,并且代码中的任何地方都没有“文档”的单一抽象。

我被要求实施一种通过WAN传输文件的方法,以便它可以与系统的核心例程一起运行。我开始创建一个漂亮的小测试客户端和服务器,包含很好的实践,测试等。我经历了核心系统架构,并寻找合适的地方来重新编写我的代码。我发现所有这些都是令人讨厌的大块,经过大量令人讨厌的复制和粘贴代码以及构成单元的微小修改。

我开始改变一些代码,事情开始破坏,我重置了我的更改。我提取了方法,发现变量分散在整个代码中,并依赖于整个过程中所做的更改。我尝试提取类,只是发现我正在提取方法批发,并且旧类的状态数据再次分散在整个代码中,并且不适合转换。

所以我说THWI。两周后,我们有一个很好的小型压缩客户端和服务器,我们的核心代码更好地解决了我们的麻烦。

如果你了解一个系统,如果你注意了,如果你测试你的代码,并且你注意了,那么以不安全的方式做出重大改变往往没有错。

我会为此付出代价,但敏捷实践已经过多地掩盖了人们的视野,现在是文献引用停止主导这些讨论的时候了。如果你每天都在使用一个系统,你就会知道系统,如果你负责并修复该死的系统,那么任何数量的单元测试都不会等于你能做到的。

当然,这就是您需要使用系统并学习系统而不仅仅是从技术到技术的原因。重写一种新语言并不像重建那种改进设计那样好。如果您发现系统中存在使用新技术可以克服的限制,那么在此时进行更改变得非常简单。

答案 5 :(得分:1)

单元测试是一件好事。但是,代码必须处于某种状态才能进行单元测试。我打赌你有数据验证,它与消息处理函数中的持久层进行对话。我确信你有数千行业务逻辑可以通过确认消息框中断用户。

您必须将业务逻辑与用户界面层分开,这可能需要做很多工作。

现在你已经有了一个经典的Catch-22情况 - 你不应该在没有单元测试覆盖的情况下重构它,并且在重构之前你不能编写单元测试。

所以唯一的解决方案就是要非常耐心。准确理解应用程序应该如何工作。尝试并理解每个凌乱的代码正在做什么。接受这样一个事实,即有时你无法理解某些代码,其原因在于代码实际上是毫无意义的。但是毫无意义的代码看起来与基本有用的代码完全相同,只有极端努力才能让你有洞察力来区分它们。

努力实现单元测试覆盖的目标 - 但缺乏单元测试不应该阻止你开始。接受这样一个事实,即您可能永远无法获得所需的单元测试覆盖率,但请牢记理想。

每天你都会对自己说:“如果我重写整个事情,这将会快得多”。不要采取这种感觉。 (见Joel的文章,已经提到过)。

答案 6 :(得分:1)

我选择重构而不是在任何可能的情况下重写,因为风险较小。如果做得好,你将总是有一个工作程序,并且当你对它进行增量更改时它会逐渐变得更好。如果你必须提前停止(由于时间或预算限制),你仍然可以获得迄今为止所做的工作的好处,因为该程序仍然可用并且它比以前更好,即使它还不完美。

另一方面,如果你选择重写并且必须在中途停止,那么你将留下原始的工作但糟糕的程序和新的干净但不完整的程序。你必须决定是否废弃新版本,让它浪费掉,或牺牲功能并通过在它准备好之前切换到它来获取bug。

答案 7 :(得分:1)

首先,我同意其他海报 - 不要屈服于从头开始重写的诱惑。毕竟,代码确实有用。其次,听起来很明显,但要将重构集中在那些需要修改的区域(对于新功能等)。可能有代码需要重构,但如果它正在工作并且在应用程序的稳定部分中,请不要管它。

有效地使用旧代码可以很好地解决您不想在没有单元测试的情况下重构的catch-22,但是如果不进行重构,则无法引入单元测试。除此之外,他建议依靠自动重构工具来避免在没有测试的情况下重构时引入错误。

其他人可能不同意我,但IMO有时候你别无选择,只能在没有单元测试的情况下进行初始重构,并依靠自动或(仔细)手动集成测试来确保你做对了。最初的重构应该允许代码在单元测试工具中运行,而不是在路上。