【问题标题】:Test -> Code -> Refactor, when should we start a refactoring?测试 -> 代码 -> 重构,我们应该什么时候开始重构?
【发布时间】:2009-08-30 22:33:54
【问题描述】:

TDD 圈是:

"Write failing Test" -> "Write Code to fit a Test" -> "Refactor"

在“编码”步骤中,我们假定编写代码尽可能简单,只是为了修复失败的测试。在真正需要之前,我们不应该编写复杂的代码。

下一步是重构。我们应该重构刚刚写好的代码吗?我认为没有真正的意义,因为只要测试通过,我们应该对代码感到满意。

可能重构活动应该由某些事情强制执行,例如代码编写是由失败的测试引起的。这里有一些可能的因素:

  1. 要编写的下一个测试需要对系统进行一些更改(重构)
  2. 性能很差。我们需要在不破坏功能的情况下对其进行改进
  3. 代码审查表明编写的代码难以理解。

您认为启动重构的其他原因是什么?

另外,这个方案是否正确:

"Write failing Test" -> "Code" -> "Refactor" -> "Write failing Test"

或许应该被视为

"Write failing Test" -> "Code/Refactor" -> "Write failing Test"
+
"External factor (like bad performance)" -> "Refactor".

【问题讨论】:

    标签: refactoring tdd


    【解决方案1】:

    你可以在通过测试的同时编写一些非常丑陋的代码;现在重构不是因为它不起作用,而是它不是很可维护。这就是重点。

    编写代码以适应多个测试后,您可以开始全面了解——这些代码之间是否存在重叠,您可以在哪些地方排除一些重复项?

    【讨论】:

    • 另请注意,重构是在代码“闻起来”时进行的,而不是在它不工作时或仅在代码审查之后进行。于是写了一段代码,让测试通过,感受一下——重构。
    【解决方案2】:

    TDD 是一个很好的工具,可以让您保持跟踪/完成任务。问题:

    “编写失败的测试”->“代码/重构”->“编写失败的测试”

    你提议,是不是很容易变成:

    “编写失败的测试”->“重构”->“代码”->“编写失败的测试”

    然后

    “编写失败的测试”->“重构”->“重构”->“重构”->“代码”->“编写失败的测试”

    这是你想要避免的。通过在实施开始时进行重构,您沉迷于推测性开发,并且没有实现编码会话的目标。避免切线并构建您不一定需要的东西很容易。如果您的功能正常工作并且测试通过,那么决定何时停止重构会容易得多。而且您可以随时停止,因为您的测试通过了。

    此外,当您的测试不是绿色时,您不想重构。

    其他几个小点:

    1. 我认为大多数文献对重构的定义略有不同。这不是“对系统进行一些更改”或性能增强,而是不会改变行为但改进设计的特定更改。如果您接受该定义,那么性能改进并没有真正的资格:它们是需要自己的验收测试的正常开发任务。我通常尝试将这些构建为面向最终用户的故事,这样做的好处是显而易见的。有意义吗?

    2. 我认为您是对的,TDD 实践并未专门解决代码审查期间发现的设计问题。 (有关此问题的其他解决方案,请参阅反射和结对编程。)这些往往是更大的、跨故事的问题,被构建为“代码债务”,并且必须花费一些时间来定期清理它。这可能是一个单独的项目,但我个人总是喜欢将其作为另一个“真实”故事的一部分。上次我这样做时,我们发现我们遇到了问题,但最终等了几周,直到我们有一个相关的故事来处理它。我们遵循了首先实现新功能的 TDD 实践——即使我们知道这是错误的。但后来我们真正了解了发生了什么以及为什么会如此混乱,然后在重构阶段花费了比平时更长的时间。效果很好。

    【讨论】:

    • 我只想强调Refactor->Refactor->Refactor 部分会导致无限的变化,这会占用大量的开发时间。另请注意,每次代码更改后,您都应该再次运行测试,无论该代码更改是新功能还是只是重构。
    • 假设我们编写了一个应用程序,到目前为止,它允许我们列出任务并发布新任务。但是要通过测试,拥有一个全局任务列表就足够了。当然,客户希望“任务被持久化”。我可以决定一个好的做法是实现 IRepository。我需要现有的代码才能使用它。所以我重构了方法,确保它们调用 IRepository。这次重构是由新的用户故事引起的。简而言之,我想弄清楚的是——我们是否可以使用用户故事/测试来驱动代码编写以及重构本身?你怎么看?
    • 我们稍微偏离了 TDD,所以我会保持简短。 refactoring 的定义(在 TDD 世界中)是在您的测试为绿色之前您不会开始重构,除非它们是绿色的(再次/仍然),否则您还没有完成。所以它们是一个有用的工具,但它们不能真正“推动”重构。我对用户故事实践中的 INVEST 相当自律。我总是尝试重新构建必要的重构以成为用户故事的一部分,就像您在上面所做的那样。我永远不会有“重构为 IRepository”的故事,而是将其作为面向用户的有价值功能的组成部分。
    【解决方案3】:

    嗯...我通常认为这些“外部”重构与 TDD 周期本身是分开的。在使用 TDD 完成功能 XYZ 之后,应该有一组健康的测试来防止通过重构引入错误(假设代码覆盖率是最佳的,等等)。无论如何,性能瓶颈和难以理解的代码通常会在事后突然出现,因此我认为此时进行重构是理想的。您可以提高性能,使代码更易于理解,并在使用测试时做任何其他需要做的事情,以确保不会将错误引入系统。

    所以为了回答您的问题,我不认为外部重构适合 TDD 模式,尽管标识符(如果您愿意的话,代码有异味)绝对是您在开发代码时需要跟踪的项目。

    【讨论】:

    • 如果我们排除“外部”重构......您认为 TDD 是否将重构作为一个单独的步骤,或者它将是“带有重构的代码”?仅在不更改现有代码就无法实现新测试的情况下进行重构。
    • 好吧,模式第三步的想法是在测试通过后让代码变得更好。并且由于测试已经到位,因此可以安全地完成。就为了编写下一个测试而进行的重构而言,编译器本身就是失败测试的一个方面。您为测试编写代码,观察它“失败”(即无法编译),然后重构代码以使其能够编译。
    【解决方案4】:

    除了因需要提高性能而引发的重构之外,我通常会在编写代码以通过测试时注意到一个我可以共享代码或更好、更优雅的方式来完成某事的地方。此时,您完成了代码以通过测试,确保所有测试都通过,然后返回并进行重构。这可能会或可能不会涉及您编写的确切代码,很可能会,但它可能是您在编写新代码时刚刚注意到的。

    有时我会注意到一些可以重构的东西,但我认为我现在正在做的事情更重要。在这一点上,我会注意这个问题以供以后重构。最终,该重构将变得与下一个特性一样或更重要,并且它将在红/绿/重构周期的重构阶段完成。在这种情况下,它可能与我刚刚的工作无关。

    【讨论】:

      【解决方案5】:

      除了在主要版本发布前一两周之外,还有什么不好的时间进行重构吗?

      【讨论】:

      • 呵呵,我觉得这引出了另一个模型:[Refactor] -> Test -> [Refactor] -> Code -> [Refactor]
      • 现在是重构的好时机,因为您将再次运行测试以证明在提交更改之前您没有破坏任何东西。如果您只根据当前项目的要求进行重构并且从不添加新功能,那只会是不好的。
      • 假设您的测试真正涵盖了所有内容。我更愿意将主要的重构留在发布周期的开始。我认为风险较小。
      【解决方案6】:

      三法则:

      1. 当你第一次做某事时,就把它做好。

      2. 当你第二次做类似的事情时,畏惧不得不重复但还是做同样的事情。

      3. 当你第三次做某事时,开始重构。

      添加功能时:

      1. 重构有助于您理解其他人的代码。如果你必须处理别人的脏代码,首先尝试重构它。干净的代码更容易掌握。您不仅会为自己改进它,还会为那些在您之后使用它的人改进它。

      2. 重构使添加新功能变得更加容易。在干净的代码中进行更改要容易得多。

      修复错误时:

      1. 代码中的错误与现实生活中的错误相似:它们存在于代码中最黑暗、最肮脏的地方。清理你的代码,错误就会自己发现。

      2. 经理们喜欢主动重构,因为它消除了以后对特殊重构任务的需要。快乐的老板造就快乐的程序员!

      在代码审查期间:

      1. 代码审查可能是在代码公开之前整理代码的最后机会。

      2. 最好与作者结对执行此类评论。这样您就可以快速解决简单的问题,并衡量解决更困难问题的时间。

      【讨论】:

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