2013-07-22 64 views
42

我一直在讨论关于DRY不要重复自己)原则也被称为DIE复制是邪恶),并有票,任何简单的代码的重复始终是一个邪恶的。我想听听您对以下几点的看法:违反DRY原则总是不好?

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

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

+3

这可能更适合程序员.se –

回答

29

这些是违反DRY的完全正当理由。我应该补充一点:表演。这几乎不是什么大不了的事情,但它可以有所作为,抽象可能会降低速度。

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

通常情况下,重复的代码并不理想,但确实有令人信服的理由允许它,可能包括比OP和我自己建议的更多的原因。

+2

是“绝对!”对问题标题或问题内容的最后一行的回应?你增加了一些需要考虑的事情,但是这并不能完全说明你有哪些立场。你的最后一句是“有时候!”,而不是“绝对!”。 – Izkata

+0

我改变了第一行的措辞来清除那个。我的意图是说可能有很好的理由来违反DRY。 – patrickvacek

+0

是的,现在看起来我更加清楚了(+1) – Izkata

3

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

2

对于这个问题,请托马斯,亨特请参阅“程序员修炼”良好的反应

在短(这是戴夫·托马斯谁在首位的术语“干”出来了),没有简单的答案,保持干燥几乎总是更好,但如果它提高了可读性,那么你应该使用你的最佳判断,这是你的呼叫!

11

工程是关于折衷的,所以没有明确的建议或设计模式对每个问题都有效。有些决策比其他决策更难支持(代码重复就是其中之一),但是如果重复代码的优点超过其在您的情况下的缺点,那么就去做吧。

+0

没错。像“DRY”或“YAGNI”这样的启发式方法,在死记硬背的情况下,不理解其背后的推理,只不过是将它们作为“正义准则”而忽略掉。 –

1

我相信是的。虽然作为一般规则DRY是理想的,但有些时候简单地重复自己会更好。我经常在开发前测试阶段发现自己忽视DRY。你永远不知道什么时候你需要对一个函数做些微的修改,而你不想在另一个中修改。我当然尝试总是观察干完成“完成”(已完成的应用程序,将不会需要修改)的应用程序,但这些应用程序很少和很远。最终它取决于应用程序期货的需求。我已经完成了我希望的应用程序是干的,我已经感谢上帝,我没有在其他人身上观察它。

16

是的,某些代码重复是非常难以分解出来的,而不会使可读性显着变差。在这种情况下,我留下一个TODO作为提醒,说明有一些重复,但在撰写本文时,似乎更好。

通常会发生什么是您在第一点中写的内容,重复出现分歧,不再重复。还有一种情况是,重复是设计问题的一个标志,但稍后才会明确。

长话短说:尽量避免重复;如果重复是非常难以分解的,并且在写作无害时,请留下评论以提醒。


97 Things Every Programmer Should Know参见:

页。 14.通过乌迪大汉

,该系统的两个完全不同的部分以相同的方式执行的一些逻辑 的事实意味着小于我想当心分享。直到我拿出共享代码的这些库,这些部分并不相互依赖。每个 都可以独立演变。每个人都可以改变其逻辑以适应系统不断变化的业务环境的需求。这四行类似的代码是偶然的 - 一个时间异常,一个巧合, 。

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

+0

每个程序员都应该知道的97件事是一本很棒的书= D – Kevin

13

让我们试着去理解为什么DRY是很重要的,然后我们就可以明白的地方打破规则是合理的:

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

试想一下,你有这样的线在你的程序:

cost = price + price*0.10 // account for sales tax 
在你的程序

和其他地方,你有类似的一行:

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

如果销售税的变化,你将需要改变这两条线。这里几乎没有重复代码,但事实是,如果您在一个地方进行更改,则需要在另一个地方进行更改,这会导致代码不干。更重要的是,你可能很难意识到你必须在两个地方做出改变。也许你的单元测试会抓住它,但也许不是,所以摆脱重复是很重要的。也许你会因素的营业税变成一个独立的恒定值,它可以在多个地方使用:

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

或可能创建一个函数来抽象甚至更多:

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的名称,则必须更改两个位置。你也许可以通过创建一个小循环并将a变成一个数组来让代码更干。但是,这种特殊的重复并不是什么大问题。首先,这两个变化非常接近,所以在不改变另一个的情况下意外改变它是不太可能的。其次,如果你使用的是编译语言,那么编译器很可能会发现问题。如果你不是编译语言,那么希望你的单元测试能够抓住它。

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

+5

+1。也许更重要的是,你可能会在你的代码中有一行,比如'x = base_price * 1。1; //为优质服务添加10%的标记。将这些代码与销售税计算结合起来应该是一个_bad_想法,即使它们目前正在做同样的事情。 –

+0

+1不能说更好:) – BrianHall

+0

@IlmariKaronen:是的,绝对。如果我在答案中包含完整性,请介意吗? –

2

不,违反DRY并不总是不好。特别是,如果你没有拿出一个好的名字来抽象出重复的代码,即一个适合这两种情况的名字,可能它们毕竟是不同的东西,应该重复。

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

我也觉得抽象到组成几乎总是在这方面比抽象继承这很容易导致你错误的公式和LSPISP违反更好的主意。