前面介紹了使用例外處理的優勢、便捷之處,本節將進一步從程式效能優化、結構優化的角度給出例外處理的一般規則。
成功的例外處理應該實現如下 4 個目標:
-
使程式程式碼混亂最小化。
-
捕獲並保留診斷資訊。
-
通知合適的人員。
-
採用合適的方式結束異常活動。
下面介紹達到這些效果的基本準則。
不要過度使用異常
不可否認,Python 的異常機制確實方便,但濫用異常機制也會帶來一些負面影響。過度使用異常主要表現在兩個方面:
-
把異常和普通錯誤混淆在一起,不再編寫任何錯誤處理程式碼,而是以簡單地引發異常來代苦所有的錯誤處理。
-
使用例外處理來代替流程控制。
熟悉了異常使用方法後,程式設計師可能不再願意編寫煩瑣的錯誤處理程式碼,而是簡單地引發異常。實際上這樣做是不對的,對於完全己知的錯誤和普通的錯誤,應該編寫處理這種錯誤的程式碼,增加程式的健壯性。只有對於外部的、不能確定和預知的執行時錯誤才使用異常。
對比前面五子棋遊戲中,處理使用者輸入坐標點己有棋子的兩種方式。如果使用者試圖下棋的坐標點己有棋子:
#如果要下棋的點不為空
if board[int(y_str) - 1) [int(x_str) - 1] !="╋" :
inputStr = input ("您輸入的坐標點己有棋子了,請重新輸入n")
continue
上面這種處理方式檢測到使用者試圖下棋的坐標點己經有棋子,立即列印一條提示語句,並重新開始下一次迴圈。這種處理方式簡潔明瞭、邏輯清晰,程式的執行效率也很好程式進入 if 塊後,即結束了本次迴圈。
如果將上面的處理機制改為如下方式:
#如果要下棋的點不為空
if board[int(y_str) - 1) [int(x_str) - 1) != "╋":
#引發預設的RuntimeError 異常
raise
上面這種處理方式沒有提供有效的錯誤處理程式碼,當程式檢測到使用者試圖下棋的坐標點己經有棋子時,並沒有提供相應的處理,而是簡單地引發一個異常。這種處理方式雖然簡單,但 Python 直譯器接收到這個異常後,還需要進入相應的 except 塊來捕獲該異常,所以執行效率要差一些。而且使用者下棋重複這個錯誤完全是可預料的,所以程式完全可以針對該錯誤提供相應的處理,而不是引發異常。
必須指出,例外處理機制的初衷是將不可預期異常的處理程式碼和正常的業務邏輯處理程式碼分離,因此絕不要使用例外處理來代替正常的業務邏輯判斷。
另外,異常機制的效率比正常的流程控制效率差,所以不要使用例外處理來代替正常的程式流程控制。例如,對於如下程式碼:
#定義一個字串列表
my_list =["Hello", "Python", "Spring"]
#使用例外處理來遍歷arr陣列的每個元素
try:
i = 0
while True:
print (my_list [i])
i += 1
except:
pass
執行上面程式確實可以實現遍歷 my_list 列表的功能,但這種寫法可讀性較差,而且執行效率也不高。程式完全有能力避免產生 indexError 異常,程式“故意”製造這種異常,然後使用 except 塊去捕獲該異常,這是不應該的。將程式改為如下形式肯定要好得多:
i = 0
while i < len(my_list):
print(my_list[i])
i += 1
注意,異常只應該用於處理非正常的情況,不要使用例外處理來代替正常的流程控制。對於一些完全可預知,而且處理方式清楚的錯誤,程式應該提供相應的錯誤處理程式碼,而不是將其籠統地稱為異常。
不要使用過於龐大的 try 塊
很多初學異常機制的讀者喜歡在 try 塊裡放置大量的程式碼,這看上去很“簡單”,但這種“簡單”只是一種假象,只是在編寫程式時看上去比較簡單。但因為 try 塊裡的程式碼過於龐大,業務過於複雜,就會造成 try 塊中出現異常的可能性大大增加,從而導致分析異常原因的難度也大大增加。
而且當時塊過於龐大時,就難免在 try 塊後緊跟大量的 except 塊才可以針對不同的異常提供不同的處理邏輯。在同一個 try 塊後緊跟大量的 except 塊則需要分析它們之間的邏輯關係,反而增加了程式設計複雜度。
正確的做法是,把大塊的 try 塊分割成多個可能出現異常的程式段落,並把它們放在單獨的 try 塊中,從而分別捕獲並處理異常。
不要忽略捕獲到的異常
不要忽略異常!既然己捕獲到異常,那麼 except 塊理應做些有用的事情,及處理並修復異常。except 塊整個為空,或者僅僅列印簡單的異常資訊都是不妥的!
except 塊為空就是假裝不知道甚至瞞天過海,這是最可怕的事情,程式出了錯誤,所有人都看不到任何異常,但整個應用可能已經徹底壞了。僅在 except 塊裡列印異常傳播資訊稍微好一點,但僅僅比空白多了幾行異常資訊。通常建議對異常採取適當措施,比如:
-
處理異常。對異常進行合適的修復,然後繞過異常發生的地方繼續執行;或者用別的資料進行計算,以代替期望的方法返回值;或者提示使用者重新操作……總之,程式應該盡量修復異常,使程式能恢復執行。
-
重新引發新異常。把在當前執行環境下能做的事情盡量做完,然後進行異常轉譯,把異常包裝成當前層的異常,重新傳給上層呼叫者。
-
在合適的層處理異常。如果當前層不清楚如何處理異常,就不要在當前層使用 except 語句來捕獲該異常,讓上層呼叫者來負責處理該異常。