【问题标题】:Refactoring a working project重构一个工作项目
【发布时间】:2010-12-24 15:24:09
【问题描述】:

假设你有一个写得很糟糕的项目,包含很多代码异味、wtfs 等。此外,它的代码结构非常复杂,很难向其中添加任何新功能。另一方面,该项目按预期工作。

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


我想稍微澄清一下这个问题,因为我所说的“重构”是什么意思有点混乱。

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

但是,一切正常:您可以轻松地骑着它去购物、工作等。但是,它的油耗有点太高了。此外,如果您想为其安装新的前灯,那将是一场灾难,因为电线上的所有东西都是一团糟。

你买不起一辆新的,所以你必须以某种方式重构汽车:将发动机位置改为正常,整理电线等。你需要这样做,因为迟早你需要更换发动机,大灯,安装新的音响等等。另一方面,你仍然需要一些东西来驱动你每天早上上班,所以你必须确保你不会把所有事情都搞砸。

现在让我们回到项目。您将如何重构像上面的汽车一样复杂的项目,同时又不影响其主要功能和目的。


我也想让它成为一个社区维基。请编辑。

目前主要趋势是:

链接:

【问题讨论】:

  • 感谢您的更新!我要问的另一个问题是:不打破汽车有多重要?在被告知停止更改之前,您可以引入和修复多少错误?
  • 好吧,你需要它在第二天骑它时工作。所以你应该始终保持它尽可能好。

标签: refactoring


【解决方案1】:

【讨论】:

  • 这是一本优秀的书籍,可用于改造现有系统的参考。
【解决方案2】:

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

阅读 Michael Feathers 的有效使用遗留代码。轻咬代码。选择一个特别冒犯您的 WTF,并使用自动化单元测试对其进行测试。然后用合理的东西替换 WTF,确保测试继续通过。起泡,冲洗,重复。

Refactor the low-hanging fruit.

【讨论】:

  • +1 表示区别。尽管 Michael 的书非常有用,但我要指出的是,他从不承认有可能、有用、有时是必要的,甚至有时是完全安全的,不小心打破大锤并从根本上重组应用程序。
【解决方案3】:

首先,创建一套自动化单元测试,并确认您的代码覆盖率很高(70% 或更多)。在重构期间,您将非常频繁地运行这些测试,以使您自己(和管理层)相信您没有破坏任何东西。

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


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

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

【讨论】:

  • @Downvoter:除非你说出原因,否则“-2”是没有意义的。如果你想让你的投票很重要,那就大声说出来。
  • -1 如果您认为为工作项目创建自动化测试是第一步,那么您不可能重构任何规模很大的项目。单元测试不是你应该对规模系统做的第一件事。阅读 Michael Feathers 的“有效使用遗留代码”。
  • Ummm...等评论再投诉?
  • @John - 我同意这个理想。但是您似乎在否认业务逻辑和用户界面的耦合度太高,无法在代码当前状态下进行单元测试。
  • @Mike:我先发表评论,以避免被大多数不发表评论的反对者认同的问题。
【解决方案4】:

这将为从头重写提供一个视角:

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

【讨论】:

  • 乔尔的观点是商业上的:重写会失去市场份额。该论点不适用于野外的大量软件。
  • 大多数大型软件不是来自企业吗?我从这篇文章中得到的是,从头开始重写软件是有风险的。编写糟糕的内部软件可能不会导致您失去市场份额,但您可能会丢掉工作。
  • @Andy:你可以;更典型的是,失去工作的风险来自于实现功能太慢,所以有人出去买了一个新包。重写内部应用程序通常比在糟糕的设计中实现主要新功能要快得多。
  • @Mike:我真的同意。作为开发人员,当我看到讨厌的代码时,我想做的第一件事就是报废整个代码并重写它。但我偶尔会重读那篇文章以帮助我克制自己,尤其是当我知道重写不完全合理时。
【解决方案5】:

对于这个问题,有很多文字非常接近文献,并且(没有冒犯这些答案的发布者)这让我想知道人们是否只是没有重新设计一个大规模的系统真的是真的搞砸了。

在我的职业生涯中,我重写了三个重要的应用程序(重要:50kish LOC 或更大、数据层、逻辑层、集成要求)。每个系统都要求我在某些时候说“To Hell With This”作为良好实践。你知道我从中学到了什么吗?在某个时刻,它可以是非常非常安全的。当然,您需要考虑将谨慎抛诸脑后意味着什么,但与您遵循别人的良好实践理念相比,您的发布要重要得多。

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

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

我被要求实现一种通过 WAN 传输文件的方法,以便它可以与系统的核心例程一起运行。我开始创建一个不错的小型测试客户端和服务器,并围绕它们进行良好的实践、测试等。我浏览了核心系统架构并寻找合适的地方来重新存根我的代码。我发现的只是一大块讨厌的复制和粘贴代码,并带有构成单元的微小修改。

我开始更改一点点代码,但事情开始出现问题,我重置了我的更改。我提取了方法,发现变量分散在整个代码中,并且依赖于整个过程中所做的更改。我尝试提取类,却发现我正在大量提取方法,并且旧类的状态数据再次分散在整个代码中,无法转换。

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

如果您了解一个系统,如果您注意,如果您测试您的代码,并且如果您注意,那么以不安全的方式进行重大更改通常并没有错。

我会对此投反对票,但敏捷实践已经过多地模糊了人们的愿景,现在是时候停止引用文献来主导这些讨论了。如果您每天都在使用一个系统,那么您就会了解该系统,并且如果您负责并修复该死的系统,那么您可以做的任何单元测试都无法匹敌。

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

【讨论】:

  • @Mike:恭喜你,但你正在做的更多的是“改造”或“重写”,而不是“重构”。你甚至能够衡量你在这个系统中引入的错误数量吗?顺便说一句,我的回答与“敏捷”无关。单元测试是唯一能证明你善意地更改为糟糕的代码实际上并没有让事情变得更糟的方法。
  • +1 来自“实用主义与理想主义”派别。这正在变成一场有趣的辩论。
  • 请注意,我并不是说不能更改代码。我是说这样的改变不是重构。在福勒的书普及该术语之前,我们曾经做过同样的事情。事实上,如果您的功能测试套件足够好,则无需单元测试即可更改代码。但是你打算多久运行一次功能测试?在您意识到自己在几周前破坏了某些东西之前,已经进行了多少次更改?
  • @John:单元测试+重构是敏捷实践者的强项。如果你不知道,好吧,我不知道该说什么。当然,尽管在文本中使用了这个词,但 OP 与具体的重构无关。 “迁移到新框架”是这一事实的重要线索 - 你不会重构到新框架,这是一个基本的系统转变,并且从一开始就没有考虑任何业务。 Fowler 对“重构”的使用在大多数情况下都没有得到保留。正是因为这种效果,他才挑剔这件事。
  • 顺便说一句,单元测试不验证功能。单元测试可能和不明智的重组一样是一条死胡同。根据我的经验,当你从垃圾开始时,单元测试比无用更糟糕。
【解决方案6】:

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

您必须将业务逻辑与用户界面层分离,这可能需要大量工作。

现在您遇到了经典的 Catch-22 情况 - 您不应该在没有单元测试覆盖的情况下对其进行重构,并且在重构之前您不能编写单元测试。

所以唯一的解决办法就是要有耐心。准确了解应用程序应该如何工作。试着理解每段乱七八糟的代码在做什么。接受有时您无法理解某些代码的事实,其原因是代码实际上毫无意义。但毫无意义的代码看起来与基本有用的代码完全一样,只有极度努力工作才能让您有洞察力来分辨差异。

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

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

【讨论】:

  • -1:我强烈反对。在存在单元测试以证明触摸它不会破坏某些东西之前,不应触摸代码。否则,触摸它,即使是出于最好的意图,破坏某些东西,并让您失去管理层的善意。
  • +1 我已经更详细地说明了这个案例,但这就是我要说的。如果您实际上没有具有任何有意义的预期行为的单元,则单元测试毫无价值。
【解决方案7】:

我会尽可能地选择重构而不是重写,因为它的风险较小。如果做得好,您将始终拥有一个工作程序,并且随着您对其进行增量更改,它会逐渐变得更好。如果您必须提前停止(由于时间或预算限制),您仍然可以从迄今为止所做的工作中受益,因为该程序仍然可用并且比以前更好,即使它还不完美。

另一方面,如果您选择重写并且必须中途停止,那么您将得到原来的工作但糟糕的程序和新的干净但不完整的程序。您必须决定是放弃新版本并让努力白费,还是牺牲功能并通过在准备好之前切换到它来获得错误。

【讨论】:

    【解决方案8】:

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

    有效地使用遗留代码很好地解决了你不想在没有单元测试的情况下进行重构的 catch-22,但是你不能在没有重构的情况下引入单元测试。除其他外,他建议依靠自动化重构工具来避免在没有测试的情况下进行重构时引入错误。

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

    【讨论】:

      猜你喜欢
      • 2015-04-26
      • 1970-01-01
      • 2015-06-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多