PHP中的異常
PHP5引入了異常,這是一個看著簡單,實際復雜的功能,本文結合PHP的具體情況討論一二。
為什么要有異常?
在PHP4的時代,那時候還沒有異常,編碼時如果要處理一些特殊情況,一般是通過返回錯誤碼(比如類似1,2,3之類的整數)的方式來處理,類似于shell中的處理方式,隨著項目尺度的增大,這種方法帶來的問題會越來越明顯,可以通過下面演示代碼來論證這一點:
先看看錯誤碼的調用方式:
02 if ($foo == FOO_ERROR_CODE) {
03 // foo error
04 }
05 $bar = bar();
06 if ($bar == BAR_ERROR_CODE) {
07 // bar error
08 }
再看看異常的調用方式:
02 $foo = foo();
03 $bar = bar();
04 } catch (FooException $e) {
05 // foo exception
06 } catch (BarException $e) {
07 // bar exception
08 }
我們可以看到,錯誤碼缺乏描述性,而且代碼容易傾向if/else之類的拉面風格。另外,如上面代碼所示,藍色代表代碼的正常流程,紅色代表錯誤處理,可以清楚看到,采用錯誤碼的調用方式,代碼的正常流程和錯誤處理是混雜在一起的;采用異常的調用方式,代碼的正常流程和錯誤處理是分離的,所以說采用異常的調用方式后,代碼的可讀性會大大優于采用錯誤碼的調用方式。
什么時候使用異常?
程序出現非預期結果的時候就應該使用異常,這聽起來有些模糊,舉例子來說一下:
假設我們想通過主鍵查找某人,此時如果沒有找到(比如說數據庫一致性出現問題)就應該拋出異常,因為從邏輯上講,既然你通過主鍵檢索,那么這個人應該是必然存在的,如果找不到,就屬于非預期結果。
假設我們想通過名字搜索某人,此時如果沒有找到就不應該拋出異常,而應該返回空,或者類似NullPerson之類的對象,此時找不到是可以預期到的結果,既然是搜索,就可能找到,也可能找不到,所以不應該拋出異常。
仔細體會方法名字中的find何search字樣,你應該能體會到我說的意思,當然,這樣的原則多少有些完美化傾向,實際使用的時候大家自己推敲。
異常的壞味道?
PHP作為腳本語言,在使用自定義異常前必須先導入這個異常的定義文件,這多少讓人有點糾結,設想一段代碼可能會拋出十種(或者更多)類型的異常,那么甭管最終運行結果會怎么樣,每次請求前你都得預先包含這些異常的定義文件,這個問題似乎沒有太好的解決方法,不過你可以盡可能使用PHP內置的SPL異常類型,因為他們是不需要導入的,缺省就存在。
另外,在一個分層結構的程序中,異常同樣也應該是分層的,比如說程序分為表現層,應用層,持久層,那么異常也應該對應分為表現層異常,應用層異常,持久層異常。這樣區分的意義在于不會在當前層拋出一個它很難理解的異常,設想我們在表現層拋出了一個數據庫無法連接(Too many connections之類的)的異常,這是多么尷尬的一件事情。以這種情況為例,最佳情況是持久層的異常應該全部在它的上一層,也就是應用層捕捉處理,即便在應用層不能立刻處理,也應該轉換成一個表現層可以理解的異常在拋出,比如說可以拋出異常說:系統正忙,請稍后再試。順著這個方向繼續思考,那么每個層次的異常都應該有一個基類,比如說持久層應該有一個統一PersistenceException基類,所有的持久層異常都從這個基類繼承,這樣做的意義在于,當我們在應用層捕捉異常的時候,可以多一個catch(PersistenceException $e)的保險,避免有持久層異常沒有得到處理而泄漏到表現層。
異常的壞味道還遠不止上面提到的這些,有時候,try/catch本身就是壞味道!設想我們有一個Person模型,里面有一個前面說過的find_person_by_id方法,可能會拋出異常,此外,還有很多其他方法也都會拋出異常,那么當在若干Action中調用這個Person模型的時候,就會不斷的try,不斷的catch,周而復始,try/catch代碼到處都是!發現問題了么?沒錯,問題就是重復!這個問題不好解決,當然,如果你的系統架構比較好的話,還是有辦法的,比如可以通過使用裝飾器模式來規避這個問題,關于裝飾器模式的理論基礎可以參考我以前寫的Web框架審美觀,通過使用裝飾器模式,我們可以把每個模型的異常捕捉過程單獨拿出來,作為一個裝飾器存在,當有Action需要使用這個模型的時候,就可以有針對性的使用這個裝飾器。
從一個理想狀態來看,假設你使用MVC框架的話,try/catch的過程應該屬于框架的工作職責(具體行為可以通過配置來改變),你的Action代碼里不應該存在try/catch代碼,這樣既可以消除重復代碼,而且還最大限度的保證了Action代碼本身的可讀性。