红色,绿色,重构 - 为什么重构?

时间:2011-04-21 22:17:39

标签: unit-testing testing refactoring tdd

我正在尝试学习TDD和单元测试概念,我看到了口头禅:“红色,绿色,重构”。我很好奇你为什么要在测试通过后重构你的代码?

这对我没有意义,因为如果测试通过,那么为什么要搞乱代码呢?我也看到TDD咒语就像“只编写足够的代码来使测试通过”。

我能提出的唯一原因是,如果要让测试通过绿色,你只需要写下任何旧代码。你只是破解了一个通过测试的解决方案。显然代码很乱,所以你可以清理它。

编辑:

我在另一个stackoverflow帖子上找到了这个链接,我认为这证实了我提出的唯一原因,即“通过”测试的原始代码可以非常简单,甚至是硬编码:http://blog.extracheese.org/2009/11/how_i_started_tdd.html

8 个答案:

答案 0 :(得分:28)

通常代码的第一个工作版本 - 即使不是一团糟 - 仍然可以改进。所以你改进它,使它更清晰,更易读,删除重复,找到更好的变量/方法名称等。这是重构。而且由于你有测试,你可以安全地重构,因为测试会显示你是否无意中破坏了某些东西。

请注意,通常您不是从头开始编写代码,而是修改/扩展现有代码以添加/更改功能。并且现有代码可能无法准备好无缝地适应新功能。因此,新功能的首次实现可能看起来很笨拙或不方便,或者您可能会发现很难进一步扩展。因此,您可以改进设计,以最简单,最干净的方式整合所有现有功能,同时仍然通过所有测试。

你的问题是对年龄“的重复,如果它有效,请不要修复它”。但是,正如Martin Fowler在 Refactoring 中解释的那样,代码可以通过多种不同的方式被破解。即使它通过了所有测试,也很难理解,因此难以扩展和维护。此外,如果它看起来很草率,未来的程序员将更加小心地保持它整洁,因此它会更快地恶化,并最终降级为完全无法维护的混乱。为了防止这种情况,我们重构以尽可能保持代码干净整洁。如果我们(或我们的前任)已经让它变得混乱,重构是一项巨大的努力,对管理层和利益相关者没有明显的直接好处;因此,他们很难说服在实践中支持大规模的重构。因此,在每次代码更改后,我们都会以小的,甚至是微不足道的步骤进行重构。

答案 1 :(得分:5)

  

我看过口头禅:“红色,绿色,重构。”

这不是'口头禅',这是例行公事。

  

我也看到TDD的咒语就像“只编写足够的代码来让测试通过。”

这是一个指导原则。

现在你的问题:

  

我能提出的唯一原因是,如果要让测试通过绿色,你只需要写下任何旧代码。你只是破解了一个通过测试的解决方案。显然代码很乱,所以你可以清理它。

你快到了。关键在于TDD的“设计”部分。您不仅要编码,还在设计解决方案。这意味着确切的API可能不会一成不变,并且您的测试可能无法反映最终设计(因为它还没有完成)。虽然编码“只够通过测试”,但您会遇到一些可能改变主意并指导设计的问题。只有在你有一些工作代码后,你才能改进它。

此外,重构步骤涉及整个代码,而不仅仅是您刚刚编写的代码以通过最后一次测试。随着编码的进步,代码的所有部分之间的交互越来越复杂,重构它的最佳时间就是它的工作。

正是因为这个非常早期的重构步骤,您不必担心第一次迭代的质量。它只是一个有助于设计的概念证明。

答案 2 :(得分:4)

很难看到OP的怀疑是没有道理的。 TDD的工作流扎根于避免过早的设计决策,因为如果不排除可能会迅速演变为不明智的YAGNI野生动物园的“裤子的座位”编码,则会付出不菲的代价。[1]

这种过早设计的延迟机制是“最小可能的测试” /“最小可能的代码”工作流程,旨在避免在通常需要解决的情况下“解决”已知的缺点或要求的诱惑。甚至是遇到的情况,即在将来的一些测试用例中,缺点(应该解决?)将直接映射到接受标准,从而捕获特定的业务目标。

此外,TDD中的测试应该是a)帮助阐明设计要求,b)设计的表面问题[2],以及c)作为项目资产,捕获并记录应用于特定故事的工作,因此替代针对适当组成的测试进行自我指导的重构工作,不仅排除了该测试可能提供的任何见解,而且还拒绝了管理人员和项目计划人员有关实现特定功能的真实成本的信息。[3]

因此,我建议一个新的测试用例,其目的是在设计中引入额外的要求,是解决除了对当前被测代码和“重构”进行样式更改之外的任何已知缺点的正确方法。这个阶段无论多么好意,都违背了这一理念,实际上是邀请进行TDD应该防止的过早的YAGNI设计之旅。我相信罗伯特·马丁(Robert Martin)制定的3条规则与这种解释是一致的。 [4-对权威的公然呼吁]


[1]先前引用的http://blog.extracheese.org/2009/11/how_i_started_tdd.html优雅地演示了将设计决策推迟到最后可能的时刻的价值。 (尽管斐波那契数列也许是一个人工的例子。

[2]参见https://blog.thecodewhisperer.com/permalink/how-a-smell-in-the-tests-points-to-a-risk-in-the-design

[3]在积压订单中添加“技术”或“尖峰”故事(闻闻与否)是确保遵循正式流程并记录开发工作并证明其合理性的适当方法……如果可以,不要说服产品负责人添加它,那么您不应该浪费时间。

[4] http://www.butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd

答案 3 :(得分:3)

因为你永远不应该重构非工作代码。如果您这样做,那么您将无法知道错误最初是在那里还是由于您的重构。如果它们在重构之前全部通过,那么失败,那么你就知道你所做的改变已经破坏了什么。

它们并不意味着要编写任何草率的旧代码来通过测试。最小和马虎之间有区别。禅宗花园很小,但不是马虎。

然而,回想起来,你在这里和那里做出的微小改变可能会更好地结合到其他人调用的其他程序中。在两个测试分开工作之后是重构的时间。重构比尝试猜测一个最低限度覆盖所有测试用例的架构更容易重构。

答案 4 :(得分:3)

首先使代码正确运行,然后将其考虑在内。如果你这样做,你可能会在修复它的同时冒成混乱/重复/代码气味的风险。

将工作代码重构为代码良好的代码通常比尝试设计好的代码代码更容易。

重构工作代码的原因是为了维护。你想要删除重复的原因,例如只需要在一个地方修复某些东西,并且知道当你在某个地方修复某些东西时,你没有错过其他地方类似代码中的相同错误。如果它们的含义与您最初的意图有所不同,您想要重命名变量,方法和类。

总的来说,编写工作代码并非易事,编写良好的代码代码并非易事。如果你试图同时做两件事,你可能既没有发挥你的全部潜力,所以首先要充分注意一个,然后另一个是有用的。

答案 5 :(得分:2)

迭代,进化重构是一种很好的方法,但首先......

不应该没有说明的事情......

要构建上述一些高级笔记,您应该了解复杂系统理论中的一些重要概念。要注意的关键概念包括系统的环境结构,系统如何增长,行为方式以及组件如何相互作用。

初始条件下的敏感依赖性(混沌理论):

系统的行为将被放大到其最具影响力的趋势 - 意味着,如果您有许多破碎的Windows ,这会影响开发人员如何编写下一个模块或进行交互对于现有的,那么这个开发人员更有可能打破另一个窗口。它甚至很容易打破窗户,因为它是唯一没有破坏的窗口。

熵:

那里有很多很多 entropy 的定义;我发现变成软件工程的一个是:系统中不能用于额外工作的能量。这就是可重用性至关重要的原因。熵主要是在重复逻辑和可理解性方面找到的。此外,这与蝴蝶效应(初始条件下的敏感依赖性)和破碎的Windows紧密相关 - 逻辑越复杂,其他实施的复制品越多每个实现超过1X来维护它。

变量放大和衰减(出现理论和网络理论):

打破一个糟糕的设计是一个很好的实现,虽然它似乎在最初几次发生时会崩溃。这就是为什么拥有一个可以支持许多改编的架构是明智的。当您的系统走向熵时,您需要一种方法让模块能够正确地相互交互 - 这就是接口进入的地方。如果您的每个模块都无法进行交互,除非他们同意一致的合同。如果没有这个,你会看到你的系统立即开始适应不良的实施 - 无论哪个轮子是最狡猾的将得到油;其他模块将成为头痛的问题。因此,不仅糟糕的实现会导致更糟糕的实现,它们还会在系统规模中创建不良行为 - 从而导致您的系统适应不同的实现并放大熵最高的规模。当发生这种情况时,您所能做的只是继续修补并希望一个更改不会与这些修改发生冲突 - 导致出现紧急,不可预测的错误。

所有这一切的关键是将您的模块包装到他们自己的独立子系统中,并提供一个 Defined Architecture ,它可以让他们进行通信 - 例如 Mediator 。这会将( Decoupled )行为的集合带入自下而上系统,然后可以将其复杂性集中到为其精确设计的组件中。

使用这种类型的架构方法,你不应该在红色,绿色,重构"的第三个任期内感到非常痛苦。问题是,你的scrum master如何根据对用户和用户的好处来衡量这一点。利益相关者

答案 6 :(得分:1)

你不应该“只编写足够的代码来使测试通过”。口头禅太字面了。 请记住,您的应用程序尚未就绪,因为您的所有测试都通过在测试通过后,您显然希望重构代码,以确保代码可读且构建良好。测试可以帮助你重构,因此重构是TDD的重要组成部分。

答案 7 :(得分:0)

首先,感谢您了解测试驱动开发。这是一种非常棒的技术,可以应用于许多编码环境,可以帮助您开发一些优秀的代码,同时让您对代码可以做什么和不可以做什么有信心。

如果你看看Martin Fowler的书“Refactoring”封面上的副标题,它也会回答你的问题 - “改进现有代码的设计”

重构是对代码的转换,不应改变程序的行为。

通过重构,您可以使程序现在更容易维护,并且从现在起6个月,它还可以使代码更容易让下一个开发人员理解。