第四章 注重偏執的實效
「你不可能寫出完美的軟體」,我們要把這句話視為生活的公理,並接受它、擁抱它。
但同時,有一些方法可以盡量把這個事實轉變為有利條件
作者用開車來模擬寫程式:每個人都知道只有他們自己是地球上的好司機,於是我們防衛性地開車,小心謹慎以避免麻煩發生,預判意料之外的事,盡量不讓自己陷入無法解救自己的境地。編碼也類似,我們不斷地與他人的**結合——可能不符合我們的高標準的**——並處理可能有效也可能無效的輸入。所以,我們要防衛性地程式設計。使用斷言檢測壞資料,檢查一致性並在資料庫的列上施加約束。
但注重實效的程式設計師更進一步,他們連自己也不信任。知道沒人能編寫完美的**,包括自己,所以要針對自己的錯誤進行防衛性地編碼。
採用防衛性程式設計的方式,可以幫助我們應對不完美的系統、荒謬的時間標度、可笑的工具、還有不可能實現的需求,——在這樣乙個世界中,讓我們安全「駕駛」。當每個人都確實要對你不利時,偏執就是乙個好主意。
1.
按合約設計
a)沒有什麼比常識和坦率更讓人感到驚訝,確保坦率的最佳方案之一就是合約。
b)合約既規定你的權利與責任,也規定對方的權利與責任。此外,還有關於任何一方沒有遵守合約後果的約定。
c)除了在人與人之間使用,合約也可以幫助軟體模組進行互動。
d)dbc 按合約設計(design by contract)
1)用文件記載並約定軟體模組的權利與責任,以確保程式的正確性。用文件記載這樣的說明,並進行校驗,是按合約設計的核心所在。而關於什麼是正確的程式,作者的觀點是:不多不少,做它宣告要做的事情的程式。
2)軟體模組在開始做某件事之前,對輸入狀態有某種期望,在執行結束後,會產出特定的陳述。這就涉及到前條件、後條件和類不變項。
前條件(precondition)為了呼叫例程,必須為真的條件;這是例程要求的輸入。在其前條件被違反時,例程不應該被呼叫。傳遞好資料是呼叫者的責任
後條件(postcondition) 例程保證會做的事情,例程完成時給出的結果。例程有後條件這一事實意味著他會結束,不允許有無限迴圈
類不變項(class invariant)。類確保從呼叫者的視角來看,該條件總是為真。在例程的內部處理過程中,不變項不一定會保持,但在例程退出、控制返回到呼叫者時,不變項必須為真,對這一點的理解覺得可以為:類的行為中需要遵守的約定,比如操作乙個list,我們可以規定插入新的項時,它不能是已經存在的。這一規定作為乙個不變項將應該總是被遵守。而為了更好得保證這一點,可以用dictionary型別替換list。
3)例程與任何潛在的呼叫者之間的合約可解讀為:如果呼叫者滿足了例程的所有前條件,例程應該保證在其完成時,所有後條件和不變項將為真。
如果任何一方沒有履行合約的條款,(先前約定的)某種補償措施就會啟用,例如異常或終止程式。不管發生什麼,不要誤以為沒能履行合約是bug,它不是某種絕不應該發生的事情,這也就是為什麼前條件不應被用於完成像使用者輸入驗證這樣的任務的原因。
編寫「懶惰」的**,對在開始之前接受的東西要嚴格,而允諾返回的東西要盡可能少。
4)繼承和多型作為物件導向語言的基石,是合約可以真正閃耀的領域。例如,繼承有liskov替換原則:子類必須要能通過基類的介面使用,而使用者無須知道其區別。這便是黎克特制轉換的內容了。
5)使用dbc的好處是它迫使需求與保證的問題走到前台來,在設計時簡單地列舉輸入域的範圍是什麼、邊界條件是什麼、例程允許交付什麼(或者不允許交付什麼)。如果沒有想清楚這些,就是在靠巧合程式設計,這是許多專案開始、結束、失敗的地方。此外,雖然用文件記載這些假定已經很管用,但讓編譯器幫忙檢查合約會有更好的效果。使用斷言可以對此進行部分的模擬。
2. 死程式不說謊
a)盡早檢測問題,盡早崩潰。有許多時候,讓你的程式崩潰是最佳選擇。死程式帶來的危害通常比有疾患的程式要小得多。
b)但也有時候,不能簡單退出,因為在退出前需要釋放申請的資源、需要寫日誌、處理事務、與其它程序互動等。
3. 斷言式程式設計
a)在自責中有一種滿足感。當我們責備自己時,會覺得在沒人有權責備我們。
b)如果它不可能發生,用斷言確保它不會發生。
c)斷言可能在編譯時被關閉,所以不要把必須執行的**放在assert中。
d)雖然斷言會影響效能,但最好保持執行。不要以為有測試就夠了,測試不能保證覆蓋全部情況,而且真實環境非常複雜,就算開發時已經沒問題,但就像乙個人又一次成功走過了鋼絲,不一定以後就不再需要防護器材了。寫程式時最缺的就是走鋼絲的心態,也許就是因為這種心態,很多程式設計師guru們在原始的語言、簡陋的編譯器、有限的計算資源下,寫出的卻是非常嚴密高效的軟體,例如高德納的tex排版軟體,從發版至今的幾十年間,一共只出現過15個bug。
e)斷言對效能會有一定影響,但除了對效能影響很大的斷言,最好在編譯時開啟其餘的斷言。
4 何時使用異常
a)檢查沒有可能的錯誤,特別是意料之外的錯誤,是一種良好的實踐,但是,在實踐中這可能會把我們引向相當醜陋的**,為了編碼這種情況,可以使用異常。
b)異常很少應作為程式的正常流程使用,它應保留給意外事件。
c)異常表示即時的、非區域性的控制轉移,這是一種級聯的goto,那些把異常用作正常處理的一部分的程式,將遭受到經典的義大利麵條式**的所有可讀性和可維護性問題的折磨。這些程式破壞了封裝,通過異常處理,例程和它們的呼叫者被更緊密地耦合在一起。
5 怎樣配平資源
a)資源(記憶體、事務、執行緒、檔案、定時器)的使用要遵循一種可**的模式,你分配資源,使用它,然後解除其分配。也就是說,對於資源的使用,要有始有終。分配某項資源的例程或物件應該負責解除該資源的分配。
b)巢狀的分配:對於一次需要不只乙個資源的例程,有另外兩個建議
1)以與資源分配的次序相反的次序解除資源的分配,這樣,如果乙個資源含有對另乙個資源的引用,你就不會造成資源被遺棄
2)在**的不同地方分配同一組資源時,總是以相同的次序分配它們,這將降低發生死鎖的可能性
c)資源的分配的解除的對稱,類似類的構造器和析構器。物件導向語言中,將資源封裝在類中,可自動配平。而在包含異常處理的**中,可以在finally中釋放資源。
程式設計師修煉之道 三
一 你的知識資產 在程式設計師的職業生涯中,知識和經驗是你重要的職業財富。遺憾的是,它們是有時效的資產 expiring asset 隨著新的技術 語言及環境的出現,你的知識可能會變得過時。不斷變化的市場驅動也許會使你的經驗變得陳舊或無關緊要。隨著你的知識的價值降低,對你的公司或者你的客戶來說,你的...
程式設計師修煉之道三
測試你的軟體,否則你的使用者就得測試 要想減少測試就要在程式設計是由更多的思考,如果你節省了這些思考的時間,那麼一定會在某個時候給你更大的蠻煩 程式設計一定要保證自己的程式,有更加靈活的結構。減少 的重複。因為更多的重複會給你後期測試出來的bug修改是給你更多修改麻煩。另外就是可撤銷性,因為一但你需...
程式設計師修煉之道閱讀筆記(三)
在純文字的威力一節中,作者告訴我們,通過純文字 xml sgml和html都是純文字的好例子 我們給予了自己技能以手工方式,也能以程式方式操縱知識的能力 實際上可以隨意使用每一樣工具。通過純文字,可以獲得自描述的 不依賴於建立它的應用的資料流。但是純文字需要更多的空間,且計算上的代價可能更昂貴。在儲...