2009年12月3日

[程式設計] 建築防禦工事

好的programmer寫作的程式碼,必定是條理分明、敘述清晰、邏輯清楚。

一份好的程式碼,本身就已經清楚說明目的和用法。

兩個都懂某程式語言的人,就像兩個都懂英文的人,能夠透過語言本身清楚地溝通。然而,我們若講或寫出一段別人聽不懂或看不懂英文時,我們會清楚知道是自己的問題。但許多寫程式的人卻因為寫出別人看不懂的程式碼而沾沾自喜,真是一件怪事!?

當然,溝通的基礎是建立在雙方都懂這個語言,並且知道慣用的標準為何。

多數的programmer都知道,debug很花時間,許多專案都浪費大把的時間在debug,好不容易找到問題,修改好了,卻又因為忽略某些情況再次出問題,修修補補的程式碼既難看又造成更多問題。

防禦工事就是在撰寫程式碼的當下,就盡可能一次把事情做到定位,因為只有當下最清楚哪些問題可能發生,若因為拿時間有限當藉口而偷懶,省下撰寫預防用途的程式碼,日後將會付出更大的代價,也就是無謂地增加開發成本,更可能造成專案因此延宕。

舉例來說,一個小程式接受使用者輸入兩個數字,並且相除。

這個程式很快就能寫好交差。

a = get_user_input();
b = get_user_input();

print a/b;

假設這個程式碼可以運作,但那一定只有在限定的狀況下。

也就是使用者輸入的第二個數字不得為零。

我們可以偷懶而將問題視而不見,反正老闆/客戶可能永遠不會發現,就算到時候被發現了,再回頭修改不就好了,這世界上哪有不會出錯的程式呢?看看微軟的BSOD,不就是經典中的經典嘛。

但想想看,當程式碼變成幾千行、幾萬行,甚至幾十萬行的時候,這個小錯誤要找出來會花掉多少時間人力阿。

在撰寫程式的當下做好防禦工事,其實很容易。

只要加上些假設條件的判斷。

a = get_user_input();
b = get_user_input();

if (b <> 0) {
print a/b;
}
else {
print "error: devide by zero";
}

當然還可以再想得更周到,讓使用者可以重新輸入。

改寫如下。

a = get_user_input();

b = get_user_input();
while (b == 0) {
print "warning: input must be non-zero value";
b = get_user_input();
}

assert(b <> 0);

print a/b;

因為使用者輸入0的時候,程式會要求重新輸入,所以我們在印出a/b的時候省略一次if判斷。但是為保險起見,加入assert的語法,以免前面的程式碼在某次的修改之後失靈,又再度發生錯誤。

assert再不同的程式語言的編譯器會有不同的實作,這個機制讓我們可以撰寫一些限制條件的驗證,在development階段可以方便debug發現問題並及早修正;而在production的階段,某些程式語言的編譯器允許選擇性地除掉這些驗證,以減少不必要的效能支出。

哪些情況的防禦可以用assert呢?

一個簡單的判別就是,當一個限制條件的判斷,在程式正式發行時就可以忽略不管,那麼就可以考慮用assert,否則要用if-else或其他方式作永久性地檢查。

* 了解一個語法適用的時機及意義,不要濫用及錯用。
* 避免使用非語言標準的語法、函式庫。
* 盡可能考慮周詳並在程式碼清楚表達出來。
* 寫出其他人能夠直接看懂、不必猜測的程式碼。
* 不要讓使用者有機會發生可以預知的錯誤,雖然錯誤的發生可能是在很愚蠢的狀況下,但現實就是真的有人會那麼蠢。

在撰寫程式碼的同時,需要多思考這段程式碼的防禦工事是否足夠,那些情況下會被攻破,在當下就盡可能做好準備工作。寫出日後更容易維護、更少出現問題的程式碼,才是合格programmer應具備的能力。


參考文獻
- Code Craft: The Practice of Writing Excellent Code

沒有留言:

張貼留言

lyhcode by lyhcode
歡迎轉載,請務必註明出處!