錯誤處理規範

錯誤處理規範

〇、概念澄清

概念 解釋
錯誤
  • 是指:導致系統不能按照用戶意圖工作的一切原因、事件
  • 不是指:java.lang.Error及其子類
異常
  • 是指:特定編程語言、開發平臺提供的一種錯誤表現機制
  • 不是指但包括:java.lang.Error及其子類,java.lang.Exception及其子類,System.Exception及其子類 ,std::exception及其子類

一、整體規範

  1. 按照錯誤類型,通常的處理方式如下:
錯誤類型 範圍 處理方式
操作員錯誤 與人機界面交互時不滿足輸入規則、輸入範圍等發生的錯誤
  • 校驗用戶輸入
  • 提示正確規則
  • 強制其改正
運行時錯誤 與外部資源交互時發生的錯誤,如網絡、文件系統、數據庫、其它業務應用系統等
  • 記錄並拋出異常
  • 其它詳見“異常處理規範”
程序員錯誤 與客戶模塊交互時不滿足前置條件後置條件發生的錯誤,如類庫被其他程序員調用時參數超出範圍等
  • 使用斷言
  1. 按照調用類型,通常的處理方式如下:
調用類型 處理方式
同步調用
  • 對有能力處理的異常,捕獲並處理之
  • 對原始信息過於技術化的異常,捕獲幷包裝之,重新拋出
  • 在調用的中間層,對未知異常保持沉默
  • 在調用的最高層,必須捕獲所有異常,避免本身進程或宿主進程崩潰
  • 其它詳見“異常處理規範”
異步調用
  • 異步調用一般不應有任何返回值
  • 服務方最好以同樣的方式返回正常信息和錯誤信息,即正常信息是通過通知的方式返回的話,錯誤信息也應該通過通知的方式返回;正常信息是通過主動查詢得到的話,錯誤信息也應該通過主動查詢得到
  1. 按照展現方式,通常的分類如下:
展現方式 涉及模塊
界面提示
  • 只有表示層需要顯示界面提示
  • 表示層必須捕獲所有錯誤,避免本身進程或宿主進程崩潰
記錄日誌
  • 除“人機交互界面的輸入驗證模塊”、“xxx模塊”外所有模塊都應記錄錯誤日誌

二、異常處理規範

  1. 原則

  • 異常應該是分層的
  • 異常應該集中處理
  1. 異常定義

  • 每個模塊應該有自己的應用程序異常類型層次,從本模塊主動拋出的應用程序異常都應該屬於該異常類型層次,客戶代碼可以只捕捉該層次的基類(?)
  • 應用程序的所有自定義異常都應該從開發平臺提供的“應用程序異常基類”派生
  • 中間件等平臺程序的運行時異常都應該從開發平臺提供的“運行時異常基類”派生(?)
  • 中間件等平臺程序的運行時錯誤都應該從開發平臺提供的“錯誤基類”派生(?)
  1. 異常捕獲

  • 對有能力處理的異常,捕獲並處理之
  • 在調用的中間層,對未知異常保持沉默
  • 在調用的最高層,必須捕獲所有異常,避免本身進程或宿主進程崩潰
  • 記錄捕獲到的每個原始異常的信息
  1. 異常拋出

  • 每個模塊應使用本模塊所能得知的最精確的錯誤原因報告異常信息
  • 如果有原始異常,在重新拋出的自定義異常中附加原始異常的信息

三、幾點說明

  1. 錯誤處理與日誌系統

  • 錯誤處理不等同於日誌系統,日誌系統只是錯誤信息的一種記錄手段
  • 錯誤信息的輸出應全部調用日誌系統來完成
  1. 程序員錯誤與運行時錯誤

  • 接口函數的前置條件,應該是一種規範,是客戶程序員必須遵守的約定;客戶程序員違反了約定,程序將產生異常或不可預知的錯誤;對於這類約定,不應該需要有運行時的代碼檢查 ;比如如果你的接口函數的一個參數不能爲null,而你在函數開始部分程序裏寫:

if(xxx == null){

    throw new someException();

}

你的代碼實際上允許該參數爲null,因爲你對null的情況進行了運行時處理;儘管你在文檔裏聲明該參數不能爲null,但如果客戶程序員遵守了約定,那麼你這段檢查代碼就是冗餘的

  • 當你對參數沒有任何檢查就進行了使用,而非法的參數值導致了錯誤,此時有兩種情況: 如果你在接口函數說明裏列出了參數不允許的非法取值,那麼客戶程序員應該修改程序避免傳入非法值,該類錯誤稱爲程序員錯誤;如果你沒有說明,那麼你應該修改函數,處理非法參數
  • 不是所有的限制條件在文檔裏說明一下,就可以把責任扔給客戶程序員了,有一些錯誤必須處理:特別是客戶程序員不需要import你的package就可以和你的接口交互的情況,如socket服務器 ,你必須在socket服務程序內部檢查所有接收到的數據,拒絕錯誤的請求,否則極易遭到攻擊
  1. 錯誤代碼與異常

  • 不應該使用bool值來返回成功與否,bool返回值只應用在真正查詢bool狀態的操作中,如 bool IsDirty(),bool hasNext()等
  • 整形或bool型的錯誤代碼返回值強迫客戶程序員檢查每一次調用,應用異常取代之

四、附錄

  1. Anders Hejlsberg談C#異常設計

譯者注:在寫一段程序時,如果沒有用try-catch捕捉異常或者顯式的拋出異常,而希望程序自動拋出,一些語言的編譯器不會允許編譯通過,如Java就是這樣。這就是Checked Exceptions最基本的意思。該特性的目的是保證程序的安全性和健壯性。Zee&Snakey(MVP)對此有一段很形象的話,可以參見http://www.blogcn.com/user2/zee/main.aspBruce Eckel 也有相關的一篇文章:《Does Java need Checked Exceptions》,參見http://www.mindview.net/Etc/Discussions/CheckedExceptions

  • Checked Exceptions特性持保留態度

Bruce Eckel C#沒有Checked Exceptions,你是怎麼決定是否在C#中放置這種特性的麼?
Anders Hejlsberg 我發現Checked Exceptions在兩個方面有比較大的問題:擴展性和版本控制。我知道你也寫了一些關於Checked Exceptions的東西,並且傾向於我們對這個問題的看法。
   
Bruce Eckel 我一直認爲Checked Exceptions是非常重要的。
Anders Hejlsberg 是的,老實說,它看起來的確相當重要,這個觀點並沒有錯。我也十分讚許Checked Exceptions特性的美妙。但它某些方面的實現會帶來一些問題。例如,從JavaChecked Exceptions的實現途徑來看,我認爲它在解決一系列既有問題的同時,付出了帶來一系列新問題的代價。這樣一來,我就搞不清楚Checked Exceptions特性是否可以真的讓我們的生活變得更美妙一些。對此你或許有不同看法。
   
Bruce Eckel C#設計小組對Checked Exceptions特性是否有過大量的爭論?
Anders Hejlsberg

不,在這個問題上,我們有着廣泛的共識。C#目前在Checked Exceptions上是保持緘默的。一旦有公認的更好的解決方案,我們會重新考慮,並在適當的地方採用的。我有一個人生信條,那就是——如果你對該問題不具有發言權,也沒辦法推進其解決進程,那麼最好保持沉默和中立,而不應該擺出一個非此即彼的架勢。

假設你讓一個新手去編一個日曆控件,他們通常會這樣想:“哦,我會寫出世界上最好的日曆控件!我要讓它有各種各樣的日曆外觀。它有顯示部分,有這個,有那個……”他們將所有這些構想都放到控件中去,然後花兩天時間寫了一個很蹩腳的日曆程序。他們想:“在程序的下一個版本中,我將實現更多更好的功能。”

但是,一旦他們開始考慮如何將腦海中那麼多抽象的念頭具體實現出來時,就會發現他們原來的設計是完全錯誤的。現在,他們正蹲在一個角落裏痛苦萬狀呢,他們發現必須將原來的設計全盤拋棄。這種情況我不是看到一次兩次了。我是一個最低綱領主義者。對於影響全局的問題,在沒有實際解決方案前,千萬不要將它帶入到整個框架中去,否則你將不知道這個框架在將來會變成什麼樣子

   
Bruce Eckel 極限編程(The Extreme Programmers)上說:“用最簡單的辦法來完成工作。”
Anders Hejlsberg 對呀,愛因斯坦也說過:“儘可能簡單行事。”對於Checked Excpetions特性,我最關心的是它可能給程序員帶來哪些問題。試想一下,當程序員調用一些新編寫的有自己特定的異常拋出句法的API時,程序將變得多麼紛亂和冗長。這時候你會明白Checked Exceptions不是在幫助程序員,反而是在添麻煩。正確的做法是,API的設計者告訴你如何去處理異常而不是讓你自己想破腦袋。

 

  • Checked Exceptions的版本相關性

Bill Venners 你提到過Checked Exceptions的擴展性和版本相關性這兩個問題。現在能具體解釋一下它們的意思麼?
Anders Hejlsberg

讓我首先談談版本相關性,這個問題更容易理解。假設我創建了一個方法foo,並聲明它可能拋出ABC三個異常。在新版的foo中,我要增加一些功能,由此可能需要拋出異常D。這將產生了一個極具破壞性的改變,因爲原來調用此方法時幾乎不可能處理過D異常。

    也就是說,在新版本中增加拋出的異常時,給用戶的代碼帶來了破壞。在接口中使用方法時也有類似的問題。一個實現特定功能的接口一經發布,就是不可改變的,新功能只能在新版的接口中增加。換句話說,就是隻能創建新的接口。在新版本中,你只有兩種選擇,要麼建立一個新的方法foo2foo2可以拋出更多的異常,要麼在新的foo中捕獲異常D,並轉化爲原來的異常AB或者C

   
Bill Venners 但即使在沒有Checked Exceptions特性的語言中,(增加新的異常)不是同樣會對程序造成破壞麼?假如新版foo拋出了需要用戶處理的新的異常,難道僅僅因爲用戶不希望這個異常發生,他寫代碼時就可以置之不理嗎?
Anders Hejlsberg

不,因爲在很多情況下,用戶根本就不關心(異常)。他們不會處理任何異常。其實消息循環中存在一個最終的異常處理者,它會顯示一個對話框提示你程序運行出錯。程序員在任何地方都可以使用try finally來保護自己的代碼,即使運行時發生了異常,程序依然可以正確運行。對於異常本身的處理,事實上,程序員是不關心的。

很多語言的throws語法(如Java),沒必要地強迫你去處理異常,也就是逼迫你搞清楚每一個異常的來源。它們要求你要麼捕獲聲明的異常,要麼將它們放入throws語句。程序員爲了達到這個要求,做了很多荒謬可笑的事情。例如他們在聲明每個方法時,都必須加上修飾語:“throws Exception”。這完全是在搧這個特性的耳光,它不過是要求程序員多作些官樣文章,對誰都沒有好處

   
Bill Venners 如此說來,你認爲不要求程序員明確的處理每個異常的做法,在現實中要適用得多了?
Anders Hejlsberg 人們爲什麼認爲(顯式的)異常處理非常重要呢?這太可笑了。它根本就不重要。在我印象中,一個寫得非常好的程序裏,try finallytry catch語句數目大概是101。在C#中,也可以使用和類似try finallyusing語句(來處理異常)
   
Bill Venners finally到底幹了些什麼?
Anders Hejlsberg finally保證你不被異常干擾,但它不直接處理異常。異常處理應該放在別的什麼地方。實際上,在任何一個事件驅動的(如現代圖形界面)程序中,在主消息循環裏,都有一個缺省的異常處理過程,程序員只需要處理那些沒被缺省處理的異常。但你必須確保任何異常情況下,原來分配的資源都能被銷燬。這樣一來,你的程序就是可持續運行的。你肯定不希望寫程序時,在100個地方都要處理異常並彈出對話框吧。如果那樣的話,你作修改時就要倒大黴了。異常應該集中處理,並在異常來臨處保護好你的代碼

 

  • Checked Exceptions的擴展性

Bill Venners 那麼Checked Exceptions的擴展性又是如何呢?
Anders Hejlsberg

擴展性有時候和版本性是相關的。 在一個小程序裏,Checked Exceptions顯得蠻迷人的。你可以捕捉FileNotFoundException異常並顯示出來,是不是很有趣?這在調用單個的API時也挺美妙的。但是在開發大系統時,災難就降臨了。你計劃包含45個子系統,每個子系統拋出410個異常。但是(實際開發時),你每在系統集成的梯子上爬一級,必須被處理的新異常都將呈指數增長。最後,可能每個子系統需要拋出40個異常。將兩個子系統集成時,你將必須寫80throw語句。最後,可能你都無法控制了。

很多時候,Checked Exceptions都會激怒程序員,於是程序員就想辦法繞過這個特性。他要麼在到處都是寫“throws Exception”,要麼——我都不知道自己看到多少回了——寫“try, da da da da da(譯者注:意思是飛快的寫一段代碼), catch curly curly(譯者注:即‘{ })”,然後說:“哦,我會回頭來處理這些空的異常處理語句的。”實際上,理所當然的沒有任何人會回頭幹這些事情。這時候,Checked Exceptions已經造成系統質量的極大下降。

所以,你可能很重視這些問題,但是在我們決定是否將Checked Exceptions的一些機制放入C#時,卻是頗費了一番思量的。當然,知道什麼異常可能在程序中拋出還是有相當價值的,有一些工具也可以作這方面的檢查。我不認爲我們可以建立一套足夠嚴格而嚴謹的規則(來完成異常檢查),因爲(異常)還可能是編譯器的錯誤引起的呢。但是我認爲可以在(程序)分析工具上下些功夫,檢測是否有可疑代碼,是否有未捕獲的異常,並將這些隱藏的漏洞給你指出來

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章