[讀書心得]Code Craft - 防禦性編程技巧
前言
書籍資訊:Code Craft,簡體中文書名為:編程匠藝,繁體中文書名為:編程創藝。
第一章即為『防禦性編程技巧』,這些東西之前充斥在我腦袋中,但卻不知道該如何來給這些想法、技巧,定義一個詞彙來說明,現在知道了,就稱作『防禦性編程』。
這一篇文章,則針對第一章的內容做個摘要,以及個人想法的補充。(因為我看的是簡體版的,所以有一些使用的詞彙會比較偏簡體用法,請見諒。)
程式碼種類
基本上程式碼可分成三類:
-
能用:在期望的正常輸入情況下不會出錯。
但輸入特殊資料則可能crash或發生錯誤。 -
正確:不管何種輸入,執行不會出錯,錯誤控制得宜。
但不一定好維護、好擴充。 -
健壯(robustness):要兼顧健壯、效能、正確。
考量到的面向較廣,例如:multi-thread的thread-safe、可維護性、彈性、Scalability。
以最壞的打算為底
在設計程式時,不要做過多的假設,這些假設都應該訴諸於約束。也就是應該額外撰寫約束的檢查,也就是Contract,來避免假設的成立,而是直接驗證其邏輯或前提是否吻合需求。
因為:
一旦有所假設,就一定會發生違反假設的情況。
什麼是假設?例如:
- 這一定有資料
- 這一定可以轉型成功
- 一定不會超出索引範圍
- 使用者會按照我們設想的方式來操作系統
防禦性編程的意義
- 儘早發現小問題,避免釀成大問題或急迫性問題。
-
防止程式出錯的措施,或在錯誤以無法理解的方式出現之前,發現錯誤。
尤其是因為設計時的假設,所引起的問題。
又大又壞的世界
- 不要假設資料是正確的、安全的、沒問題的。
- 不要假設使用者是好人,更應該把每個使用者當做壞人
- 每個Request都可能是攻擊的行為。
防禦性編程技巧
-
好的編碼風格和設計:
例如使用StyleCop,定義出屬於團隊的coding style、coding rule等等… -
不要倉促地編寫代碼:
(1)一次只做一件事
(2)透過草圖來釐清想法
(3)抽象看系統,用人話描述系統
(4)透過TDD來思考 -
不要相信任何人:
(1)外部資料
(2)外部服務
(3)正常使用者
(4)惡意使用者
(5)團隊成員的假設(包括自己)
任何資料、行為、權限,都應該驗證與約束。 -
編碼的目標是清晰,不是簡潔:
(1)簡單就是一種美,清晰到一目了然,稱為簡單
(2)寧可選擇與預期較為符合的程式碼,即使不夠優雅。也不要選擇可以達成目的,但簡潔到沒人看的懂的程式碼。 -
不要讓任何人做他們不該做的修補工作:
(1)最小知識原則
(2)封裝
(3)該用private的地方,就不要開放更大的存取層級
(4)把所有變數生命週期保持在最小的範圍內 -
編譯時,打開所有警告:
編譯器的警告可以捕捉許多愚蠢的編碼錯誤,這些錯誤因子在任何時候都可能被引爆。不管什麼情況下,都把警告的開關打開,確保撰寫出的程式碼,可以安安靜靜的完成編譯。 -
使用靜態分析工具:
就像做健康檢查報告一樣意思,透過數據來進行修正,可以讓程式碼越來越健壯。例如FxCop, SourceMonitor, Simian等工具… -
使用安全的數據結構:
例如.NET使用managed code,以及考慮thread-safe的資料結構。 -
檢查所有的回傳值:
任何回傳都可能是種錯誤,不符合預期的情況,針對這樣的情況,我們必須辨別出回傳的狀態,並做出相對應的錯誤處理(也有可能是商業邏輯錯誤) -
審慎處理記憶體管理,以及其他資源:
追求最佳效率的作法,例如:
(1)了解stack與heap的差異
(2)了解GC的運作原理
(3)了解boxing/unboxing
(4)了解reflection優缺點
(5)了解try/catch effort
(6)了解using, Dispose與Finalize -
在宣告位置初始化所有變數值:
C#編譯器會強迫檢查區域變數。 -
推遲宣告變數:
指的是讓變數的宣告位置盡量接近使用它的位置,好維護也較有效率。 -
使用標準語言工具:
避免使用編譯器預設值,例如宣告class,不定義其訪問存取層級。 -
使用好的記錄Log工具:
開發與偵錯時,可能會加入一些幫忙偵錯的code,但發佈時留著可能造成額外問題,或影響效率。如果移除了,下一次偵錯可能又需要加入同樣的code。在.NET中可以透過Debug類別來協助,在Release版本編譯時,被Debug宣告的方法內容將被清空。 -
審慎使用強轉型:
(1)了解is/as/強轉型。
(2)當可能出錯,它就真的會出錯。
(3)小心隱含轉換與精準度的差異。 -
細則(設計時的小地方):
(1)提供預設行為。例如switch/case時的default case處理。不帶else子句的if子句,思考是否該控管其預設行為。
(2)遵循語言習慣。例如.NET的命名,會在介面前加上I。
(3)檢查數值上下限。例如防呆的設計,是否overflow、index out of range、除以0的錯誤等等…這邊也可以透過code contracts實作。
(4)正確設定常數。應了解const的定義與使用方式。const效能高,但要注意重新編譯的參考問題,可能會影響線上運作不一致的情況
約束(Contract)
Code contracts基本上分為三類:
- 前置條件(pre-conditions)
- 後置條件(post-conditions)
- 不變條件(object invariants)
可以參考蹂躪的簡介。
使用斷言(assert),來確保程式在執行過程中,仍可符合預期,且在該檢查的時機就檢查出問題,避免當出錯時,程式碼額外執行了不該執行的部份。簡單的說,在第一時間把問題抓出來。
約束的內容包括了幾種:
- 檢查資料是否都在邊界內
- 檢查物件是否應為非Null
- 確保函數參數有效性
- 在函數回傳結果前,對其進行充分檢查
- 操作對象前後,確保沒有違反物件的某些限制
實作的重點則在於:在主要函數中,放置前置條件與後置條件,在關鍵循環中放置不變條件。
並記得在開發與偵錯階段啟用,正式發行環境關閉其作用。
當發現並修正一個bug時,在修正錯誤的地方,加上斷言是一個好習慣,避免錯誤不會重複發生。所以每一次bug的產生,都是一種珍貴的回饋,透過斷言來避免同樣的問題,因為不同的原因而再次發生。
結論
這邊只是簡單的做個讀書心得筆記,以及摘要。
也希望可以讓讀者朋友們了解到,程式碼不是會動就好,可以審思一下,我們面對的程式碼,是屬於三類的哪一類?通常都不是健壯那一類…
blog 與課程更新內容,請前往新站位置:http://tdd.best/