2009-06-07 90 views
6

聲明:我是一位正在學習編程的非專業人士。從未成爲項目的一部分,也沒有寫入任何超過500行的內容。防禦性編程是否違反DRY原則?

我的問題是:防禦性編程是否違反了不重複自己的原則?假設我對防禦性編程的定義是正確的(讓調用函數驗證輸入而不是相反),那麼這對您的代碼是否有害?

舉例來說,這是糟糕的:

int foo(int bar) 
{ 
    if (bar != /*condition*/) 
    { 
     //code, assert, return, etc. 
    } 
} 

int main() 
{ 
    int input = 10; 
    foo(input); //doesn't the extra logic 
    foo(input); //and potentially extra calls 
    foo(input); //work against you? 
} 

相比,這樣的:

int main() 
{ 
    if (input == /*condition*/) 
    { 
     foo(input); 
     foo(input); 
     foo(input); 
    } 
} 

同樣,作爲一個外行人,我不知道簡單的邏輯語句多少算對你就隨着表演的進行,但是防禦性編程對於節目或靈魂來說不是好事。

+0

這與C++有什麼關係? – xtofl 2009-06-07 12:40:33

+0

對不起,如果你想要刪除那個標籤,我在示例中使用了它。 – jkeys 2009-06-07 14:57:13

+0

我在15歲時自學自己的時候寫過這樣的內容。現在我可能已經不那麼流行了,但我寫了更多實質性的程序。我想兩個步驟... – jkeys 2014-10-21 22:06:01

回答

6

這一切歸結爲合同界面提供。有兩種不同的情況:輸入和輸出。

輸入 - 由此我基本上是指函數的參數 - 應該由實現作爲一般規則進行檢查。

輸出 - 作爲返回結果 - 至少在我看來,應該基本上由主叫方信任。

所有這一切都受到這個問題的衝擊:如果一方違約,會發生什麼?例如,假設你有一個接口:

class A { 
    public: 
    const char *get_stuff(); 
} 

而且合同中規定,一個空字符串將永遠不會返回(它會在最壞的情況是一個空字符串),那麼它的安全做到這一點:

A a = ... 
char buf[1000]; 
strcpy(buf, a.get_stuff()); 

爲什麼?那麼,如果你錯了,被調用者返回null,那麼程序將崩潰。那實際上是確定。如果一些客體違反了合同,那麼一般來說結果應該是災難性的。

您面臨過度防禦的風險是您編寫了大量不必要的代碼(這可能會引入更多的錯誤),或者您可能通過吞下您實際上不應該遇到的異常來掩蓋嚴重問題。

當然情況可以改變這一點。

+2

+1這一切都歸結爲界面提供的合同。防禦性程序!=屏蔽問題,防禦性程序==如果違反合同,則通過拋出異常來暴露問題。 – TimW 2009-06-07 12:11:16

4

讓我先說一味遵循一個原則是理想化和錯誤的。你需要達到你想要實現的目標(比如你的應用程序的安全性),這通常要比違反DRY要重要得多。在良好的節目製作中,故意違反原則是非常必要的。

一個例子:我在重要階段進行雙重檢查(例如LoginService - 在調用LoginService.Login之前首先驗證一次輸入,然後再次驗證),但有時候我會在確認一切工作100%,通常使用單元測試。這取決於。

雖然我從來沒有得到過雙重條件檢查。另一方面完全忘記他們通常是多重幅度更糟:) :)

1

在你的簡單例子中,是的,第二種格式可能是可取的。

然而,這並沒有真正應用到更大,更復雜,也更現實的方案。

因爲你永遠不知道提前哪裏或如何「foo」將被使用,則需要通過驗證輸入保護FOO。如果輸入由調用者驗證(例如,在您的示例中爲「main」),那麼「main」需要知道驗證規則並應用它們。

在現實世界的編程中,輸入驗證規則可能相當複雜。讓呼叫者知道所有驗證規則並正確應用它是不恰當的。有些調用者在某處會忘記驗證規則,或者做錯誤的規則。所以最好將驗證放在「foo」中,即使它會被重複調用。這將負擔從調用者轉移到被調用者,這使得調用者可以更少地思考「foo」的細節,並將其更多地用作抽象的,可靠的界面。

如果你真的有一個,其中「富」會得到相同的輸入多次調用模式,我建議一個包裝函數,它驗證一次,未受保護的版本,側步驗證:

void RepeatFoo(int bar, int repeatCount) 
{ 
    /* Validate bar */ 
    if (bar != /*condition*/) 
    { 
     //code, assert, return, etc. 
    } 

    for(int i=0; i<repeatCount; ++i) 
    { 
     UnprotectedFoo(bar); 
    } 
} 

void UnprotectedFoo(int bar) 
{ 
    /* Note: no validation */ 

    /* do something with bar */ 
} 

void Foo(int bar) 
{ 
    /* Validate bar */ 
    /* either do the work, or call UnprotectedFoo */ 
} 
+1

更好地公開BarIsValid()並讓調用者編寫自己的循環。然後你有一個最小的API:BarIsValid()和Foo(),並且當用戶想要一個更復雜的循環時不需要擴展API。 PS:我認爲RepeatBar不應該驗證當repeatCount爲0時的酒吧。 – 2009-06-07 07:12:34

9

違反了DRY原則看起來像這樣:

int foo(int bar) 
{ 
    if (bar != /*condition*/) 
    { 
     //code, assert, return, etc. 
    } 
} 

int main() 
{ 
    int input = 10; 
    if (input == /*condition*/) 
    { 
     foo(input); 
     foo(input); 
     foo(input); 
    } 
} 

,你可以看到,問題是,我們有相同的兩次檢查中的程序,所以如果條件的變化,我們必須在兩個地方進行修改,而且我們很可能會忘記其中的一個,造成奇怪行爲。 DRY並不意味着「不執行相同的代碼兩次」,但「不寫相同的代碼兩次」

1

像亞歷克斯說,這取決於不同的情況,例如,我幾乎總是驗證輸入的登錄過程的每個階段。

在其他地方,你並不需要那麼多。

但是,你給了,我假設,在第二個例子,假設你有一個以上的輸入,「導致否則這將是多餘的調用同一個功能3次同樣的輸入,這意味着該示例你必須寫出這個條件3次。現在這是多餘的。

如果輸入的總是要來檢查只是包括它的功能。

3

我認爲防禦性編程得到怎樣的一個壞名聲,因爲它做一些事情,是一種不可取的,包括羅嗦的代碼,更顯著,掩蓋了錯誤。

大多數人似乎都認爲程序在遇到錯誤時應該快速失敗,但關鍵任務系統最好永遠不會失敗,而是要竭盡全力繼續面對錯誤狀態。

有這種說法有問題,當然了,如何能計劃,甚至是關鍵任務,繼續當它處於不一致的狀態。當然它不能,真的。

你想要的是程序採取一切合理步驟,做正確的事,即使有事情有點怪。與此同時,程序應該大聲抱怨,每次遇到這樣一個奇怪的狀態。如果遇到無法恢復的錯誤,通常應避免發佈HLT指令,而應該優雅地失敗,安全地關閉其系統,或者在有可用的情況下激活某個備份系統。

相關問題