異常是所有開發(fā)者在寫代碼過程中都會遇到并且要妥善處理的,不少開發(fā)者認(rèn)為php無需做異常處理,框架本身已經(jīng)幫我們做好了,異常處理是JAVA,C語言才要做的事情,其實這種說法是很片面的,雖然PHP的框架比如Laravel,Yii等有非常好的異常處理,但是他們只是提供了一種工具,框架不會去理解業(yè)務(wù),而在業(yè)務(wù)邏輯中,難免會遇到各種異常處理,我們要提升對異常的認(rèn)知,做到在工作中靈活運用 ”
接下來在異常處理這篇文章中,我將從頭梳理PHP開發(fā)中 Exception 這個關(guān)鍵字的周邊知識,爭取梳理清楚如下的幾個問題
1.什么是異常
2.異常從哪里來
3.異常應(yīng)該到哪里去
4.異常之分門別類
5.異常和錯誤的區(qū)別和聯(lián)系
6.異常之于業(yè)務(wù)場景
什么是異常
在我們開始所有解釋之前,我么首先舉個栗子. 假設(shè)你要按照給定id來查找humans表的用戶郵箱,這個功能可以寫成這樣:
但是這絕對不是一個健壯的代碼,一旦用戶傳遞一個不存在的id(數(shù)據(jù)庫不存在),或者傳遞是壓根就不是int型的正整數(shù),程序就會進(jìn)入: PHP Notice 狀態(tài),導(dǎo)致整個程序進(jìn)入非預(yù)期流程
這里的非預(yù)期流程可能是個PHP層的警告,也可能是個fatal error(假設(shè)方法的實現(xiàn)還有更加致命的邏輯假設(shè)),導(dǎo)致程序異常終止
所以這時候需要這個方法告訴調(diào)用方你的參數(shù)有誤這種信息,這個時候通常我們的做法有兩種.
1.
2.
而第二種做法就是我們討論的Exception,好了,到這里我們只是討論清楚了拋出異常的必要性,但是就如上面兩個例子看到的,調(diào)用方調(diào)用完畢這個方法怎么按照你給的信號(throw 出來的exception)做出合理的處理呢?
異常從哪里來?
上面說清楚了異常的必要性以及可能性,那么異常從哪里來呢?
異常來自開發(fā)者對整體代碼邏輯的非預(yù)期結(jié)果給出的提示. 所以簡單來說,異常是PHP代碼層拋出的. 一般常見的當(dāng)我們調(diào)用第三方接口,如果不做異常處理,接口異常,或者超時,都會產(chǎn)生致命性錯誤。
異常應(yīng)該到哪里去
上面已經(jīng)定義了function getHumanEmailById,同時對參數(shù)的非法性做了異常的拋出,但是我們不知道異常到哪里去了,我們嘗試調(diào)用它getHumanEmailById(1),這里假設(shè)id是1的human信息在數(shù)據(jù)庫中已經(jīng)被刪除,看看PHP執(zhí)行器返回結(jié)果:
Fatal error: Uncaught InvalidArgumentException: 參數(shù)非法 這里發(fā)現(xiàn),我們拋出的異常居然變成了Fatal error,所以異常拋出后交給了PHP解釋器,解釋器沒有找到 catch 這個異常的邏輯代碼,所以直接fatal error,說明這一點
我們繼續(xù)修改代碼
執(zhí)行結(jié)果正常輸出: 參數(shù)非法 在Laravel中,內(nèi)置了非常多的異常處理類,如果我們善于觀察,可能會經(jīng)常遇到 諸如router 404, 405Method Not Allowed 429 to many request ,model 404 這樣的laravel 已經(jīng)幫我們處理的異常處理,當(dāng)然還是開頭那句話,我們業(yè)務(wù)的異常,框架是不會幫我們處理的 到此,我們解釋清楚了異常應(yīng)該到哪里去的問題
異常之分門別類
也許你留意到上面的代碼沒有catch InvalidArgumentException,是因為Exception是InvalidArgumentException的父類,因為異常的類型很多,我們有一些需求確實需要根據(jù)不同的異常做不同的事情,例如下面的偽代碼:
異常和錯誤的區(qū)別和聯(lián)系
這個主題,我想只有PHP官方PHP5時代的開發(fā)才能解釋清楚這一點,通俗的來說,錯誤可能是不可修復(fù)的,當(dāng)然現(xiàn)在的PHP7版本已經(jīng)將error和exception統(tǒng)一了,他們都集成自throwable這個interface.具體的關(guān)系如圖
PHP7以下的話,我們可以嘗試將error轉(zhuǎn)為exception,具體實現(xiàn)代碼:
異常之于業(yè)務(wù)場景
特定一類throwable統(tǒng)一輸出json 最后,我們回歸到上面最初的代碼,如果human的id是非法的,就拋出了異常,假設(shè)這個id恰好是業(yè)務(wù)前端傳遞的,我們就需要告訴用戶這個id是非法的,明確告訴他非法請求. 實現(xiàn)的邏輯代碼大致為:
如果這中參數(shù)對應(yīng)的msg想統(tǒng)一起來,且前端提交的參數(shù)非常多,都需要這樣判斷呢? 這里我們可以抽象出有一個類,繼承自InvalidArgumentException的類ApiInvalidArgumentException,然后統(tǒng)一在上層捕獲這個異常,然后統(tǒng)一輸出json格式 這里的最佳實踐可以在laravel中看到影子,大致思路如下:
1.繼承IlluminateFoundationExceptionsHandler::render($request, Exception $e)
2.ApiInvalidArgumentException類定義toJson方法
3.拿到$e,特判ApiInvalidArgumentException,return出tojson
如此對業(yè)務(wù)代碼無侵入,看起來干凈且明了
將沒有catch的異常介入第三方統(tǒng)計 線上在所難免的會有一些異常是未捕獲的,這時PHP會將信息直接fatal error,并輸出堆棧信息,所以我們可以將這些信息介入第三方,實時發(fā)消息給開發(fā)者,解決和發(fā)現(xiàn)線上問題. 推薦大家使用sentry作為PHP異常處理和發(fā)現(xiàn)的工具,很強(qiáng)大,目前我們團(tuán)隊線上就在大量使用.
因為我們的業(yè)務(wù)需要判斷和處理太多不符合預(yù)期的結(jié)果了,有時候一個魯棒性強(qiáng)的代碼可能是核心業(yè)務(wù)代碼的2-3倍,這個時候如果我們能夠很好的利用exception,既能讓代碼健壯,又能讓健壯性判斷可讀性高,例如我們可以二次封裝if判斷,轉(zhuǎn)變?yōu)閍ssert斷言,然后在斷言中拋出異常.現(xiàn)在很多第三方類庫,都很好的定義了自己的異常,為的就是健壯和可讀性,希望通過這篇文章,大家能夠收獲一些新的心得體會,也希望你能斧正文章的錯誤。






