第 1 章 介紹
(一切都圍繞複雜性)
編寫計算機軟體是人類歷史上最純粹的創作活動之一。程式設計師不受物理定律等現實條件的約束,可以創造出令人興奮的虛擬世界,讓其中的事物以現實世界中不可能的方式執行。程式設計不像芭蕾或籃球那樣,需要高超的身體技巧或協調能力。程式設計所需的,只是富有創造力的頭腦和組織思路的能力。如果你能在腦中構想出一個系統,很可能就能用計算機程式實作它。
本章概覽這本書要解決什麼問題?
這意味著,編寫軟體的最大限制在於我們理解所建立系統的能力。隨著程式不斷演進、功能不斷增加,程式會變得越來越複雜,元件之間也會產生微妙的依賴關係。複雜性日積月累,程式設計師在修改系統時越來越難以顧及所有相關因素。這會減慢開發速度並導致程式碼缺陷,而缺陷又會進一步拖慢開發、增加成本。在任何程式的生命週期中,複雜性都不可避免地會增加。程式越大,參與開發的人越多,管理複雜性就越困難。
好的開發工具可以幫助我們應對複雜性,過去幾十年也湧現了許多出色的工具。但僅靠工具,能做的終究有限。要讓軟體更容易編寫,以更低的成本構建功能更強大的系統,就必須找到簡化軟體的方法。即使竭盡全力,複雜性仍會隨著時間推移而增加;不過,更簡單的設計能讓我們在複雜性變得難以駕馭之前,構建出更大、功能更強大的系統。
有兩種解決複雜性的通用方法,這兩種方法都將在本書中進行討論。第一種方法是透過使程式碼更簡單和更易理解來消除複雜性。例如,可以透過消除特殊情況或以一致的方式使用識別符號來降低複雜性。
應對複雜性的第二種方法是將其封裝起來,讓程式設計師在開發系統時不必同時面對全部複雜性。這種方法稱為模組化設計。在模組化設計中,軟體系統被劃分為模組,例如物件導向語言中的類。各模組在設計上相對獨立,程式設計師可以開發其中一個模組,而不必瞭解其他模組的細節。
譯者圖解消除規則,還是隔離規則?
軟體易於改變,因此軟體設計是一個貫穿系統整個生命週期的持續過程。這一點不同於建築物、船舶或橋樑等物理系統的設計。但人們並非一直這樣看待軟體設計。在程式設計的早期,設計往往集中在專案開始時進行,與其他工程學科一樣。這種做法的極端形式稱為瀑布式模型:專案被劃分為需求定義、設計、編碼、測試和維護等彼此分明的階段。每個階段完成後,下一個階段才會開始;許多專案還會由不同的人負責各個階段。整個系統在設計階段一次性設計完畢,隨後凍結設計,後續階段則負責細化並實作它。
不幸的是,瀑布式模型很少能在軟體開發中取得良好效果。軟體系統本身比物理系統更複雜。在尚未動手構建時,不可能把大型軟體系統的設計構想得足夠周全,預先看清其中的所有影響。因此,初始設計會存在許多問題,往往要到實作工作推進了相當一段時間後才會顯現。但瀑布式模型並不便於在此時作出重大設計變更(例如,設計人員可能已轉到其他專案)。於是,開發人員設法在不改變整體設計的前提下修修補補,導致複雜性急劇增加。
由於這些問題,當今大多數軟體開發專案採用敏捷開發等增量方法,初始設計只關注整體功能的一小部分。先設計、實作並評估這一小部分,找出並修正設計中的問題,再設計、實作和評估更多功能。每次迭代都會暴露現有設計的問題,並在設計下一批功能前解決這些問題。把設計工作分散到整個開發過程中,就能在系統還很小時修正初始設計的問題。後續功能可以借鑑實作早期功能時積累的經驗,因此問題也會較少。
增量方法適用於軟體,因為軟體足夠靈活,允許在實作過程中進行重大設計變更。相比之下,物理系統的重大設計變更要困難得多:例如,在施工中途改變支撐橋樑的塔架數量就不切實際。
增量開發意味著軟體設計永無止境。設計貫穿系統的整個生命週期,開發人員應始終思考設計問題。增量開發也意味著持續重新設計。系統或元件的初始設計幾乎從來都不是最佳設計;隨著經驗積累,總會發現更好的做法。作為軟體開發人員,你應不斷尋找改進系統設計的機會,並在計劃中為此留出一部分時間。
如果軟體開發人員應始終考慮設計問題,而降低複雜性是軟體設計中最重要的要素,則軟體開發人員應始終考慮複雜性。這本書就是關於如何使用複雜性來指導軟體設計的整個生命週期。
本書有兩個總體目標。首先是說明軟體複雜性的本質:“複雜性”是什麼意思,為什麼重要,又該如何識別程式中不必要的複雜性?第二個目標更具挑戰性:介紹在軟體開發過程中儘量減少複雜性的技術。遺憾的是,沒有簡單的訣竅能保證做出出色的軟體設計。因此,我會提出一些較高層級、近乎哲學的概念,例如“類應該是深的”或“透過定義來規避錯誤”。這些概念未必能讓你立即找到最佳設計,卻能幫助你比較備選方案,探索可能的設計。
1.1 如何使用這本書
本書中的許多設計原則比較抽象,不結合實際程式碼,可能很難體會。範例既要足夠小,能放進書中,又要足夠大,能說明真實系統中的問題,這樣的例子並不好找(如果遇到好的範例,請發給我)。因此,僅靠閱讀本書,可能還不足以學會應用這些原則。
使用本書的最佳方式是結合程式碼審查。閱讀別人的程式碼時,想一想它是否符合本書討論的理念,這又如何影響程式碼的複雜性。相比自己的程式碼,別人的程式碼中有哪些設計問題,往往更容易看出來。你可以藉助書中的危險訊號發現問題並提出改進建議。程式碼審查也能讓你接觸新的設計方法和程式設計技術。
提高設計能力的最佳方法之一,是學會識別危險訊號:它們表明一段程式碼可能存在不必要的複雜性。本書會圍繞各個主要設計問題指出相應的危險訊號,並在書末彙總最重要的一些。編碼時可以利用這些訊號:一旦發現,就停下來尋找能消除問題的其他設計。剛開始嘗試時,你可能需要比較幾個方案,才能找到消除危險訊號的辦法。不要輕易放棄:在解決問題之前嘗試的方案越多,學到的也越多。漸漸地,你會發現程式碼中的危險訊號越來越少,設計也越來越整潔。你還會從自己的經驗中發現其他有助於識別設計問題的危險訊號(我很樂意聽到這些)。
應用本書中的思想時,務必把握分寸、審慎判斷。每條規則都有例外,每項原則都有侷限。把任何設計理念推向極端,都可能適得其反。好的設計體現了不同思想和方法之間的平衡。書中若干章設有“做過頭了”一節,介紹如何識別自己是否把一種好做法用過了頭。
本書幾乎所有範例都使用 Java 或 C++ 編寫,大部分討論也圍繞如何在物件導向語言中設計類展開。但這些思想同樣適用於其他領域。幾乎所有關於方法的思想,都可以用於沒有物件導向特性的語言中的函式,例如 C 語言的函式。這些設計思想也適用於類以外的模組,例如子系統或網路服務。
在這種背景下,讓我們詳細討論導致複雜性的原因以及如何簡化軟體系統。