話說我個人對函數的設計比類別的設計來的有興趣的多,函數的設計有許多地方是有準則可以遵循的,我希望在我的程式設計生涯中,能找出這些準則。
這篇主要在探討在函數中,執行「中斷」的一些設計準則。
所謂的「中斷」,是一種條件,這種條件使函數執行失敗時跳脫程式既定的「循序流程」,亦可能引發錯誤而使程式停止。
補述:我個人其實並不喜歡「例外處理」,我覺得在使用「例外處理」之前,設計者應盡到責任,將可「自理」的部份做好處理。濫用「例外處理」的結果,就會把「所有的責任」推給作業系統來收爛攤子。
讓我們考慮這一類型的函數:
先舉個例子來看看:
function 載入:模組($模組, $中斷 = true){
$前綴 = (PHP_SHLIB_SUFFIX == 'dll') ? 'php_' : '';
if(!extension_loaded($模組) && !@dl($前綴.$模組.'.'.PHP_SHLIB_SUFFIX)){
if($中斷){
$訊息 = '無法載入 '.$模組.' 模組';
trigger_error($訊息, E_USER_ERROR);
// exit($訊息);
}else{
return false;
}
}else{
return true;
}
}
這是一個載入 PHP Extension 用的函數,在實務上,當我們的 PHP 程式在執行時,有時必須載入某些模組,當這些模組無法載入時,則必須強制停止程式的執行。
先不論這裡的錯誤處理方式是否優雅,我們關注的是,中斷行為是一個(可控)條件,當執行失敗時,我們可以選擇是中斷。
這篇主要在探討在函數中,執行「中斷」的一些設計準則。
所謂的「中斷」,是一種條件,這種條件使函數執行失敗時跳脫程式既定的「循序流程」,亦可能引發錯誤而使程式停止。
補述:我個人其實並不喜歡「例外處理」,我覺得在使用「例外處理」之前,設計者應盡到責任,將可「自理」的部份做好處理。濫用「例外處理」的結果,就會把「所有的責任」推給作業系統來收爛攤子。
讓我們考慮這一類型的函數:
- 這類函數進行一些單純的動作,執行結果只有成功或是失敗兩種結果。
- 呼叫該函數時,我們對其成功或失敗的原因不感興趣,我們只希望知道其執行結果到底是成功或是失敗。
- 在其執行失敗時,有時會需要將程式流程中斷下來,我們需要一個條件去指示是否需要進行中斷。
先舉個例子來看看:
function 載入:模組($模組, $中斷 = true){
$前綴 = (PHP_SHLIB_SUFFIX == 'dll') ? 'php_' : '';
if(!extension_loaded($模組) && !@dl($前綴.$模組.'.'.PHP_SHLIB_SUFFIX)){
if($中斷){
$訊息 = '無法載入 '.$模組.' 模組';
trigger_error($訊息, E_USER_ERROR);
// exit($訊息);
}else{
return false;
}
}else{
return true;
}
}
這是一個載入 PHP Extension 用的函數,在實務上,當我們的 PHP 程式在執行時,有時必須載入某些模組,當這些模組無法載入時,則必須強制停止程式的執行。
先不論這裡的錯誤處理方式是否優雅,我們關注的是,中斷行為是一個(可控)條件,當執行失敗時,我們可以選擇是中斷。












Recommend to Front page



撇開函式或類別,我個人到目前為止所學到的設計原則是:錯誤能往上丟就往上丟,函式或方法裡要儘可能保持簡潔的中斷,並讓呼叫它的程式能夠處理。也就是應該讓函式保有更大的彈性空間,而不會因為外部需求的改變而必須經常修改。
個人淺見,參考看看。
1.在 __set() 或 __construct() 中發生的錯誤一定用 throw 。因為 __set(), __construct() 都是宣告為無回傳值的函數,所以只能 throw 。
2.如果成功的結果是一個唯一值,而失敗有許多原因時,我們用 return 。 return 0 表示成功,其他值為失敗代號。
See also: exit(), proc_close().
3.如果成功的結果是一個不含 FALSE 的集合,且錯誤原因只有一個或不重要時,則我們用 return 。return FALSE 表示錯誤, 其他值表示成功。
See also: strpos().
4.如果成功的結果是一個集合 (不論是否含 FALSE),而錯誤原因有多個且重要時,我們用 throw 。因為我們不能用回傳值區別錯誤原因, throw 是唯一能夠告知調用者錯誤原因的途徑。
4-例外情形: 如果是一個函數成員,個體中另有一個可以表示錯誤原因的資料成員。如果成功的結果是一個不含 FALSE 的集合,而錯誤原因有多個且重要時,我們用 return 。 return FALSE 表示失敗,失敗原因儲放在另一個資料成員中。但此法通常不保證 multi-thread 作業安全。
在 PHP 中的說明方式:
第一種情形: void myfunc();
第二種情形: bool myfunc(), int myfunc();
第三種情形: mixed myfunc();
第四種情形: mixed myfunc(); throw exception
「中斷」的含意我解釋得很清楚,他是「流程中斷」的意思。
事實上,一個函數的執行失敗責任,在可行的範圍內,我認為該函數需要有「能力」去自理,而非把責任往上拋。
如果所有的函數都這麼作,那最後的責任就由作業系統承接,結果就是出現一個訊息框,告訴你發生了什麼例外,然後結束程式。
如果結局是這樣,那為何不讓函數自我了斷,這樣比較還比較直接一點,當然這個例子是比較極端一些,其實大多數時候我們會在「適當」的地方來作處理。
例外處理適用於該函數無法「自理」的時候,中斷是適用於該函數有「能力」處理,而給予調用該函數的一個選擇性條件。
如何確定這裡的「中斷」是必須的?我認為判斷的責任不在函數本身,而在調用該函數的那頭。
在你的例子中用了 trigger_error() (在 PHP5 中以 throw 取代) ,把錯誤拋掉 error_handler 。然而我們極少在函數中自己設定 error_handler (如果自己就能 handler 何必觸發錯誤?) 因此事實上就是「把責任往上拋」。
話又說回來,我注意到你、我所說的使用情境不同。這麼說吧,調用者也可以是一個函數,所以當一個函數A 調用另一個函數B 而函數B 回傳錯誤時,身為調用者的函數A 就要「自理」了,對吧?
若你、我是同一個 team 的 programmer ,在此例中,你是負責設計函數A 的人,而我就是負責設計函數B 的人。
那麼「調用者」(函數A) 的設計準則跟「被調用者」(函數B) 的設計準則為何不同?我想用 MVC 架構來說明會簡單些。在 Controler 中的函數都是「調用者」,而在 Model 中的函數都是「被調用者」。從 MVC 的設計架構一想,這兩者的設計準則就很容易區分了。
在這裡,原本的設計是用exit來強制中斷程式,後來為何為改成trigger_error呢?
這是因為為了讓xdebug可以抓到錯誤的緣故。
事實上,我並沒有設置任何的error_handler,而沒設置的情況去觸發error,結果跟中斷程式是同樣的效果,也就是說,如果我沒有加掛xdebug,那麼這兩個行為幾乎是等價的。
再來談函數責任問題,中斷的設計是給調用者一個選擇,決定是否要「射後不理」。
如果沒有中斷的設計,那麼我只能選擇把責任往上拋,或是很粗魯的強制停止程式,無論是哪一個,我都必須作一些檢查:在調用函數之前先作一些檢查來避免中斷的產生或是檢查傳回值去決定是否要作進一步的錯誤處理。
當然,對於調用函數這頭,大多數情況還是要處理的,但這並不是個推諉卸責的好藉口。
我只是提出,當我們的函數具備哪些特性時,可以這樣設計,賦予其可中斷的特性,使得其分擔調用函數這頭的責任。
而判斷是否要另函數進行這樣的責任分擔,決定權在調用函數這頭。
MVC探討的東西比較多,我不想去碰那種大題目,我只會去觸碰一些容易討論出結果、可以令人覺得說:這是有一致性的作法,類似這樣的東西這麼作不錯。這樣的東西~
Comment Permissions: Allow commenting