上次讀書會之後,最近在替團隊安排第二次讀書會的材料。

這次我選擇的主題是【軟體品質講座】。

在蒐集、自炊閱讀材料的過程中,腦中一直想到當年翻譯 The C++ Programming Language 時,一直被洗腦的「要以階層方式、全盤角度考量錯誤處理」觀點,以及對 defensive programming 的保留態度。

搞定這麼一疊閱讀材料後,順便把以前 The C++ Programming Language 的說法調閱出來,做個對照。近 20 年前的觀點,現在來看,仍不過時。搭配這次讀書會精心蒐集的材料,對這種「階層方式、全盤角度」的觀點,掌握得又更徹底了。

以下摘錄 The C++ Programming Language, Special Edition (2000) 書中與這主題有關的內容,順便對舊譯文稍作潤飾。



§14.9 其他做法

異常處理機制,旨在讓程式的某一部份能夠通知其他部份說「我這兒發生了異常狀況」。此機制假定:這兩個部份,可能是由不同的人獨立撰寫的;遇到此異常狀況的程式,通常自己也能就地解決一些錯誤。

想有效使用異常處理機制,就必須採取全盤策略:程式的每一個部份,必須對於「該如何使用異常」和「該在哪裡處理錯誤」達成一致的共識。本質㆖,異常處理機制是非局部性的,所以全盤策略是有必要的。也就是說:最好在系統設計最早期的階段就將錯誤處理策略納入考量,整套策略也必須清晰而簡單(相對於整支程式的複雜度而言);過於複雜的策略,無法前後一致地融入本質上就已很精密機巧的錯誤處理領域。

最重要的是:你應該屏棄「單用一套機制或技術就能應付所有錯誤」的想法,那只會帶來更多複雜度。成功的容錯系統都是多層次的,每一層都只負責它能負責的,不過於勉強自己;剩下的問題則交給更高層次去處理。

[...]

並不是每一個函數都要有這種防火牆。絕大部份系統,都不可能讓每一個函數都做這麼多檢查,不可能確保每一個函數都只有「正常結束」或「以正常狀態失敗」這兩種情況。對大型程式來說:

  1. 確保「安全可靠」所需付出的精力太過龐大了,無法前後一致地貫徹;
  2. 想讓系統運作到可接受的程度(譬如說,四處都是檢查「不合法的參數」同一類錯誤的程式碼),時間空間代價過於龐大;
  3. 以其他程式語言所寫的程式碼,不見得會遵循這些規則;
  4. 像這種純屬局部觀點的「安全可靠」觀點,反而會因過於複雜及代價過高,影響全面系統的穩固。

不過,將整個系統切割成只有「正常結束」或「以正常狀態失敗」兩種行為的獨立子系統,反而是必要的、可行的、經濟的做法;程式庫主體、子系統、關鍵函數都應該如此設計。 [...]

我們不常有機會可以從無到有撰寫系統所有的程式碼,因此,想以同一套錯誤處理策略實施於整套系統㆖,就不得不考慮到那些可能採取其他策略的程式片段。我們必須考慮各程式片段可能的資源管理方式,以及它們在處理錯誤之後,將系統置於什麼樣的狀態,我們的目標是:讓各程式片段看起來彷彿遵循同一套一般性錯誤處理策略,縱使它們的內部並非如此。

[...]

錯誤處理措施應該要盡可能地階層化。如果函數偵測到執行期錯誤,它不應該倒過來向原呼叫者要求協助或要求資源;這會導致系統依存迴路,造成「錯誤處理」與「錯誤回復」之間難以理解的無窮迴路。

你應該以「資源索取即是初始化 RAII」這類的簡化技術,搭配「以異常代表錯誤」這類的簡化假定,讓錯誤處理程式更有規律。也請參考 §24.3.7.1 介紹的方式:利用恆定性 (invariant)斷言 (assertion) 讓異常的觸發時機更加規律。



PS. 其實 The C++ Programming Language 的〈附錄E 標準程式庫的異常處理安全性〉也很精彩,不過,對其他語言使用者來說,直接看《例外處理設計的逆襲》pp. 217—250 的「強健度等級與例外處理策略」會更有幫助。