第 1 章 介紹
編寫計算機軟體是人類歷史上最純粹的創作活動之一。程式設計師不受諸如物理定律等實際限制的約束。我們可以用現實世界中永遠不會存在的行為建立令人興奮的虛擬世界。程式設計不需要很高的身體技能或協調能力,例如芭蕾或籃球。所有程式設計都需要具有創造力的頭腦和組織思想的能力。如果您能夠將一個系統具象化,就可以在計算機程式中將它實現。
這意味著編寫軟體的最大限制是我們理解所建立系統的能力。隨著程式的演進和更多功能的加入,它變得越來越複雜,其元件之間會具有微妙的依賴性。隨著時間的流逝,複雜性不斷累積,程式設計師在修改系統時將所有相關因素牢記在心中變得越來越難。這會減慢開發速度並導致程式碼缺陷,從而進一步拖慢開發速度並增加成本。在任何程式的生命週期中,複雜性都會不可避免地增加。程式越大,工作的人越多,管理複雜性就越困難。
好的開發工具可以幫助我們應對複雜性,許多出色的工具已經在過去的幾十年中被創建出來。但是,僅憑工具我們能做的事情仍然有限制。如果我們想簡化編寫軟體的過程,從而可以用更低的成本構建功能更強大的系統,則必須找到簡化軟體的方法。儘管我們盡了最大努力,複雜性仍會隨著時間的推移而增加,但是更簡單的設計使我們能夠在複雜性取得壓倒性優勢之前構建出更大、功能也更強大的系統。
有兩種解決複雜性的通用方法,這兩種方法都將在本書中進行討論。第一種方法是透過使程式碼更簡單和更易理解來消除複雜性。例如,可以透過消除特殊情況或以一致的方式使用識別符號來降低複雜性。
解決複雜性的第二種方法是封裝它,以便程式設計師可以在系統上工作而不會立即暴露在所有複雜性的面前。這種方法稱為模組化設計。在模組化設計中,軟體系統分為模組,例如面嚮物件語言中的類。這些模組被設計為彼此相對獨立(低耦合),以便程式設計師可以在一個模組上工作而不必瞭解其他模組的細節。
由於軟體具有很好的延展性,軟體設計是一個貫穿軟體系統整個生命週期的連續過程。這使得軟體設計與諸如建築物、船舶或橋樑的物理系統的設計不同。但是,軟體設計並非總是以這種方式被看待。在程式設計歷史的早期階段,設計往往都集中在專案的開始,就像其他工程學科一樣。這種方法的極端稱為瀑布式模型,該模型將專案劃分為離散的階段,例如需求定義、設計、編碼、測試和維護。在瀑布式模型中,每個階段都在下一階段開始之前完成;在許多情況下,每個階段都由不同的人負責。在設計階段,立即設計整個系統。在設計階段結束時凍結設計,而後續階段的作用是充實和實現這個設計。
不幸的是,瀑布式模型很少適用於軟體。軟體系統本質上比物理系統更為複雜。在構建任何東西之前,不可能充分具象化出大型軟體系統的設計,以瞭解其所有含義。結果,初始設計將有許多問題。在實現到一定程度之前,問題不會變得明顯。但是,瀑布式模型此時無法適應主要的設計變更(例如,設計師可能已轉移到其他專案)。因此,開發人員嘗試在不改變整體設計的情況下解決問題。這導致了複雜性的爆炸式增長。
由於這些問題,當今大多數軟體開發專案都使用諸如敏捷開發之類的增量方法,其中初始設計僅著重於整體功能的一小部分。這一小部分將被設計、實現和評估,然後發現和糾正原始設計中的問題,然後再設計、實現和評估更多功能。每次迭代都會暴露現有設計的問題,這些問題在設計下一組功能之前就已得到解決。透過以這種方式擴充套件設計,可以在系統仍然很小的情況下解決掉初始設計的問題。較新的功能受益於較早的功能在實現過程中獲得的經驗,因此問題也會較少。
增量方法適用於軟體,因為軟體具有足夠的延展性,可以在實施過程中進行重大的設計變更。相比之下,對物理系統而言,主要的設計變更更具挑戰性:例如,在建築過程中更改支撐橋樑的塔架數量是不切實際的。
增量開發意味著永遠不會完成軟體設計。設計在系統的整個生命週期中不斷發生:開發人員應始終在思考設計問題。增量開發還意味著不斷的重新設計。系統或元件的初始設計幾乎從來都不是最好的。隨著經驗累積,不可避免地會產生更好的做事方式。作為軟體開發人員,您應該始終在尋找機會來改進正在開發的系統的設計,並且應該計劃將部分時間花費在設計改進上。
如果軟體開發人員應始終考慮設計問題,而降低複雜性是軟體設計中最重要的要素,則軟體開發人員應始終考慮複雜性。這本書就是關於如何使用複雜性來指導軟體設計的整個生命週期。
這本書有兩個總體目標。首先是描述軟體複雜性的本質:“複雜性”是什麼意思、為什麼它很重要、以及當程式具有不必要的複雜性時如何識別?本書的第二個也是更具挑戰性的目標是介紹可在軟體開發過程中使用的技術,以最大程度地減少複雜性。不幸的是,沒有簡單的方法可以保證出色的軟體設計。取而代之的是,我將提出一些與哲學相關的更高層級的概念,例如“類應該是深的”或“透過定義來規避錯誤”。這些概念可能不會立即確定最佳設計,但您可以使用它們來比較設計備選方案並引導您探索設計空間。
1.1 如何使用這本書
這裡描述的許多設計原則有些抽象,因此如果不看實際的程式碼,可能很難理解它們。找到足夠小的示例以包含在書中,但是又足夠大以說明真實系統的問題是一個挑戰(如果遇到好的示例,請發給我)。因此,這本書可能不足以讓您學習如何應用這些原則。
使用本書的最佳方法是與程式碼審查結合使用。閱讀其他人的程式碼時,請考慮它是否符合此處討論的概念,以及它與程式碼的複雜性之間的關係。在別人的程式碼中比在您的程式碼中更容易看到設計問題。您可以使用書裡描述的危險訊號來發現問題並提出改進建議。審查程式碼還將使您接觸到新的設計方法和程式設計技術。
改善設計技能的最好方法之一就是學會識別危險訊號:危險訊號表明一段程式碼可能比需要的複雜。在本書的過程中,我將指出一些危險訊號,這些危險訊號與一些主要的設計問題相關,其中最重要的內容總結在本書的最後。您可以在編碼時使用它們:當看到危險訊號時,停下來尋找可消除問題的替代設計。當您第一次嘗試這種方法時,您可能必須嘗試幾種設計替代方案,然後才能找到消除危險訊號的方案。不要輕易放棄:解決問題之前嘗試的替代方法越多,您就會學到更多。隨著時間的流逝,您會發現程式碼中的危險訊號越來越少,並且您的設計越來越清晰。您自己的經驗可能也涉及到一些其它的可用於識別設計問題的危險訊號(我很樂意聽到這些)。
在應用本書中的思想時,務必要節制和謹慎。每條規則都有例外,每條原則都有其侷限性。如果您將任何設計創意都發揮到極致,那麼您可能會陷入困境。精美的設計反映了相互競爭的思想和方法之間的平衡。有幾個章節的標題為“做過頭了”,它們描述瞭如何識別自己是否正在把事情做得過頭了。
本書中幾乎所有示例都是使用 Java 或 C++ 語言編寫的,並且大部分討論都是針對以面向物件的語言設計類的。但是,這些想法也適用於其他領域。幾乎所有與方法有關的思想也可以應用於沒有面向物件特性的語言中的功能,例如 C 語言。設計思想還適用於除類之外的模組,例如子系統或網路服務。
在這種背景下,讓我們詳細討論導致複雜性的原因以及如何簡化軟體系統。