2010/11/27

[Software]約耳與雷蒙討厭exception handling(例外處理)?

約耳續談軟體中,約耳認為exception handling只是另一個goto,它隱瞞了發生錯誤的可能,無法直接看出發生錯誤時的處理路徑。我認同這是為了程式碼的簡潔,不要因為插入太多錯誤處理碼,把錯誤處理碼集中放到catch而不可避免的後果。

在演算法與程式的開發過程中,如果寫下每一行時都要考慮這個函式可能傳回的error,不僅讓撰寫變慢,而且本來流暢的思緒被卡住。如果寫程式時,先只關注預期的程式路徑,把catch放在程式最外圈。經過不斷的測試之後,再把錯誤頻繁發生的區段用try包,這樣開發才快。我這想法比較像test-driven,也許有人會認為「測試引導開發」會埋下更大的錯誤,到了很晚才爆發。但我認為很多事情你自己不先快速走一遭,你永遠無法知道會發生什麼。你在寫程式前預期會發生的錯誤沒有發生,反倒是「不可能」、「沒想到」的錯誤發生。如果你這個第一次快速走一遭的「預習」太晚完成,將會拖累整個開發的進度。


Raymond Chen的Blog: Old New Things中也說Exception Handling是Cleaner, more elegant, and wrong。但他舉的例子不好。在這例子就算是逐行用if-goto來檢查,寫的不好一樣有問題。C++ exception最大的問題是,許多人誤以為只要最後有用catch接到例外,try區塊中曾經配置過的memory、resource都會自動釋放,所有的狀態都會回復到try發生之前。

這是錯誤的!如果沒有達到David Abrahams所稱的Exception Guarantees,不管你是用if亦或exception來寫,都會有問題。狀態無法rollback成try之前,resource沒被釋放。

想像有一個提款機轉帳的函式,先從sender帳戶扣款,再給recipient帳戶加上款項。

void TransferMoney(Person& sender, Person& recipient, int money){
SubtractAccount(sender, money);
AddAccount(recipient, money);
}


如果SubtractAccount()執行成功,但要執行下一行時,連線出問題,那sender就虧大了,白白損失一筆錢。如果說你討厭exception,你用if來寫程式:


HRESULT TransferMoney(Person& sender, Person& recipient, int money){
HRESULT hr=S_OK;
if((hr=SubtractAccount(sender, money)) != S_OK)
return hr;
if((hr=AddAccount(recipient, money))!=S_OK)
return hr;
return hr;
}

這個程式執行一半,出了錯誤,直接return error,雖然從程式碼中可直接看出錯誤處理的路徑,但並不符合Abrahams' Guarantees!所有帳款資料庫的狀態沒有回復到函式執行之前。這個問題只要C++語法中沒有提出transactional programming解決方案前,不管是用if還是exception,錯誤處理都是要逐行細心考慮。