設計和維護好的軟體就像是一個抵制複雜度的永無止境的奮鬥過程。任何足夠大小的應用程式的程式碼路徑和元件都能迅速成長成令人眼花繚亂的組合爆炸。
一點都不簡單。
當部署類似於Heroku和AWS的平臺時,單伺服器的Web應用程式成為了分散式系統。現代瀏覽器模糊了客戶端和伺服器之間的界線。當簡單程式在多個CPU核心上執行時,它們就會成為複雜的協調問題。雖然像測試驅動開發等實踐和SOLID原則等指導,可以幫助我們模擬問題,簡化解決方案,但大多數軟體應用程式都是一些複雜的系統,每個元件也會以意想不到的方式進行互動和組合。
當軟體系統中發生意外情況時,會造成很嚴重的後果。幸運的是,軟體開發人員可以借鑑另一門更古老的學科,來應對對於複雜系統的關注、維護和除錯,這門學科就是:醫學。
鑑別診斷是醫生用來匹配系列症狀及其可能病因的系統化方法。一個好的鑑別診斷包括以下4個步驟:
- 列出所有觀察到的症狀。
- 列出可能的病因。
- 按輕重緩急給這些病因排名。
- 按照優先順序進行測試,以排除病因。
雖然上面這4個步驟是為醫生而整理的,但是我們同樣可以像一個醫生一樣思考,用一種強有力的方式來找到並消除軟體缺陷。將診斷過程分解為一個一個目的單一的步驟,確保每個步驟都能得到應有的重視。按照優先順序是為了保證專注檢查的重點,並作出務實的干預措施。然後進行測試,排除假設,以確保除錯的嚴謹。
白板是個好東西
當錯誤發生時,我們大多會想也不想地立馬去調查最可能的原因。懂得向後跟蹤和少許背景知識,人性就會趨向於投機主義。但是好的診斷始於列出的症狀,而不是病因。寫下可以觀察出來的所有症狀,無論是異常處理,還是錯誤程式碼,哪怕只是異常的行為,都可以。可以使用文字編輯器或者白板,但是,你最好能對診斷過程中的每一個步驟做筆記,這很重要。從假設出發分開觀察,有助於確保你不會排除或忽視潛在原因。並且多數時候,列出更多的症狀反而會縮小可能範圍,避免你將時間浪費在測試不正確的假設上。
寫好了一系列症狀,那麼接下來就可以開始考慮原因了。
斑馬和馬
“當你聽到馬蹄聲的時候,找的應該是馬,而不是斑馬。”
在應用程式中出現程式碼bug的可能性比在Web框架中出現bug的可能性要大,而在Web框架中發現bug又比在作業系統中發現bug更容易。當然讓別人來審查程式碼是個好主意,但事實是,大多數bug審查起來特別無聊。所以在開始考慮進階到更復雜的問題之前,先給出最簡單的解釋。
話又說回來,正如同一個症狀卻又可能是完全不同的病因引發的,所以我們應該將所有能想到的相關病因都寫下來。就像原先我們對症狀直接描述為“what”,後來用“how”區分開來,頭腦風暴解釋法的目的是用“how likely”來區分“how”。捕捉任何看似合理的要點,以便於節約分析。
重中之重,不能有害
鑑別診斷與其他的演繹方法不同,因為醫生必須不斷地評估風險,並權衡對病人生命的影響。當然如果我們的產品中存在著bug,雖然不會像醫生那樣負有生命責任那般嚴重,但是停頓修復會產生既現實又痛苦的成本費用。就像威脅生命的疾病事件一樣需要立即進行干預,嚴重的bug可能需要粗暴的簡單修復,例如回滾和重新啟動。將假設按優先順序排列,然後再考慮權衡,並判斷決定是否啟動測試假設或立即進行干預。
準備圖表
正如患者會有醫院病歷和其他背景資訊的圖表,你的軟體系統可能也需要具備圖表。從日誌和錯誤報告系統收集資訊,來說明你的分析。至於系統指標和跟蹤誤差,你不妨將它們當作是明智的預防性藥品。
如果你的病人尚未處於嚴重危險之中,那麼可以先進行假設-演繹。從你定義的優先順序最高的假設開始,一個一個地證明它們是錯誤的。雖然支援性證據有時候或許能有助於你找到bug的所在,但是失敗的測試驅動了演繹過程。這乍一看上去似乎有悖直覺,但是測試-消除假設策略是追溯bug到它的起因的最快方式。在許多情況下,一些簡單的測試就可以一次消除幾個假設。當然,也有時候,為了否決假設你就得執行更多的測試。
實驗室工作
不同於醫療世界的令人難以接受,只要你願意,你隨時都可以克隆軟體應用程式,執行可怕的人體實驗。如果你有足夠的資訊來觸發你要診斷的bug,那麼可以將它複製到受控環境中,例如一個有著最新資料庫備份的臨時伺服器。當你消滅原因,收集到新的資料,並完善假設之後,你的bug的真正原因線索將變得更加清晰。
清楚地思考複雜系統需要的關心和專注。採用結構化的診斷過程來指導檢查可以節省時間和避免挫折感。最重要的是,它 很有用。下次你再陷入bug之中時,那麼不妨試試拋開鍵盤,將步驟一步一步寫到白板上,像一個醫生診病一樣進行除錯。
相關閱讀
評論(1)