論程序中的錯誤處理

     一直以來,都對程序中的錯誤處理存有許多疑惑。介紹程序本身的文章有很多,但是介紹對錯誤的處理的卻甚少。經過在三年的編碼實踐中,總結出了一些對錯誤處理的原則,現寫下來,僅作爲參考和討論。
     
     我對“錯誤”的理解:程序運行時與預期不一致的場景。導致與預期不一致的原因有多種,錯誤的可能情況,隨着操作主體的增加,會變得難以完全羅列。
      總結下來,錯誤主要分爲兩種:“邏輯不一致”和“物理不一致”。其中“邏輯不一致”表示一個操作執行之後,得到了結果,但是結果跟預期不一致。比如查詢用戶的狀態,預期用戶狀態應該爲“鎖定”,但實際上狀態是“正常”。“物理不一致”是指一個操作在執行過程中,並未得到結果。比如查詢數據庫時,數據庫返回了一個錯誤。
     以上是對基本概念的一些解釋。接下來,我給出了幾個在處理錯誤過程中的規則。

1、千萬不要把bug當做錯誤
     在頭腦中思考對錯誤的處理時,最讓我苦惱的就是,各種繁雜的“與預期不一致”的情況混在在一起,無法對其進行分類,然後分門別類的加以分析,從而也就難以在程序中精確地對這些情況進行反饋、處理。注意我剛用了個“與預期不一致”,而不是“錯誤”,這是因爲這些“與預期不一致”的情況並非都是錯誤,其中一部分,應該被定性爲“bug”。比如用戶的狀態本應該爲“鎖定”,但實際上卻是“正常”,如果按照系統完全正常、沒有bug的情況,絕對不應該出現狀態爲“正常”的情況,那這個場景因該被定性爲“bug”,並且應當在測試階段予以暴露並解決。否則,這些bug隱藏在程序中,被當做錯誤來處理,而由於bug所導致的“預期不一致”的情況,往往是難以自動糾正的。
     bug與錯誤的區分,我認爲應當是“獲取到了結果,但結果錯了”與“獲取結果的過程出錯了”的區別。對於前者,在開發與測試階段,應當儘可能的讓其暴露出來,以便予以修正。但是程序到了一定規模,往往bug是很難完全排除的,這一點需要在實踐中不斷積累,提高程序的可靠性以及讓bug暴露的概率。這其中最忌諱的,就是把bug當做錯誤,一併處理,這樣的系統跑起來後,絕對會讓你崩潰。
     bug既然不能歸入“錯誤”,那麼在程序中對這一類“與預期不一致”的情況,要給予嚴重警告甚至報錯退出,這其中可以利用斷言之類的操作,讓其容易被發現,並且被及早解決。
     另外,無論是bug還是錯誤,都應該對所有可能出現的點進行控制,不應當有忽略錯誤的情況發生,除非這個錯誤被認定爲允許被忽略。但這個時候,這更像一個狀態,而不是一個錯徐。

2、利用原子操作
     典型的原子操作就是數據庫的事務,利用數據庫的該特性可以做到一系列的操作“要麼完全成功”,要麼“什麼都沒做”的效果。避免出現不一致的數據。但是如果碰到數據庫操作中間穿插着其他系統的操作,那這些操作往往難以組成一個事務。同時由於數據庫的操作和其他系統的操作都存在出錯的可能性,導致“一定會出現不一致數據”的場景出現。對此我不知道是否存在優秀的解決方案,對此也歡迎加以討論,完善這部分內容。今天跟領導討論下來,領導給出了一個很不錯的方案。方案基本上是模擬事務的處理。針對剛纔提到的數據庫操作與第三方系統操作並存的情況,可以先讓數據庫操作成功,並且成功在一箇中間狀態上。然後再去執行第三方系統的操作,並根據操作反饋,來更新數據庫的中間狀態。這時候,前面那位蹲着的同學提出來了:“你數據庫更新中間狀態時,仍然有可能出錯,還是會導致數據不一致的情況發生!”。這位同學的分析是對的,的確會出現這種情況。請看下一條原則。

3、儘量設計可重入的過程(函數、方法等)
     可重入的概念在數學上稱作“冪等性”,是說一個操作做1次和做n次的結果是一樣的,比如說計算1的平方這個操作,你無論做多少次,結果都是1,那這就是個冪等性的操作。應用到程序中呢,可以說執行一個函數,執行1次和執行n次,結果是一樣的。當然其中一次如果出錯了,那繼續執行,有可能得到正確結果。這個理論用於處理第2調最後提出的問題,如果第三方系統操作未得到結果,那應當有相關的操作,可以查到上一次操作的結果,根據不同的結果,來重新執行,或者是修正數據庫狀態。如果這整個操作設計成可重入的,那在外部調用這個過程時,就可以簡單地循環調用,直到返回正確結果。如果碰到操作實在無法設計成可重入或者爲了實現可重入要付出很重的代價,可以考慮將操作拆分,嘗試將可重入的操作與不可重入的動作拆分,從而嘗試整個操作的可重入設計。

4、錯誤可以包裝後向上層提交
     這個原則是出於亮點考慮。第一點,一個錯誤應當由“能夠決定出現這個錯誤之後接下來該怎麼辦”的邏輯來進行解釋並處理,而不是隨意在某個節點進行解釋並處理;第二點,每一層都不可能知道這個錯誤的完整原因是什麼,需要每一層將自己所知道的信息進行彙報。第一點好理解,第二點比較像設計模式中的“裝飾者模式”,當前層級對錯誤進行裝飾,並將裝飾了本層次信息的錯誤向上提交。比如說,在最外邊的層次可能得到這樣一個錯誤信息“鎖定用戶失敗:更新用戶信息失敗:執行數據庫更新失敗:數據庫連接已斷開”,如此可以清晰地用類似於調用棧的形式來展現錯誤的來龍去脈,十分的明確。同時,還可以將錯誤對象設計成嵌套模式,這樣就可以解析出每一層的錯誤信息。不過我認爲必要性不大,你知道了,也難以對每一種錯誤做出針對性的處理,還不如利用第3條,通過重入的方式來修正系統。


備註:本文允許隨意轉發,但轉發時需帶有原文章作者以及原始鏈接信息,謝謝。
      
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章