最近更新: 2007-02-13

函數設計準則

我們在設計函數時,通常要考量函數之中發生錯誤時應如何處理?這一方面的知識沒有捷徑,通常需要仰賴足夠的設計經驗以為準則。概略而言可分兩部份來談:其一是錯誤回報;其二是資源回收。

由於此文是我回應 Tokimeki 的 PHP 文章之內容整理而來,故會夾雜一些 PHP 的例子。

錯誤回報

錯誤回報亦即回傳 (return) 或擲出 (throw) 錯誤狀況給調用者。回傳或擲出錯誤狀況之策略選擇,依設計經驗而有明確的使用情境。

  1. 在 setter 或 construct 中發生的錯誤一般採擲出處理。因為 setter 一般宣告無回傳值的函數,而 construct 的結果只能是指定類別之實例,所以只能將錯誤狀況擲出。
  2. 如果成功的結果是一個唯一值而失敗有許多原因時,採回傳處理。 return 0; 表示成功,其他值為失敗代號。
    See also: exit(), proc_close().
  3. 如果成功的可能結果之集合不含 FALSE 且錯誤原因只有一個或不重要時,則採回傳處理。return FALSE; 表示錯誤, 其他值表示成功。
    See also: strpos().
  4. 如果錯誤原因有多個且重要時且成功的可能結果是一個集合 (不論是否含 FALSE),則採擲出處理。因為我們不能用回傳值區別錯誤原因, throw 是唯一能夠告知調用者錯誤原因的途徑。
  5. 例外情形: 如果是一個函數成員且個體中另有一個可以表示錯誤原因的資料成員。當成功的可能結果之集合不含 FALSE ,而錯誤原因有多個且重要時,一般採回傳處理。 return FALSE; 表示失敗,失敗原因儲放在另一個資料成員中。但此法通常不保證 Multi-thread 作業安全。

函數回傳方式於 PHP manual 中的慣用說明形式。通常看到某函數的說明形式時,就可以判斷該函數的錯誤處理策略。

第一種情形: void myfunc();
try {
    myfunc();
}
catch (MyException $e) {
    echo $e;
}
第二種情形: bool myfunc(); int myfunc();
第三種情形: mixed myfunc();
if (($result = myfunc()) === FALSE) {
    echo 'Error';
}
第四種情形: mixed myfunc(); throw exception
try {
    $results = myfunc();
}
catch (MyException $e) {
    echo $e;
}

資源回收

函數式語言中規定函數不可以有副作用。在程序式語言中同樣可以如此檢視一個函數是否設計得宜。所謂沒有副作用,簡單地說是指在調用函數之前後,除了函數回傳值外,沒有其他狀態改變。通常我們會檢查函數在回傳前,是否釋放了其配置之資源。

同理,當函數之中發生錯誤時,函數也應該先釋放它先前要求配置的資源之後,才進行錯誤回報動作。如果發生錯誤後便直接回傳或擲出錯誤,而沒有先釋放函數中配置的資源,那麼程式中便會同時存在兩個以上的狀態變動,一是錯誤狀態,其他就是資源配置狀態 之變動。除增加整個錯誤處理程序的複雜度,也將為程式主體帶來副作用。

本文以我個人的設計經驗為主,而我是從 BASIC (我指的是 QuickBASIC 而非 VisualBASIC), Assembly, C/C++ 一路走過來的,大部份設計經驗是在這一段時間中定型。基本上不只適用 PHP ,也適用於其他程式語言。

樂多舊網址: http://blog.roodo.com/rocksaying/archives/2721973.html

樂多舊回應
HACGIS@gmail.com(tokimeki) (#comment-3961813)
Wed, 14 Feb 2007 01:07:53 +0800
受教了!
我那兩篇文章中,我後來發現我應該用的正確詞彙是「失敗」而非「錯誤」。

這兩天容我好好想想其中的分別,我會再寫一篇文章來闡述我的論點。