2013-07-22 66 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違反更好的主意。