如題,Python既可以返回複雜的值,也有不錯的錯誤拋出/捕獲功能。那麼在工程代碼或者庫開發中,如果函數碰到了非預期情況(如參數類型錯誤、操作非法),是約定某個返回值代表錯誤比較好呢,還是直接拋出錯誤比較好?

舉個例子,比如python的BIF,str的子串查找,同樣面對未找到的情況,find方法返回-1,而index方法拋出ValueError錯誤。


這其實是一個編碼規範的問題,沒有任何場景都適用的解決方案,就好比有了 list,但是還是需要 tuple,所以對於拋出異常好,還是返回值好,是需要具體情況看的。

在 C 語言中,通用的做法是函數返回一個 code 描述狀態,例如 main 函數,一般返回 0 就是正常,其他數值就需要去查錯誤碼。這種方式仍廣泛使用於 HTTP API 等介面。而到了面向對象編程語言,異常也是一個類的實例,用於表達特定的意思,這樣異常有了更豐富的表現方式,處理方式也顯得更優雅。

但是異常的捕獲就是萬能的了嗎?

並不是,現實情況是,不少人仍是「錯誤」的處理異常。例如從頭到尾的一個 try except,並且捕獲所有異常,這就是為了處理異常而處理異常,反而增加了代碼量。

異常捕獲其實也是需要講究技術的,就好比會寫代碼,不一定會寫出優雅的代碼、高效的代碼;會捕獲異常,不一定會「優雅」地捕獲異常。

對於開發團隊的管理者來說,一些編碼「規範」(例如何時改拋出異常,怎麼捕獲等)也是越早立下越好,對於後期項目的維護和轉移是非常有利的。

對於開發者來說,一些編碼「規範」的培養,不但是個人編碼「素養」的提高,也是技術視野的提升。一段時間後反觀自己過去的代碼,一定會覺得怎麼寫的這麼「醜陋」。

下面我也簡單講一下什麼場景下該怎麼「使用」異常。

如下情況應該盡量使用異常處理:

  • 執行的錯誤情況非常多樣化,單一的返回值無法表示多種情況
  • 執行的錯誤需要調用方區別對待
  • 方法的調用層次比較深,拋出異常能使代碼更簡潔且易讀
  • 用於鏈式調用的方法,例如生成器模式中,避免調用方多餘的判斷

如下情況應該盡量避免使用異常處理:

  • 不要用異常做條件判斷,條件判斷語句的性能一般更好
  • 方法返回值簡單,且用於條件判斷的,例如 if 語句中的子串查找都用 find 而不是 index

下面是異常處理的一些規範分享:

  • 不要在最外層調用整段 try except,這非常不負責任,而且也不好 debug
  • 一般捕獲的都是 Exception 異常的子類,一般情況下不用捕獲底層異常
  • 系統一般都推薦定義自己的異常類(甚至是異常類樹),即使內容是空的(最好有文檔和__str__),便於區分
  • 盡量捕獲明確的子類異常,用多 catch 捕獲,最後可以捕獲 Exception,不要上來就直接捕獲 Exception,區分異常的類型能更好的幫助我們區別處理
  • 不要為了不拋出異常而去捕獲並拋棄,如果不能處理,就拋給上層調用者
  • 要善於使用 finally 和 else,特別是資料庫事務,連接等需要主動關閉或回滾的情況
  • 不需要每次捕獲都列印日誌,規劃好日誌級別,不然日誌會難以閱讀
  • 捕獲嚴重異常後列印日誌最好用 traceback 帶上調用鏈信息,便於定位問題

先寫這麼多吧,語言等技術只是實現方式,最後還是得看我們需要解決的問題。


找不到是個結果,不是錯誤,此時返回-1並不會引起歧義,它的意思的明確的

下標越界是錯誤,即使這點存在討論空間,那麼這時不拋異常又能返回什麼呢


  • 對於我們並不期望發生的錯誤,使用try except 來捕獲,並做好錯誤棧的列印,錯誤的善後處理
  • 對於我們覺得可能會發生的錯誤,可以使用錯誤返回碼。
  • 我自己的使用原則一般都是,用戶使用,業務過程中發生的錯誤,更多使用返回碼。系統級別的錯誤,比如fd許可權,資源不足,我會使用異常捕獲。
  • 上述都是針對同步代碼,非同步的話,就要分開說了。


主要是看要做什麼任務吧。

比如面向用戶的圖形化界面和web,要求具有用戶友好的特點,肯定不能因為一個錯誤就崩潰。一般就try except捕捉異常,遇到異常彈出提示框,也就可以理解為約定返回值。

如果是數據處理演算法之類的,要求是嚴謹,有問題不能將錯就錯,肯定是要停止運行,這樣做可以提示程序員當前操作是錯誤的,可以儘快調整。

這裡我覺得可以分享一下我去年暑假寫的模塊pynotice,在程序出錯的時候可以用聲音或者郵件提醒,需要在pypi上下載安裝,國內源不知道為啥一直不更新

閃電俠的右手:模型跑好久不知道什麼時候結束?pynotice通知您!?

zhuanlan.zhihu.com圖標

約定返回值的優勢在節省資源,c的錯誤處理、unix諸多命令、HTTP狀態碼都是類似的設計;拋出exception則是隨計算機資源越來越豐富,逐漸演化出來的,更方便人類閱讀理解編碼的方式。

在程序內部,對於Java、Python這樣有完備異常處理的語言,一般用exception就行了。資源真缺到一定地步,也不會用Python吧。。

如果是介面,一般用返回碼好,節省資源,異常類型也不太多,且也容易形成相應規範。


Optional + Error.

從 Python 的類型註解中可以找到 Optional 類型 : Optional[X]Union[X,None] 的縮寫. 也就是說, Python 是鼓勵這種模式的. 換句話說, 標準庫中 find 函數的設計, (至少我認為)是有問題的, 它應該返回一個 Optional[X], 找不到的時候應該返回 None 而不是什麼 -1. 換句話說, -1 這個東西不具備一般意義.

Error 本身是一個 Effect, 用來彌補寫程序時檢查無法預知的錯誤, 例如 IO 錯誤, 內存錯誤.

諸如給函數傳入了錯誤類型的參數, 這種事屬於程序編寫的錯誤, 換句話說, 如果設計者不粗心大意, 理論上根本就不會出現的那類問題, 對於這種錯誤, 應該想辦法讓程序崩潰, 並督促程序員修改這段邏輯.

你舉的這兩個例子很有意思, find 的目的是什麼? 就是希望讓程序員判斷一個數組中是否含一個值, 如果含, 這個值在哪裡? 這是編寫程序邏輯的一環, 它相當於一個用於搜索的 for 循環的封裝, 沒道理要拋出異常. 而 index 就不一樣了, 請你回答, 對一個大小是 10 的數組, 我取第 20 個元素, 這個操作的寓意何在么? 這完全就是一段混亂的代碼, 一旦這種代碼出現, 證明編寫者根本就沒搞清楚自己在幹嘛, 直接讓程序崩潰就可以了.


find使用的場景是不確定是否存在的情況,如果不存在就報錯,函數可用性就有問題。

index 是要知道str的位置,潛台詞是這個東西是存在的,如果沒存在就報錯,因為前置要求崩了。

函數怎麼返回還是根據你業務期望


分階段

開發階段: 老程序員會用assert這樣的語法,如果條件不滿足就儘早主動拋出錯誤,以免後期出現未知錯誤時 ,還需要返回頭查錯誤在哪裡發生的。

上線系統, 應該有一定的容錯性。出現極端情況也應該顯示一些信息,比如顁制的500頁面,而不是直接拋出異常信息給最終用戶。一不專業,二不安全,黑客可以直接看到你的後台配置。在View或者Controller層,用log記錄下所有的錯誤信息方便後期重現。


推薦閱讀:
相关文章