测试 -> 代码 -> 重构,我们应该什么时候开始进行重构?

5
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".
6个回答

9
你可以在通过测试的同时编写一些相当丑陋的代码;现在进行重构是因为它不太可维护,而不是因为它不能工作。这就是要点。
在编写适用于多个测试的代码后,您可以开始从更大的角度来看待问题——是否存在代码之间的重叠部分,您可以将一些重复内容剥离出来?

还要注意,重构是在代码“有异味”时进行的,而不是在代码无法工作或仅在代码审查后才进行。因此,编写一个使测试通过的代码,感受异味-重构。 - Yaroslav Yakovlev

6
TDD是一个很好的工具,可以帮助你保持在正确的轨道上。你提出的问题是:“编写失败的测试” -> “编码/重构” -> “编写失败的测试”,它很容易变成:“编写失败的测试” ->“重构”->“编码”->“编写失败的测试”,或者变成“编写失败的测试” ->“重构”->“重构”->“重构”->“编码”->“编写失败的测试”,这是需要避免的。通过在实施开始时进行重构,你正在沉迷于投机性开发,并且无法达到编码会话的目标。很容易走向岔路并建立你不一定需要的东西。如果你的功能正常运行并且测试通过,那么决定何时停止重构就更容易了。而且,由于测试已经通过,你随时可以停止重构。此外,在测试未通过时进行重构也是不可取的。还有两个小提示:1.我认为大多数文献对重构的定义略有不同。它不是“对系统的一些更改”或性能增强,而是特定的更改,不改变行为但改善设计。如果接受这个定义,那么性能改进并不真正符合要求:它们是需要自己的验收测试的常规开发任务。我通常尝试将这些框架化为面向最终用户的故事,其中做它们的好处是清楚的。有道理吗?2.我认为你说得对,TDD实践并没有特别解决代码审查中暴露的设计问题。(请参见反思和配对编程以获取其他解决方案。)这些问题往往是更大的、跨故事的问题,积累成“代码债务”,需要定期清理。这可以是一个单独的项目,但我个人总是喜欢将其作为另一个“真正”的故事的一部分来完成。上次我这样做时,我们确定了问题,但最终等待几周,直到我们有一个相关的故事来解决它。我们遵循了先实现新功能的TDD实践,即使我们知道它非常错误。但后来我们真正理解了发生了什么以及为什么会出现混乱,然后在重构阶段花费了比平常更长的时间。效果很好。

我想强调的是,重构->重构->重构 部分会导致无限制的更改,这可能会消耗大量的开发时间。此外,需要注意的是,在每次代码更改后,都应该再次运行测试,无论该代码更改是新功能还是仅仅是重构。 - quamrana
考虑我们编写一个应用程序,到目前为止,它允许我们列出任务并发布新任务。但是为了通过测试,拥有全局任务列表就足够了。当然,客户希望“任务被持久化”。我可以决定实现IRepository是一个好的做法。而且我需要现有的代码来使用它。因此,我重构方法,确保它们调用IRepository。这次重构是由新的用户故事引起的。简而言之,我试图弄清楚的是 - 我们是否可以使用用户故事/测试来驱动重构本身,而不仅仅是编写代码?你认为呢? - alex2k8
我们正在逐渐远离TDD,所以我会简短地介绍一下。在TDD世界中,“重构”的定义是:只有当你的测试通过了,并且它们仍然通过时,你才开始重构,而且除非测试再次通过,否则你永远不会完成。因此,测试是一个有用的工具,但它们不能真正“驱动”重构。我对用户故事实践中的INVEST原则相当自律。我总是尝试将必要的重构重新塑造为用户故事的一部分,就像你上面做的那样。我永远不会有一个故事叫做“重构成IRepository”,而是将其作为有价值的用户功能的一个组成部分。 - ndp

1
嗯...我通常认为这些所谓的“外部”重构与TDD周期本身是分开的。使用TDD完成XYZ功能后,人们应该具备一套健康的测试来防止在重构过程中引入错误(假设代码覆盖率是最优的等),性能瓶颈和难以理解的代码通常会在事后出现,因此在那时进行重构是理想的。您可以利用测试来确保您不会将错误引入系统,同时提高性能、使代码更易于理解并执行其他需要的操作。

因此,回答您的问题,我认为外部重构不符合TDD模式,尽管标识符(如果您愿意称之为代码气味)绝对是在开发代码时要跟踪的项目。


如果我们排除“外部”重构...你认为TDD是否将重构作为一个单独的步骤,还是它将成为“带有重构的代码”?只有在无法实现新测试而不改变现有代码的情况下才进行重构。 - alex2k8
好的,模式的第三步的想法是在测试通过后使代码更好。既然测试已经存在,这可以安全地完成。就重构而言,为了能够编写下一个测试,编译器本身就是失败测试的一个方面。你为测试编写代码,观察它“失败”(即无法编译),然后重构代码以使其编译。 - Doctor Blue

1

除了由于需要提高性能而触发的重构之外,通常在编写代码以通过测试时,我会注意到可以共享代码或更好、更优雅地完成某些任务的地方。此时,您需要完成代码以通过测试,确保所有测试都通过,然后返回并进行重构。这可能涉及到您编写的确切代码,但很可能是您在编写新代码时注意到的内容。

有时候,我会注意到一些可以重构的东西,但决定现在正在处理的工作更重要。这时,我会记录下这个问题,以便以后进行重构。最终,那个重构将变得与下一个功能一样或更重要,并且它将在红/绿/重构周期的重构阶段中完成。在这种情况下,它可能与我刚刚处理的内容无关。


1

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


呵呵,我认为这会导致另一个模型:[重构] -> 测试 -> [重构] -> 代码 -> [重构] - alex2k8
1
没有重构的坏时机,因为在提交更改之前,您将再次运行测试以证明您没有破坏任何东西。只有当您仅进行重构而从不根据当前项目要求添加新功能时,才会出现问题。 - quamrana
假设你的测试真正覆盖了所有情况。我更喜欢在发布周期开始时进行重大重构。我认为这样风险较小。 - WW.

0

三原则:

  1. 第一次做某事时,只需完成任务。

  2. 第二次做类似的事情时,会感到重复而烦恼,但仍需完成相同的任务。

  3. 第三次做某事时,开始重构代码。

添加功能时:

  1. 重构有助于理解他人的代码。如果你必须处理别人的混乱代码,请先尝试重构它。清晰的代码更容易理解。你不仅可以为自己改进代码,也可以为后续使用者改进代码。

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

修复错误时:

  1. 代码中的错误就像现实生活中的错误一样:它们存在于代码中最黑暗、最肮脏的地方。清理代码,错误几乎就会自行发现。

  2. 经理欣赏积极的重构,因为这消除了以后需要特殊重构任务的需要。开心的老板会让程序员更开心!

在代码审查期间:

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

  2. 最好与作者一起进行这样的审查。这样,您可以快速修复简单问题,并评估修复更困难问题所需的时间。


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