極客時間-左耳聽風-程序員攻略-軟件設計
程序員練級攻略:軟件設計
編程範式
學習編程範式可以讓你明白編程的本質和各種語言的編程方式。因此,我推薦以下一些資料,以幫助你系統化地學習和理解。
- 極客時間的《編程範式遊記》系列文章,目錄如下。
- 編程範式遊記(1)- 起源
- 編程範式遊記(2)- 泛型編程
- 編程範式遊記(3)- 類型系統和泛型的本質
- 編程範式遊記(4)- 函數式編程
- 編程範式遊記(5)- 修飾器模式
- 編程範式遊記(6)- 面向對象編程
- 編程範式遊記(7)- 基於原型的編程範式
- 編程範式遊記(8)- Go 語言的委托模式
- 編程範式遊記(9)- 編程的本質
- 編程範式遊記(10)- 邏輯編程範式
- 編程範式遊記(11)- 程序世界裏的編程範式
- Wikipedia: Programming paradigm ,維基百科上有一個編程範式的頁面,順著這個頁面看下去,你可以看到很多很多有用的和編程相關的知識。
- Six programming paradigms that will change how you think about coding,中文翻譯版為 六個編程範型將改變你對編程的看法。這篇文章講了默認支持並發(Concurrent by default)、依賴類型(Dependent types)、連接性語言(Concatenative languages)、聲明式編程(Declarative programming)、符號式編程(Symbolic programming)、基於知識的編程(Knowledge-based programming)等六種不太常見的編程範式,並結合了一些你沒怎麽聽說過的語言來分別進行講述。
- Programming Paradigms for Dummies: What Every Programmer Should Know ,通過這篇文章能學到不少在設計編程語言時要考慮的問題,讓你重新審視自己所使用的編程語言應該怎樣用才能用好,有什麽局限性,這些局限性能否被克服等。
- 斯坦福大學公開課:編程範式,這是一門比較基礎且很詳細的課程,適合學習編程語言的初學者。它通過講述 C、C++、並發編程、Scheme、Python 這 5 門語言,介紹了它們各自不同的編程範式。以 C 語言為例,它解釋了 C 語言的基本要素,如指針、內存分配、堆、C 風格的字符串等,並解釋了為什麽 C 語言會在泛型編程、多態等方面有局限性。
一些軟件設計的相關原則
Don’t Repeat Yourself (DRY) ,DRY 是一個最簡單的法則,也是最容易被理解的。但它也可能是最難被應用的(因為要做到這樣,我們需要在泛型設計上做相當的努力,這並不是一件容易的事)。它意味著,當在兩個或多個地方發現一些相似代碼的時候,我們需要把它們的共性抽象出來形成一個唯一的新方法,並且改變現有地方的代碼讓它們以一些合適的參數調用這個新的方法。
Keep It Simple, Stupid(KISS) ,KISS 原則在設計上可能最被推崇,在家裝設計、界面設計和操作設計上,復雜的東西越來越被眾人所鄙視了,而簡單的東西越來越被人所認可。宜家(IKEA)簡約、高效的家居設計和生產思路;微軟(Microsoft)“所見即所得”的理念;谷歌(Google)簡約、直接的商業風格,無一例外地遵循了“KISS”原則。也正是“KISS”原則,成就了這些看似神奇的商業經典。而蘋果公司的 iPhone 和 iPad 將這個原則實踐到了極至。
Program to an interface, not an implementation,這是設計模式中最根本的哲學,註重接口,而不是實現,依賴接口,而不是實現。接口是抽象是穩定的,實現則是多種多樣的。在面向對象的 S.O.L.I.D 原則中會提到我們的依賴倒置原則,就是這個原則的另一種樣子。還有一條原則叫 Composition over inheritance(喜歡組合而不是繼承),這兩條是那 23 個經典設計模式中的設計原則。
You Ain’t Gonna Need It (YAGNI) ,這個原則簡而言之為——只考慮和設計必須的功能,避免過度設計。只實現目前需要的功能,在以後你需要更多功能時,可以再進行添加。如無必要,勿增復雜性。軟件開發是一場 trade-off 的博弈。
Law of Demeter,迪米特法則 (Law of Demeter),又稱“最少知識原則”(Principle of Least Knowledge),其來源於 1987 年荷蘭大學的一個叫做 Demeter 的項目。克雷格·拉爾曼(Craig Larman)把 Law of Demeter 又稱作“不要和陌生人說話”。在《程序員修煉之道》中講 LoD 的那一章將其叫作“解耦合與迪米特法則”。
關於迪米特法則有一些很形象的比喻:1) 如果你想讓你的狗跑的話,你會對狗狗說還是對四條狗腿說?2) 如果你去店裏買東西,你會把錢交給店員,還是會把錢包交給店員讓他自己拿?和狗的四肢說話?讓店員自己從錢包裏拿錢?這聽起來有點兒荒唐,不過在我們的代碼裏這幾乎是見怪不怪的事情了。對於 LoD,正式的表述如下:
對於對象 ‘O’ 中一個方法’M’,M 應該只能夠訪問以下對象中的方法:
- 對象 O;
- 與 O 直接相關的 Component Object;
- 由方法 M 創建或者實例化的對象;
- 作為方法 M 的參數的對象。
面向對象的 S.O.L.I.D 原則:
- SRP(Single Responsibility Principle)- 職責單一原則。關於單一職責原則,其核心的思想是:一個類,只做一件事,並把這件事做好,其只有一個引起它變化的原因。單一職責原則可以看作是低耦合、高內聚在面向對象原則上的引申,將職責定義為引起變化的原因,以提高內聚性來減少引起變化的原因。
職責過多,可能引起它變化的原因就越多,這將導致職責依賴,相互之間就產生影響,從而極大地損傷其內聚性和耦合度。單一職責,通常意味著單一的功能,因此不要為一個模塊實現過多的功能點,以保證實體只有一個引起它變化的原因。
- OCP(Open/Closed Principle)- 開閉原則。關於開發封閉原則,其核心的思想是:模塊是可擴展的,而不可修改的。也就是說,對擴展是開放的,而對修改是封閉的。對擴展開放,意味著有新的需求或變化時,可以對現有代碼進行擴展,以適應新的情況。對修改封閉,意味著類一旦設計完成,就可以獨立完成其工作,而不要對類進行任何修改。
- LSP(Liskov substitution principle)- 裏氏代換原則。軟件工程大師羅伯特·馬丁(Robert C. Martin)把裏氏代換原則最終簡化為一句話:“Subtypes must be substitutable for their base types”。也就是,子類必須能夠替換成它們的基類。即子類應該可以替換任何基類能夠出現的地方,並且經過替換以後,代碼還能正常工作。另外,不應該在代碼中出現 if/else 之類對子類類型進行判斷的條件。裏氏替換原則 LSP 是使代碼符合開閉原則的一個重要保證。正是由於子類型的可替換性才使得父類型的模塊在無需修改的情況下就可以擴展。
- ISP(Interface Segregation Principle )- 接口隔離原則。接口隔離原則的意思是把功能實現在接口中,而不是類中,使用多個專門的接口比使用單一的總接口要好。舉個例子,我們對電腦有不同的使用方式,比如:寫作、通訊、看電影、打遊戲、上網、編程、計算和數據存儲等。
如果我們把這些功能都聲明在電腦的抽象類裏面,那麽,我們的上網本、PC 機、服務器和筆記本的實現類都要實現所有的這些接口,這就顯得太復雜了。所以,我們可以把這些功能接口隔離開來,如工作學習接口、編程開發接口、上網娛樂接口、計算和數據服務接口,這樣,我們的不同功能的電腦就可以有所選擇地繼承這些接口。
- DIP(Dependency Inversion Principle)- 依賴倒置原則。高層模塊不應該依賴於低層模塊的實現,而是依賴於高層抽象。舉個例子,墻面的開關不應該依賴於電燈的開關實現,而是應該依賴於一個抽象的開關的標準接口。這樣,當我們擴展程序的時候,開關同樣可以控制其它不同的燈,甚至不同的電器。也就是說,電燈和其它電器繼承並實現我們的標準開關接口,而開關廠商就可以不需要關於其要控制什麽樣的設備,只需要關心那個標準的開關標準。這就是依賴倒置原則。
CCP(Common Closure Principle) - 共同封閉原則,一個包中所有的類應該對同一種類型的變化關閉。一個變化影響一個包,便影響了包中所有的類。一個更簡短的說法是:一起修改的類,應該組合在一起(同一個包裏)。如果必須修改應用程序裏的代碼,那麽我們希望所有的修改都發生在一個包裏(修改關閉),而不是遍布在很多包裏。
CCP 原則就是把因為某個同樣的原因而需要修改的所有類組合進一個包裏。如果兩個類從物理上或者從概念上聯系得非常緊密,它們通常一起發生改變,那麽它們應該屬於同一個包。CCP 延伸了開閉原則(OCP)的“關閉”概念,當因為某個原因需要修改時,把需要修改的範圍限制在一個最小範圍內的包裏。
CRP(Common Reuse Principle)- 共同重用原則 ,包的所有類被一起重用。如果你重用了其中的一個類,就重用全部。換個說法是,沒有被一起重用的類不應該組合在一起。CRP 原則幫助我們決定哪些類應該被放到同一個包裏。依賴一個包就是依賴這個包所包含的一切。
當一個包發生了改變,並發布新的版本,使用這個包的所有用戶都必須在新的包環境下驗證他們的工作,即使被他們使用的部分沒有發生任何改變。因為如果包中包含未被使用的類,即使用戶不關心該類是否改變,但用戶還是不得不升級該包並對原來的功能加以重新測試。CCP 則讓系統的維護者受益。CCP 讓包盡可能大(CCP 原則加入功能相關的類),CRP 則讓包盡可能小(CRP 原則剔除不使用的類)。它們的出發點不一樣,但不相互沖突。
好萊塢原則 - Hollywood Principle ,好萊塢原則就是一句話——“don’t call us, we’ll call you.”。意思是,好萊塢的經紀人不希望你去聯系他們,而是他們會在需要的時候來聯系你。也就是說,所有的組件都是被動的,所有的組件初始化和調用都由容器負責。
簡單來講,就是由容器控制程序之間的關系,而非傳統實現中,由程序代碼直接操控。這也就是所謂“控制反轉”的概念所在:1) 不創建對象,而是描述創建對象的方式。2)在代碼中,對象與服務沒有直接聯系,而是容器負責將這些聯系在一起。控制權由應用代碼中轉到了外部容器,控制權的轉移,是所謂反轉。好萊塢原則就是IoC(Inversion of Control) 或DI(Dependency Injection)的基礎原則。
高內聚, 低耦合 & - High Cohesion & Low/Loose coupling,這個原則是 UNIX 操作系統設計的經典原則,把模塊間的耦合降到最低,而努力讓一個模塊做到精益求精。內聚,指一個模塊內各個元素彼此結合的緊密程度;耦合指一個軟件結構內不同模塊之間互連程度的度量。內聚意味著重用和獨立,耦合意味著多米諾效應牽一發動全身。對於面向對象來說,你也可以看看馬薩諸塞州戈登學院的面向對象課中的這一節講義High Cohesion and Low Coupling。
CoC(Convention over Configuration)- 慣例優於配置原則 ,簡單點說,就是將一些公認的配置方式和信息作為內部缺省的規則來使用。例如,Hibernate 的映射文件,如果約定字段名和類屬性一致的話,基本上就可以不要這個配置文件了。你的應用只需要指定不 convention 的信息即可,從而減少了大量 convention 而又不得不花時間和精力啰裏啰嗦的東東。
配置文件在很多時候相當影響開發效率。Rails 中很少有配置文件(但不是沒有,數據庫連接就是一個配置文件)。Rails 的 fans 號稱其開發效率是 Java 開發的 10 倍,估計就是這個原因。Maven 也使用了 CoC 原則,當你執行
mvn -compile
命令的時候,不需要指定源文件放在什麽地方,而編譯以後的 class 文件放置在什麽地方也沒有指定,這就是 CoC 原則。SoC (Separation of Concerns) - 關註點分離 ,SoC 是計算機科學中最重要的努力目標之一。這個原則,就是在軟件開發中,通過各種手段,將問題的各個關註點分開。如果一個問題能分解為獨立且較小的問題,就是相對較易解決的。問題太過於復雜,要解決問題需要關註的點太多,而程序員的能力是有限的,不能同時關註於問題的各個方面。
正如程序員的記憶力相對於計算機知識來說那麽有限一樣,程序員解決問題的能力相對於要解決的問題的復雜性也是一樣的非常有限。在我們分析問題的時候,如果我們把所有的東西混在一起討論,那麽就只會有一個結果——亂。實現關註點分離的方法主要有兩種,一種是標準化,另一種是抽象與包裝。標準化就是制定一套標準,讓使用者都遵守它,將人們的行為統一起來,這樣使用標準的人就不用擔心別人會有很多種不同的實現,使自己的程序不能和別人的配合。
就像是開發鏍絲釘的人只專註於開發鏍絲釘就行了,而不用關註鏍帽是怎麽生產的,反正鏍帽和鏍絲釘按照標準來就一定能合得上。不斷地把程序的某些部分抽象並包裝起來,也是實現關註點分離的好方法。一旦一個函數被抽象出來並實現了,那麽使用函數的人就不用關心這個函數是如何實現的。同樣的,一旦一個類被抽象並實現了,類的使用者也不用再關註於這個類的內部是如何實現的。諸如組件、分層、面向服務等這些概念都是在不同的層次上做抽象和包裝,以使得使用者不用關心它的內部實現細節。
DbC(Design by Contract)- 契約式設計 ,DbC 的核心思想是對軟件系統中的元素之間相互合作以及“責任”與“義務”的比喻。這種比喻從商業活動中“客戶”與“供應商”達成“契約”而得來。如果在程序設計中一個模塊提供了某種功能,那麽它要:
- 期望所有調用它的客戶模塊都保證一定的進入條件:這就是模塊的先驗條件(客戶的義務和供應商的權利,這樣它就不用去處理不滿足先驗條件的情況)。
- 保證退出時給出特定的屬性:這就是模塊的後驗條件(供應商的義務,顯然也是客戶的權利)。
- 在進入時假定,並在退出時保持一些特定的屬性:不變式。
ADP(Acyclic Dependencies Principle)- 無環依賴原則 ,包(或服務)之間的依賴結構必須是一個直接的無環圖形,也就是說,在依賴結構中不允許出現環(循環依賴)。如果包的依賴形成了環狀結構,怎麽樣打破這種循環依賴呢?
有兩種方法可以打破這種循環依賴關系:第一種方法是創建新的包,如果 A、B、C 形成環路依賴,那麽把這些共同類抽出來放在一個新的包 D 裏。這樣就把 C 依賴 A 變成了 C 依賴 D 以及 A 依賴 D,從而打破了循環依賴關系。第二種方法是使用 DIP(依賴倒置原則)和 ISP(接口分隔原則)設計原則。無環依賴原則(ADP)為我們解決包之間的關系耦合問題。在設計模塊時,不能有循環依賴。
一些軟件設計的讀物
- 《領域驅動設計》 ,本書是領域驅動設計方面的經典之作。全書圍繞著設計和開發實踐,結合若幹真實的項目案例,向讀者闡述如何在真實的軟件開發中應用領域驅動設計。書中給出了領域驅動設計的系統化方法,並將人們普遍接受的一些實踐綜合到一起,融入了作者的見解和經驗,展現了一些可擴展的設計新實踐、已驗證過的技術以及便於應對復雜領域的軟件項目開發的基本原則。
- 《UNIX 編程藝術》 ,這本書主要介紹了 Unix 系統領域中的設計和開發哲學、思想文化體系、原則與經驗,由公認的 Unix 編程大師、開源運動領袖人物之一埃裏克·雷蒙德(Eric S. Raymond)傾力多年寫作而成。包括 Unix 設計者在內的多位領域專家也為本書貢獻了寶貴的內容。本書內容涉及社群文化、軟件開發設計與實現,覆蓋面廣、內容深邃,完全展現了作者極其深厚的經驗積累和領域智慧。
- 《Clean Architecture》,《Clean Code》 和 《The Clean Coder》,《Clean Architecture》。對軟件架構的元素、方法等講得很清楚。示例都比較簡單,並帶一些軟件變化歷史的講述,很開闊視野。
- The Twelve-Factor App ,如今,軟件通常會作為一種服務來交付,它們被稱為網絡應用程序,或軟件即服務(SaaS)。12-Factor 為構建 SaaS 應用提供了方法論,這也是架構師必讀的文章。(中譯版) 這篇文章在業內的影響力很大,必讀!
- Avoid Over Engineering ,這篇文章是一篇非常不錯地告訴你什麽是過度設計的文章。
- Instagram Engineering’s 3 rules to a scalable cloud application architecture ,Instagram 工程的三個黃金法則:1)使用穩定可靠的技術(迎接新的技術);2)不要重新發明輪子;3)Keep it very simple。我覺得這三條很不錯。其實,Amazon 也有兩條工程法則,一個是自動化,一個是簡化。
- How To Design A Good API and Why it Matters - Joshua Bloch ,Google 的一個分享,關於如何設計好一個 API。
- 關於 Restful API 的設計,你可以學習並借鑒一下下面這些文章。
- Best Practices for Designing a Pragmatic RESTful API
- Ideal REST API design
- HTTP API Design Guide
- Microsoft REST API Guidelines
- IBM Watson REST API Guidelines
- Zalando RESTful API and Event Scheme Guidelines
- The Problem With Logging ,一篇關於程序打日誌的短文,可以讓你知道一些可能以往不知道的打日誌需要註意的問題。
- Concurrent Programming for Scalable Web Architectures ,這是一本在線的免費書,教你如何架構一個可擴展的高性能的網站。其中談到了一些不錯的設計方法和知識。
極客時間-左耳聽風-程序員攻略-軟件設計