Code Complete: Defensive Programming Note – 2

Code Complete: Defensive Programming Note – 2

Exception 是撰寫程式時很重要的一個環節,所有預期會發生的錯誤都由它來處理,此節講述書中所介紹需要注意的細節。

前言

Chapter: 8.3 Error-Handling Techniques
Chapter: 8.4 Exceptions

這份筆記是在閱讀 Code Complete 時,閱讀時認為重要的重點節錄,以及我的心得筆記,文中範例皆來自 Code Complete 書中。

如果您在閱讀時發現有錯誤的地方,請來信告知我,謝謝!

關於例外處理的那些細節

如前一篇文章所說,斷言是用來處理不該出現在程式碼的錯誤(通常發生於程式的 bug),而例外便是用來處理可能會發生的錯誤(使用者輸入)。

那在發生例外時,應該要怎麼處理呢?

Return a neutral value

有時候對於垃圾資料 (bad data) 的處置方式最好的方式,是以一個可接受的值取而代之。舉個例子,當你發現車子的行進速度為負值,那麼可以將之取代為 0。

Substitute the next piece of valid data

以下次的有效資料作為代替,在很多情況之下值是連續變動的。例如每秒抓取一次溫度並將之轉換為另外一種度量單位,這次抓取到的為低於絕對零度的值(不可能存在),因此便不處理,等待下次的資料再回傳。

Return the same answer as the previous time

這一點與上一點十分相似,不過上一點提到的是以下一次的資料作為替代輸入,這次則是將上次的處理結果直接回傳。

Substitute the closest legal value

在有些情況之下,你可以決定回傳接近的合法數值。例如一個範圍只在 0 ~ 100 的溫度計中,發現溫度低於 0 則回傳 0。

Log a warning message to a file

不過你當然也可以把這些 bad data 都記錄下來囉,這個方法也能與上述之一做結合。

Return an error code

在設計系統時,也能夠設計成只有某部分的高階模組能處理錯誤,而其他模組沒辦法處理錯誤,只會拋出錯誤、回傳錯誤代碼 … 等,讓位於更高階、權限更高的模組來處理這些錯誤。

傳遞錯誤的方法有:
* 設定某一個儲存狀態的變數值
* 把錯誤代碼包在回傳值當中
* 例如常見的:{'status': 'error', 'msg': 'Input value invalid'}
* 利用使用的語言內建的例外機制來處理

Call an error-processing routine / object

另一個方法是將錯誤處理集中在某個錯誤處理模組,這個會讓除錯的時候更方便。

Shutdown

有些系統在發生錯誤的時候最好的方式就是自己關機,這種方式通常都被用在有高度安全疑慮的系統。例如有一套系統是用來控制治療癌症的輻射線,那在發生錯誤時最好的方式便是把系統暫時關閉。

Robustness vs. Correctness

接下來讓我們探討功能強大 & 運作正確之間應該要如何去做取捨,這當然還是根據你的系統取向而定。

Correctness (正確性) 代表著總是要回傳精確的值,不能有絲毫的偏差。

而 Robustness (功能性) 代表著總是需要有些結果回來,例如在使用者操作時畫面破圖了,可能按一下 PageDown 再 PageUp 就恢復了,此時便不能因為偵測到錯誤而把系統給 Shutdown。

撰寫例外處理時的重點

如果例外處理用的好,可以減少複雜度;用不好,則會讓程式碼難追蹤難讀懂。

Use exceptions to notify other parts of the program about errors that should not be ignored

利用 exception 來通知其他程式這個錯誤必須被處理不能忽略掉,就像標題所說的一樣。

Throw an exception only for conditions that arre truly exceptional

簡單來說,例外就應該真的被用在是 “例外” 的情況,不應該被用在該使用 assertion 的那個時候。

Don’t use an exception to pass the buck

別把例外丟來丟去,如果能在這段程式碼處理掉就處理掉,不應該 return 一個 exception 給呼叫的程式碼處理。

Avoid throwing exceptions in constructors and destructor unless you catch them in the same place

與上一點所提的相似,這點再次強調不要在建構子與解構子中拋出例外。

Throw exceptions at the right level of abstraction

舉個例子,現在有一個 function:

這邊拋出的是 EOFException。

而另一個例子:

這才是正確的寫法,因為兩個的層級不一樣,在撰寫的時候應該要注意拋出的例外層級與抽象化層級相符。

Include in the exception message all information that led to the exception

例外的錯誤訊息記得要足夠明確,必須讓別人能夠理解為什麼會出現這個問題。例如在一個陣列的 Out Of Range 例外,就應該要把變數、陣列的大小、存取的 Index 寫出來。

Avoid empty catch blocks

在例外處理的時候,不要把 catch block 留白,像這樣子:

可能這個是很少見的例外,但就算是這樣,用 Logger 來寫下紀錄也會比留白還要好:

Know the exceptions your library code throws

假如你使用一些不是自己寫的函式庫,必須確定你自己瞭解使用這些 lib 會拋出甚麼例外,否則沒有做例外處理會導致你的程式崩潰。

Consider building a centralized exception reporter

像是 Python,會有 logging 這種套件可以 import,然後用於記錄錯誤訊息,你可以考慮來做一個類似 “訊息處理模組” 的東西,將所有 Log 都記錄下來,以不同的文字或顏色來標示 ErrorWarningINFODEBUG 等不同等級的訊息。

Leave a Reply

Your email address will not be published. Required fields are marked *