PHP Exception 異常處理和 exit/die

來源:互聯網
上載者:User
一直很難理解異常處理,比如我的程式底層使用了 mysql 資料庫連接,而且我的上層所有程式都建立在此基礎上(不考慮緩衝等其他),比如一個頁面要取出當前 url 中 id 指定的 post 內容,當調用底層資料庫連接時,結果 mysql_connect 無法串連,那建立在此基礎上的應用也再沒有執行的必要了,我的 mysql_connect 處不應該直接 exit/die 終止程式嗎?即使說要友好的錯誤提示,那我可以自訂一個函數比如 MyError($code),在此函數中來美化我的輸出再在裡面決定要不要 exit/die 啊。

如果使用異常處理,那在我覺得所有可能出現不正確的地方,我都必須加上 try/catch 了?那我的一段程式下來豈不是一堆 try/catch ... 這和我直接用 die/exit 或者調用自訂 MyError() 有何分別?如果說 Exception 可以往上傳遞,但我很多時候就應該當即處理啊,就比如資料庫連不上,或者我 include 系統配置的時候檔案不存在,這個時候難道不應該當即做出處理嗎?

比如我做了單入口,Router -> Controllers -> Services/Models -> ModelBase -> DbFactory -> MySQL -> Driver,是不是我在 Router 外面加個 try/catch 就可以了,裡面全都 throw?比如我的 MySQL 中 mysql_connect 出現問題了,我的 Controller 裡面在 get 取 id 時,url 中的 id 在資料庫中不存在,那這兩處我都要 try/catch/throw,然後在 Router 外面的 catch 中再處理嗎?

實在是很混亂,求解答,手冊都是教怎麼用,沒教應用情境,還有為什麼要用?(那個為什麼真心看不出為什麼),求指點!能指出具體的應用情境最好了,謝謝~

2015-06-19

很感謝各位的詳細回答,謝謝!還有一些問題想麻煩各位,寫到評論裡不太好,就拿來寫到這裡了。

比如設定檔丟失這個問題,處理的方法:

1、最原始的直接 require,這樣設定檔不存在就直接報錯了(報錯開啟),這樣顯然不合適,不友好而且會暴露實體路徑。

2、我已經預先意識到了 require 可能會出現問題,所以決定在 require 之前先 is_file/file_exists 判斷檔案是否存在,那麼:

$file = '/a/b/c.php';is_file($file) or die('config file not found');require($file);

或者

$file = '/a/b/c.php';try{    is_file($file) or throw new Exception('config file not found');}catch(Exception $e){    //todo}require($file);

第一個代碼可能簡陋了點,不多那個 die 可以換成自訂錯誤提示函數,也可以輸出友好的錯誤資訊。

那第二個代碼有什麼好處呢?是不是我在 //todo 處依舊拋出這個 $e,或者乾脆這裡不要 try/catch 了?假設我是單入口,最後整個系統的 Exception 都交給最上一層處理?

1、那依照分層處理的概念,現在就假設 config 這段代碼為“config層”,首先可以肯定的是我的“Dispatcher層”、“Controller層”、“DB層”、“Model層”統統依賴於它,難道說我要這些層自己各自去處理這個異常嗎?還是說這些層都各自再 throw 出去?或者這些層統統不管(我才不管呢,誰最後誰管)?這種“config層”一對多為其它層服務,那“config層”在設定檔丟失的時候不應該自己就處理掉嗎?這不也符合異常盡量不擴散嗎?

2、由“config層”自己處理的話,那 //todo 處理的時候不也應該 exit/die 嗎?否則豈不是又去執行 require 了?

3、那如果它自己 exit/die 掉了,這個時候異常的好處不就僅僅是比我自訂的 MyError 多了 trace 資訊嗎?

回複內容:

一直很難理解異常處理,比如我的程式底層使用了 mysql 資料庫連接,而且我的上層所有程式都建立在此基礎上(不考慮緩衝等其他),比如一個頁面要取出當前 url 中 id 指定的 post 內容,當調用底層資料庫連接時,結果 mysql_connect 無法串連,那建立在此基礎上的應用也再沒有執行的必要了,我的 mysql_connect 處不應該直接 exit/die 終止程式嗎?即使說要友好的錯誤提示,那我可以自訂一個函數比如 MyError($code),在此函數中來美化我的輸出再在裡面決定要不要 exit/die 啊。

如果使用異常處理,那在我覺得所有可能出現不正確的地方,我都必須加上 try/catch 了?那我的一段程式下來豈不是一堆 try/catch ... 這和我直接用 die/exit 或者調用自訂 MyError() 有何分別?如果說 Exception 可以往上傳遞,但我很多時候就應該當即處理啊,就比如資料庫連不上,或者我 include 系統配置的時候檔案不存在,這個時候難道不應該當即做出處理嗎?

比如我做了單入口,Router -> Controllers -> Services/Models -> ModelBase -> DbFactory -> MySQL -> Driver,是不是我在 Router 外面加個 try/catch 就可以了,裡面全都 throw?比如我的 MySQL 中 mysql_connect 出現問題了,我的 Controller 裡面在 get 取 id 時,url 中的 id 在資料庫中不存在,那這兩處我都要 try/catch/throw,然後在 Router 外面的 catch 中再處理嗎?

實在是很混亂,求解答,手冊都是教怎麼用,沒教應用情境,還有為什麼要用?(那個為什麼真心看不出為什麼),求指點!能指出具體的應用情境最好了,謝謝~

2015-06-19

很感謝各位的詳細回答,謝謝!還有一些問題想麻煩各位,寫到評論裡不太好,就拿來寫到這裡了。

比如設定檔丟失這個問題,處理的方法:

1、最原始的直接 require,這樣設定檔不存在就直接報錯了(報錯開啟),這樣顯然不合適,不友好而且會暴露實體路徑。

2、我已經預先意識到了 require 可能會出現問題,所以決定在 require 之前先 is_file/file_exists 判斷檔案是否存在,那麼:

$file = '/a/b/c.php';is_file($file) or die('config file not found');require($file);

或者

$file = '/a/b/c.php';try{    is_file($file) or throw new Exception('config file not found');}catch(Exception $e){    //todo}require($file);

第一個代碼可能簡陋了點,不多那個 die 可以換成自訂錯誤提示函數,也可以輸出友好的錯誤資訊。

那第二個代碼有什麼好處呢?是不是我在 //todo 處依舊拋出這個 $e,或者乾脆這裡不要 try/catch 了?假設我是單入口,最後整個系統的 Exception 都交給最上一層處理?

1、那依照分層處理的概念,現在就假設 config 這段代碼為“config層”,首先可以肯定的是我的“Dispatcher層”、“Controller層”、“DB層”、“Model層”統統依賴於它,難道說我要這些層自己各自去處理這個異常嗎?還是說這些層都各自再 throw 出去?或者這些層統統不管(我才不管呢,誰最後誰管)?這種“config層”一對多為其它層服務,那“config層”在設定檔丟失的時候不應該自己就處理掉嗎?這不也符合異常盡量不擴散嗎?

2、由“config層”自己處理的話,那 //todo 處理的時候不也應該 exit/die 嗎?否則豈不是又去執行 require 了?

3、那如果它自己 exit/die 掉了,這個時候異常的好處不就僅僅是比我自訂的 MyError 多了 trace 資訊嗎?

基於你的這個問題來說,你需要有一定的抽象能力,要有封裝的概念,其實我更喜歡稱之為分層,也就是我們要建立類似下列概念:

上層角色 Upper Role------------------層分界線------------當前層角色 Current Role------------------層分界線------------下層角色 Lower Role

基於上述概念來說,無論exit/die還是Exception可以分為2種情況:

 a. Upper Role作為接受方,Current Role作為行為方 b. Current Role作為接受方,Lower Role作為行為方

對於a來說,你要考慮的做出的行為的接受方Upper Role是誰?如果Upper Role是Top Role(頂層角色,一般基於我們的應用來說,頂層角色指的是使用瀏覽器的人),那麼就應該用exit/die,這個是要Top Role做出處理的,也就是除了Top Role以外的所有Upper Role無權幹涉,而當Upper Role非Top Role,那麼就應該用Exception,這樣除了Top Role以外的所有Upper Role才有權利去進行一些額外處理。

基於a來說mysql_connect串連的事情,(按照我的設計)在使用mysql_connect時,Current Role指的是DB處理串連mysql相關邏輯,而Upper Role是調用DB的未知邏輯(想想這裡我為什麼要用“未知”這個概念),Current Role僅僅知道mysql_connect串連失敗了,而並不知道Upper Role是否還有其他DB或額外選擇,那麼Current Role為了讓Upper Role有一定選擇權,這裡(我)選擇使用Exception,讓Upper Role決定是交給Top Role處理還是更上層的Upper Role處理。

對於b的情況來說,Current Role要判斷Lower Role是不是會有一些非Current Role期望的情況出現(即:Exception),則有以下選擇

是否有非Current Role期望的情況如果沒有,Current Role不需要try/catch,如果有,Current Role需要處理嗎?    Current Role需要處理,try/catch       Current Role可以處理?           可以處理,處理           Upper Role需要處理Current Role的非期望,throw              Current Role不需要處理,無視         

基於b來說mysql_connect串連的事情,Current Role指的是需要用到DB的相關邏輯,而Lower Role是DB處理串連mysql相關邏輯,在Current Role使用的時候,Current Role期望mysql可以正常串連,但是額外情形也會出現,那麼Current Role就要考慮自身的情況來選擇,比如說Current Role是一個select操作,那麼Current Role就是可以返回為空白,那麼對於msyql串連失敗或者資料表真的為空白,性質一樣,Current Role就可以將非期望捨棄掉(捕獲不處理)返回為空白,而對於一個update來說,可以選擇處理或者不處理都可以,而對於一個可以選擇n個mysql 串連的邏輯來說,就必須處理,等等

總之,囉嗦了一堆,就是說你要用好異常,那麼你要有上下層的概念,並且在上下層邏輯處理中,要分清Current Role是可以終止程式執行(exit)還是交由Upper Role處理(throw Excepton)

最後,throw是Current Role反饋給Upper Role,try/catch是Current Role處理Lower Role反饋,希望你能更好的使用Exception

樓主應該沒有理解好 異常的執行機制,先讀讀PHP官方手冊對異常的描述 點這裡。

先不說異常該怎樣使用,我們先看看 Exception 執行過程

try {    throw new Exception('This is first exception.');    echo 'AAA';} catch (Exception $e) {    echo 'BBB';}//這裡會輸出echo 'CCC';throw new Exception('This is second exception.');

你覺得上面的輸出結果是什麼呢? 下面是答案

BBBCCCFatal error: Uncaught exception 'Exception' with message 'This is second exception.' ...

從以上的執行結果,我們可以看出Exception的一些特點:

  1. try 與 catch 一定是成對出現的,try{ } 中包含可能會拋出異常的代碼。
  2. try { } 中的代碼一旦發生異常,那麼異常後面的代碼將停止執行。(這裡特指try{ }裡面的代碼)
  3. 使用 catch(){ } 捕捉並處理完異常後,try{ }和catch(){} 後面的代碼(比如這裡的 echo 'CCC')會繼續執行
  4. 如果異常沒有被捕捉,那麼將拋出一個致命錯誤 Fatal error。

結合以上的特點,那麼貼主提出來的疑問,就比較容易解決了。

問題1:是不是每個有異常的地方都要try catch 一下?

我的答案:這樣做太麻煩了,而且一般沒必要,你可以在所有可能拋出異常的地方,用一個 try catch 包含即可。catch 到異常後,對誰處理,怎麼樣處理都可以。這個也滿足單一入口,單一出口的開發理念

問題2:遇到錯誤,是拋 Exception 還是使用 die()/exit()

我的答案:異常你可以隨便拋,但最終你必須有個地方處理。處理完後(比如記錄日誌等等),最終你要把錯誤告訴前端使用者,告訴這個過程就是輸出。一旦發生錯誤時,我推薦使用異常 Exception,因為它符合單一出口開發理念,這樣你可以監控所有頁面輸出。 如果到處使用 die()/exit()來直接輸出,那就無法控制輸出了

再者,Exception 實際上本來就包含更豐富的資訊,比如行數line、錯誤碼code、錯誤堆棧資訊trace等等。所以不是更好嗎?

問題3:應用情境問題,該什麼時候用?

我的答案:異常在任何情況下用都可以,關鍵你想Exception起到什麼作用了。比如自己在寫API服務程式時,任何錯誤(包括程式異常、商務邏輯錯誤、欄位類型錯誤等等),一律拋異常,一律由一個地方處理。實際上,我感覺這種開發體驗是非常棒的,因為第一次寫的時候,我不是使用Exception的,囧~。但這裡有一個疑問:API 它是沒有介面的,它只負責返回一些資料,而且返回的資料格式要求一般是統一的規範的

如果你在寫一個前端程式,我一邊寫一邊想,可不可以這樣使用 Exception任何程式錯誤拋異常,任何業務上的驗證出錯時使用比如 return/echo/die 返回這樣子。 因為畢竟異常能表達的東西自然是有限的,如果有些錯誤(一般業務錯誤)返回非常複雜,而且前端應用非API 自由度會比較大,用異常或許不太方便。

上面就是自己的愚見吧,有不對的地方,希望指出,共同進步吧!下班了,走人。。。

如果你希望全域捕獲錯誤,或者異常,請看
http://cn2.php.net/manual/zh/function.set-error-handler.php
http://cn2.php.net/manual/zh/function.set-exception-handler.php

都允許用一個callable對象捕獲錯誤或者異常

PHP調用exit退出指令碼執行不會導致PHP服務退出,這個跟其他語言有本質區別,所以其他語言只好用try/catch捕獲異常或判斷傳回值來進一步處理,對PHP則不是必須的。

看了這個文章:http://bbs.phpchina.com/thread-212378-1-1.html,我的問題裡可能沒有理解異常,像資料庫連不上或者設定檔丟失這種或許 halt 更安全直接雖然有點粗暴。

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.