违反DRY原则总是不好?

我一直在讨论DRYDo not Repeat Yourself )原则,也就是所谓的DIE复制是邪恶的 ),有票,任何简单的代码重复总是邪恶的。 我想听听你对以下几点的看法:

  1. 不确定的未来 。 比方说,我们在两个地方有相同的代码。 关键是,这两个地方只有偶然的内涵。 有一种可能性,他们将来会有所不同,因为他们的语境和语义是不同的。 从这些地方抽象化并不便宜,如果其中一个地方发生了变化,那么抽象化就更加昂贵。
  2. 可读性 。 有一个复杂的计算涉及几个variables或步骤。 在另一个代码的地方还有另一个,有一些相同的部分。 问题是,如果我们拿出公共部分,计算的可读性会下降,创build抽象将很难给出一个描述性的名字。 更糟糕的是,如果某一部分algorithm在未来会像第一点那样变化的话。

上述情况是否有理由放弃抽象过程,只留下重复的代码,以利于将来更改或只是可读性的风险?

这些是违反DRY的完全正当理由。 我应该补充一点:表演。 这很less是一个大问题,但是它可以有所作为,抽象可能会降低速度。

实际上,我将添加第四个:浪费时间,并且可能通过更改可能已经正常工作的代码库的两个(或更多)部分来引入新的错误。 如果你不需要这些东西,怎么抽象这些东西是否值得花费呢,而且在将来可能不会节省很多时间呢?

通常情况下,重复的代码并不理想,但是肯定有令人信服的理由来允许它,可能包括比OP和我自己所build议的更多的理由。

是的,某些代码重复是非常难以分解的,而不会使可读性显着变差。 在这种情况下,我留下一个TODO来提醒我们有一些重复的事情,但是在编写本文的时候最好还是留下来。

通常发生的事情是你在第一点上写的东西,重复是不一样的,不再重复。 还有一种情况是,重复是devise问题的一个标志,但后来才清楚。

长话短说:尽量避免重复; 如果重复是很难分解出来的,在写作无害的时候,请留下一个提醒。


另请参阅每个程序员应该知道的97个事情 :

页。 14.谨防Udi Dahan分享

系统中两个截然不同的部分以同样的方式执行了一些逻辑的事实意味着比我想象的要less。 直到我拿出共享代码的库,这些部分并不相互依赖。 每个人都可以独立演变。 每个人都可以改变其逻辑以适应系统不断变化的业务环境的需求。 这四行类似的代码是偶然的 – 一个时间exception,一个巧合。

在这种情况下,他创build了更好地保持独立的系统的两个部分之间的依赖关系。 解决scheme基本上是重复的。

让我们试着去理解DRY为什么重要,然后我们可以理解违反规则的合理位置:

应该使用DRY来避免两个代码在概念上做相同的工作,所以无论何时在一个地方更改代码,都必须在其他地方更改代码。 如果相同的逻辑在两个不同的地方,那么你必须始终记住要在两个地方改变逻辑,这很容易出错。 这可以适用于任何规模。 它可以是一个被复制的整个应用程序,也可以是一个常量值。 也可能根本没有任何重复的代码,这可能只是一个重复的原则。 你必须问:“如果我要在一个地方做出改变,我是否需要在其他地方做一个等同的改变?” 如果答案是“是”,那么代码违反了DRY。

想象一下,你的程序里有这样的一行:

 cost = price + price*0.10 // account for sales tax 

和你的程序中的其他地方,你有一个类似的路线:

 x = base_price*1.1; // account for sales tax 

如果销售税变化,你将需要改变这两条线。 这里几乎没有重复的代码,但事实是,如果你在一个地方做了改变,需要在另一个地方进行更改,这就是代码不干的原因。 更重要的是,可能很难认识到你必须在两个地方做出改变。 也许你的unit testing会抓住它,但也许不是,所以摆脱重复是很重要的。 也许你会把销售税的价值分成一个单独的常数,可以在多个地方使用:

 cost = price + price*sales_tax; x = base_price*(1.0+sales_tax); 

或者可能创build一个更抽象的函数:

 cost = costWithTax(price); x = costWithTax(base_price); 

无论哪种方式,这很可能是值得的麻烦。

或者,您可能有看起来非常相似但不违反DRY的代码:

 x = base_price * 1.1; // add 10% markup for premium service 

如果要改变计算销售税的方式,则不希望更改该行代码,因此实际上并不重复任何逻辑。

还有一些情况下,在多个地方做同样的改变是可以的。 例如,也许你有这样的代码:

 a0 = f(0); a1 = f(1); 

这个代码在几个方面不是干的。 例如,如果要更改函数f的名称,则必须更改两个地方。 你也许可以通过创build一个小的循环并把a变成一个数组来让代码更加干爽。 但是,这种特殊的重复并不是什么大不了的事情。 首先,这两个变化是非常接近的,所以在不改变另一个的情况下偶然地改变它是不太可能的。 其次,如果你使用的是编译语言,那么编译器很可能会遇到问题。 如果你不是在编译语言,那么希望你的unit testing会抓住它。

有很多很好的理由让你的代码干,但也有很多不好的理由。

工程是关于所有权衡,所以没有明确的build议或devise模式是有效的每一个问题。 有些决策比其他决策更难支持(代码重复就是其中之一),但是如果重复代码的优点超过了它的缺点,那就去做吧。

没有绝对的东西,它总是会成为两个恶魔中较小的一个的判断。 通常情况下,DRY获胜,而当你开始违规时,你必须小心滑溜,但你的推理对我来说似乎很好。

关于这个问题的一个很好的回答,请参考Thomas,Hunt的“The Pragmatic Programmer”(Dave Thomas首先提出了“Dry”这个词)

总之,没有简单的答案,保持干燥几乎总是更好,但如果它提高了可读性,那么你应该用你最好的判断,这是你的呼吁!

不,违反DRY并不总是不好的。 特别是,如果你没有拿出一个好的名字来抽象出重复的代码,也就是一个适合这两种语境的名字,那么它们可能是不同的东西,应该被重复。

根据我的经验,这种巧合虽然很less见,而且重复的代码越大,描述一个概念的可能性就越大。

我也发现从抽象到构图几乎总是一个比这更好的概念,而不是抽象inheritance ,这很容易导致你错误的方程式和LSP和ISP的违规行为。

我相信是的。 尽pipeDRY通常是理想的,但有些时候只是简单地重复自己。 我经常在开发前的testing阶段发现自己无视DRY。 你永远不知道什么时候你需要稍微改变一个function,你不想在另一个function。 当然,我总是在“完成”(即已完成的应用程序,将不需要被修改)应用程序上观察DRY,但是这些应用程序很less。 最终取决于应用程序的期货需求。 我已经做了我希望做的应用程序干,我感谢上帝,我没有在其他人观察。