53

在最近的一次代码审查中,我发现一个类中有几行重复的逻辑(少于 15 行)。当我建议作者重构代码时,他认为这样代码更容易理解。再次阅读代码后,我不得不同意提取重复的逻辑会稍微损害可读性。

我知道 DRY 是指导方针,而不是绝对规则。但总的来说,你愿意以 DRY 的名义损害可读性吗?

4

13 回答 13

68

重构:改进现有代码的设计

三法则

你第一次做某事,你就去做。第二次你做
类似的事情时,你对重复感到畏缩,但你还是做了重复
的事情。第三次你做类似的事情,你重构。

三击,你重构。


工作中的编码员

Seibel:所以对于这些 XII 调用中的每一个,您都在编写一个实现。
你有没有发现你积累了大量非常相似的代码?

Zawinski:哦,是的,当然。通常到第二次或第三次你已经剪切和粘贴
那段代码时,好吧,是时候停止剪切和粘贴并将其放入子程序中了。

于 2010-02-19T17:32:59.663 回答
41

我一个都不容忍。由于时间限制或其他原因,我可能最终会得到一些。但是我仍然没有找到真正需要重复代码的情况。

说它会损害可读性仅表明您不擅长选择名称:-)

于 2010-02-19T17:33:46.677 回答
38

就个人而言,我更喜欢首先让代码易于理解。

DRY 是关于简化代码的维护。在许多情况下,为了删除重复的代码而使您的代码更难理解,这比有一些重复的代码行更会损害可维护性。

话虽如此,我确实同意 DRY 是一个很好的目标,如果可行的话。

于 2010-02-19T17:22:28.020 回答
12

如果有问题的代码具有明确的业务或技术支持目的 P,您通常应该重构它。否则,您将遇到克隆代码的经典问题:最终您会发现需要修改支持 P 的代码,并且您不会找到实现它的所有克隆。

有些人建议 3 个或更多副本是重构的门槛。我相信如果你有两个,你应该这样做;在一个大系统中找到其他克隆[或者甚至知道它们可能存在] 是很困难的,无论你有两个或三个或更多。

现在,这个答案是在没有任何工具来查找克隆的情况下提供的。如果您可以可靠地找到克隆,那么重构的最初原因(避免维护错误)就不那么有说服力了(具有命名抽象的效用仍然是真实的)。您真正想要的是一种查找和跟踪克隆的方法;抽象它们是确保您可以“找到”它们的一种方法(通过使查找变得微不足道)。

可以可靠地找到克隆的工具至少可以防止您出现更新失败克隆维护错误。一个这样的工具(我是作者)是CloneDR。CloneDR 使用目标语言结构作为指导来查找克隆,因此无论空格布局、注释更改、重命名变量等如何都可以找到克隆。(它已针对多种语言实现,包括 C、C++、Java、C#、COBOL 和 PHP )。CloneDR 将在大型系统中查找克隆,无需任何指导。显示检测到的克隆以及反统一剂,这本质上是您可能已经编写的抽象。它的版本(用于 COBOL)现在与 Eclipse 集成,当您在缓冲区中的一个克隆中进行编辑时,它会向您显示,以及其他克隆在哪里,以便您可以在那里检查/修改其他克隆。(您可能会做的一件事是重构它们:)。

我曾经认为克隆是完全错误的,但人们这样做是因为他们不知道克隆与原始版本有何不同,因此在克隆行为发生的那一刻,最终的抽象还不清楚。现在我相信克隆是好的,如果您可以跟踪克隆并在抽象变得清晰之后尝试重构。

于 2010-02-19T17:25:20.550 回答
8

一旦你重复任何事情,你就会创建多个地方进行编辑,如果你发现你犯了一个错误,需要扩展它,编辑,删除或任何其他你可能遇到的几十个其他原因强制改变。

在大多数语言中,将块提取到适当命名的方法很少会损害您的可读性。

这是您的代码,符合您的标准,但我对您的“多少钱?”的基本回答。是没有...

于 2010-02-19T17:53:52.290 回答
3

您没有说是什么语言,但在大多数 IDE 中,它是一个简单的重构 -> 提取方法。这要容易得多,而且带有一些参数的单个方法比 2 块重复代码更易于维护。

于 2010-02-19T17:24:58.117 回答
3

很难抽象地说。但我自己的信念是,即使是一行重复的代码也应该变成一个函数。当然,我自己并不总是达到这个高标准。

于 2010-02-19T17:26:22.257 回答
3

重构可能很困难,这取决于语言。所有语言都有局限性,有时重复逻辑的重构版本在语言上可能比重复代码更复杂。

当具有不同基类的两个对象在操作方式上具有相似性时,通常会出现代码 LOGIC 的重复。例如 2 个 GUI 组件,它们都显示值,但不实现用于访问该值的通用接口。重构这种系统要么需要采用比所需更多的通用对象的方法,然后是类型检查和强制转换,要么需要重新考虑和重构类层次结构。

这种情况与代码完全重复的情况不同。如果我只打算使用两次,并且两次都在同一个函数中,我不一定会创建一个新的接口类。

于 2010-02-19T22:55:20.423 回答
2

我不接受重复代码。如果某些东西在多个地方使用,它将成为框架的一部分,或者至少是一个实用程序库。

最好的代码行是不写的代码行。

于 2010-07-06T08:29:18.413 回答
1

DRY 的重点是可维护性。如果代码更难理解,则更难维护,因此如果重构损害了可读性,您实际上可能无法达到 DRY 的目标。对于少于 15 行代码,我倾向于同意你同学的观点。

于 2010-02-19T17:24:52.650 回答
1

一般来说,没有。反正不是为了可读性。总有一些方法可以将重复的代码重构为一种意图揭示通用方法的意图,这种方法读起来就像一本书,IMO。

如果你想为违反 DRY 提出一个论据以避免引入依赖关系,那可能会更重要,你可以在这里得到 Ayende 的固执己见以及代码来说明这一点。

除非您的开发人员实际上是 Ayende,尽管我会坚持 DRY 并通过意图揭示方法获得可读性。

BH

于 2010-02-19T17:35:38.313 回答
0

这实际上取决于很多因素,代码的使用量、可读性等。在这种情况下,如果只有一份代码副本并且这样更容易阅读,那么也许就可以了。但是如果你需要在第三个地方使用相同的代码,我会认真考虑将它重构为一个通用函数。

于 2010-02-19T17:25:18.623 回答
0

可读性是代码可以拥有的最重要的东西之一,我不愿意在它上面妥协。重复的代码是难闻的气味,而不是致命的罪过。

话虽如此,这里还是有问题的。

如果此代码应该是相同的,而不是巧合相同,则存在可维护性风险。我会在每个地方都有指向另一个地方的评论,如果它需要在第三个地方,我会重构它。(实际上我确实有这样的代码,在两个不共享适当代码文件的不同程序中,所以每个程序中的注释都指向另一个。)

您还没有说这些线条是否构成一个连贯的整体,执行一些您可以轻松描述的功能。如果他们这样做,请将它们重构出来。这不太可能是这种情况,因为您同意代码嵌入在两个地方更具可读性。但是,您可以寻找更大或更小的相似性,并可能分解出一个函数来简化代码。仅仅因为十几行代码重复并不意味着一个函数应该由那十几行组成,而不是更多。

于 2010-02-19T17:33:38.427 回答