红色,绿色,重构——为什么要重构?

14

我正在尝试学习TDD和单元测试概念,我见过这个口号:“红灯、绿灯、重构”。我很好奇为什么在测试通过后你需要重构代码?

对我来说这毫无意义,因为如果测试通过,那么你为什么要改动代码呢?我还看到TDD的口号:“只编写足够使测试通过的代码。”

我能想到的唯一理由是,如果为了让测试通过变绿色,你只是随便编写任何旧代码。 你只是匆忙地拼凑出一个解决方案来过关。 那么显然代码就会一团糟,所以你需要清理它。

编辑:

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

8个回答

29

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

请注意,通常情况下,您不是从头开始编写代码,而是修改/扩展现有代码以添加/更改功能。现有代码可能无法平稳地容纳新功能。因此,新功能的第一次实现可能看起来笨拙或不方便,或者您可能发现难以进一步扩展。因此,您改进设计以最简单、最干净的方式融合所有现有功能,同时仍然通过所有测试。

您的问题是古老的问题“如果它能运行,就不要修理它”的重新表述。然而,正如马丁·福勒在《重构》中所解释的那样,代码可以以许多不同的方式被破坏。即使它通过了所有测试,也可能难以理解,因此难以扩展和维护。此外,如果它看起来很杂乱,未来的程序员将更加不注意保持其整洁,因此它将更快地恶化,并最终退化为完全无法维护的混乱代码。为防止这种情况发生,我们通过重构始终尽可能保持代码的清洁和整洁。如果我们(或我们的前辈)已经让它变得杂乱不堪,那么重构需要付出很大的努力,而管理层和利益相关者很难被说服在实践中支持大规模的重构,因为它没有明显的直接好处。因此,我们在每次代码更改后小心翼翼地进行微小甚至琐碎的重构。


@Péter Török,你的回答简洁优雅,点赞! - John Tobler

6
我看到过这样的口号:“红,绿,重构”。 这不是一句“口号”,而是一个例行程序。 我还看到过TDD的口号,比如“只写足够让测试通过的代码”。 那只是一个指导方针。 现在你的问题: 唯一能想到的理由是,如果要用绿色使测试通过,您只需随意编写任何旧代码。您只需拼凑出一个解决方案以使测试通过。然后显然代码就很混乱,所以您可以清理它。 你已经接近答案了。关键在于TDD的“设计”部分。你不仅在编码,你还在设计你的解决方案。这意味着确切的API可能还没有确定,你的测试可能不能反映最终的设计(因为它还没有完成)。在编码“只有足够的测试通过”的过程中,你会遇到一些问题,这些问题可能会改变你的想法并指导设计。只有在有工作的代码之后,你才能改进它。 此外,重构步骤涉及整个代码,而不仅仅是你刚刚编写以通过最后一个测试的部分。随着编码的进展,您的代码之间有越来越复杂的交互,最好的重构时间是在它运行后不久。 正是因为这个非常早期的重构步骤,你不必担心第一次迭代的质量。它只是一个概念证明,有助于设计。

你是一个精力充沛的人。我喜欢你的风格。 - Christopher Perry

6
很难看出OP的怀疑不是有道理的。TDD的工作流程根源于通过施加重大成本(如果不是排除)避免过早的设计决策,以防止“随意编码”迅速演变为不明智的YAGNI safari。[1] 这种推迟过早设计的机制是“最小可能测试”/“最小可能代码”的工作流程,旨在避免在通常需要解决或甚至遇到之前,“修复”所感知的缺陷或需求的诱惑,即假定该缺陷将(应该?)在一些未来的测试用例中得到解决,该测试用例直接映射到捕获特定业务目标的验收标准。

此外,TDD中的测试应该a)帮助澄清设计要求,b)揭示设计问题[2],c)作为捕获和记录应用于特定故事的工作量的项目资产,因此,将自我导向的重构努力替换为适当组合的测试不仅排除了测试可能提供的任何见解,而且也否认了管理层和项目计划者实现特定功能的真实成本信息。[3]
因此,我建议采用一种新的测试用例来引入设计中的额外需求,这是解决任何认为当前被测试代码存在缺陷的适当方式,而“重构”阶段虽然出于良好意图,但与这种哲学相悖,实际上邀请了进行过早的YAGNI设计冒险,而TDD本应该防止这种情况。我相信罗伯特·马丁的三条规则版本与这种解释是一致的。[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
你应该先让代码正确运行,然后再进行重构。如果你反过来做,就有可能在修复问题时出现混乱、重复或代码质量低下等问题。
通常来说,将可工作的代码重构为高质量的代码比一开始就设计高质量的代码更容易。
对可工作的代码进行重构是为了方便维护。你需要消除重复代码,这样只需在一个地方修复问题即可,同时也可以确保在类似的代码中修复问题时不会遗漏相同的错误。如果变量、方法或类的含义与最初的意图不同,你还需要重命名它们。
总之,编写可工作的代码和高质量的代码都不是轻松的任务。如果你试图同时完成两者,可能都无法达到最佳状态,因此先专注于其中一个,再处理另一个是非常有用的。

3

迭代性和渐进重构是一种好的方法,但首先...

一些不应该被忽视的事情...

要在上面的高级笔记基础上建立,您应该理解一些重要的概念来自于复杂系统理论。需要注意的关键概念包括系统环境结构、系统生长方式、系统行为方式以及组件之间的相互作用。

初始条件敏感性(混沌理论):

系统的行为会朝着它最具影响力的倾向放大,这意味着,如果您有许多破窗户影响开发人员编写下一个模块或与现有模块交互的方式,那么这个开发人员更有可能破坏另一个窗户,甚至会因为只有这个窗户没有破损而去破坏它。

熵:

有许多许多的定义;我发现一个适用于软件工程的定义是:系统中不能用于附加工作的能量数量。这就是为什么可重用性至关重要。熵主要表现为重复的逻辑和可理解性。此外,这与蝴蝶效应(初始条件敏感性)和破窗户密切相关 - 重复的逻辑越多,额外实现就需要更多的复制黏贴并且每个实现维护它们都是大于1X的。

变量放大和阻尼(新兴理论和网络理论):

打破一个坏设计是一个好的实践,但当它发生几次时,似乎所有的一切都乱了套。这就是为什么有一个可以支持多种适应方法的架构是明智的。随着您的系统趋向熵,您需要一种使模块能够正确交互的方式 - 这就是接口的作用。如果每个模块不能相互交互,除非它们已经同意了一致的协议,否则您将看到您的系统立即开始适应不良实现 - 最受关注的轮子会得到润滑剂;其他模块将成为头疼的来源。因此,不良实施不仅会导致更多的不良实施,还会在系统规模处创建不良行为 - 使您的系统大规模适应不同的实施并放大熵。当这种情况发生时,您所能做的就是继续打补丁,并希望一个变化不会与这些适应发生冲突 - 导致新的、不可预测的错误。

所有这一切的关键在于将模块包含到它们自己的离散子系统中,并提供一个允许它们进行通信的定义架构,例如一个调解员。这将把一系列(解耦)行为带入一个自底向上系统中,该系统可以将其复杂性集中于为此设计的组件中

采用这种架构方法,你不应该在“红、绿、重构”的第三个阶段遇到显著的问题。问题是,你的Scrum主管如何以用户和利益相关者的利益为衡量标准来衡量这一点?


1
你不应该太过于字面理解“只编写足以通过测试的代码”这个口号。 记住,仅仅因为所有测试都通过了,你的应用程序并没有准备好。你需要在测试通过后重构代码,以确保代码可读性和良好的架构。测试的目的是帮助你进行重构,所以重构是 TDD 的重要组成部分。

0

首先,感谢您关注测试驱动开发。这是一种非常棒的技术,可以应用于许多编码情况,帮助您开发出优秀的代码,并且让您对代码能够做什么和不能做什么充满信心。

如果您看一下 Martin Fowler 的书《重构》封面上的副标题,它也回答了您的问题——“改善现有代码的设计”。

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

通过重构,您可以使程序更易于维护,现在和6个月后都是如此,同时还可以使代码更容易被下一个开发人员理解。


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