組合思維
分層思維
工程思維
對象思維
疊代思維

Unix 作業系統誕生于 20 世紀 60 年代,經過幾十年的發展,技術日臻成熟。在這個過程中,Unix 獨特的設計哲學和美學也深深地吸引了一大批技術開發人員,他們在維護和使用 Unix 的同時,Unix 也影響了他們的思考方式和看待世界的角度。
Unix 哲學是一套基于 Unix 作業系統頂級開發者們的經驗所提出的軟體開發的準則和理念。
也就是說,Unix 哲學并不是正統的計算機科學理論,它的形成更多是以經驗為基礎。你一定聽說過子產品化、解耦、高内聚低耦合這些設計原則,還有類似開源軟體和開源社群文化,這些最早都是起源于 Unix 哲學。可以說 Unix 哲學是過去幾十年裡對軟體行業影響意義最深遠的程式設計文化。
Unix 設計哲學,主張組合設計,而不是單體設計;主張使用集體智慧,而不是某個人的特殊智慧。
對程式設計的啟示:
啟示一:保持簡單清晰性,能提升代碼品質
代碼之間的互相影響越多,軟體越複雜。比如,A 依賴 B,B 依賴 C……一直這樣循環下去,程式就會變得非常複雜,也就是我們程式設計中常說的,如果一個類檔案寫了上萬行代碼,那麼代碼邏輯将會非常難了解。
軟體複雜度一般有以下三個來源。
代碼庫規模。
這個就與開發工具、程式設計語言等有關了,不過需要注意,代碼行數與複雜度并不呈正相關。比如,Java 語言編寫的庫通常會比 C++ 的庫的代碼行數更多(語言特性決定),但不能說 Java 類庫就一定比 C++ 的類庫更複雜。
技術複雜度。
這個指的是不同的程式設計語言、編譯器、伺服器架構、作業系統等能夠被開發人員了解的難易程度。比如,Netty 庫,對于很多 Java 程式員來說,了解起來就有一定的難度,這就是有一定的技術複雜度。
實作複雜度。
不同的程式設計人員,對于需求的了解不同,在程式設計時就會有截然不同的編寫風格,比如,前端程式員和後端程式員網頁分頁的代碼實作風格就會明顯不同。
該如何降低軟體複雜度呢?
首先,在代碼庫規模方面,可以通過減少寫死來控制代碼量。
比如,使用設計模式中的政策模式來替換大量的 if-else 語句,使用通用工具類來減少重複的方法調用。除此之外,還可以利用語言特性來減少代碼量,比如,在 Java 8 中使用 lambda 表達式來精簡語句。
其次,對于技術複雜度來說,要想在整體上保持簡單性,需要在設計時就做好技術選型。
換句話說,好的技術選型能夠有效控制元件引入技術複雜度的風險。比如,在做系統設計時,引入像 Kafka 這樣的消息中間件之前,你需要從系統吞吐量、響應時間要求、業務特性、維護成本等綜合次元評估技術複雜度,如果你的系統并不需要複雜的消息中間件,那麼就不要引入它,因為一旦引入後,就會面臨指派人員學習與維護、出現故障後還要能及時修複等問題。
最後,就降低實作複雜度而言,可以使用統一的代碼規範。
比如,使用 Google 開源項目的編碼規範,裡面包含了命名規範、注釋格式、代碼格式等要求。這樣做的好處在于,能快速統一不同開發人員的程式設計風格,避免在維護代碼時耗費時間去适應不同的代碼風格。
是以,Unix 哲學中所說的保持簡單性,并不單單是做到更少的代碼量,更是在面對不同複雜度來源時也能始終保持簡單清晰的指導原則。
啟示二:借鑒組合理念,有效應對多變的需求
對于任何一個開發團隊來說,最怕遇見的問題莫過于:不停的需求變更導緻不停的代碼變更。
即便你花費了大量的時間,在項目前期做了詳細的需求分析和系統的分析設計,依然不能完全阻擋需求的變化,而一旦需求發生變更,那麼就意味着開發團隊需要加班加點地修改代碼。
事實上,Unix 在設計之初就已經遇見過這些問題,那它是怎麼解決的呢?下面我們就來看一下 Unix 那些能夠“任意組合”的例子。
所有的指令都可以使用管道來互動
這樣,所有指令間的互動都隻和 STD_IN、STD_OUT 裝置相關。于是,就可以使用管道來任意地拼裝不同的指令,以完成各式各樣的功能。
可以任意地替換程式
比如,我喜歡 zsh,你喜歡 bash,我們可以各自替換;你喜歡 awk,我不喜歡 awk,也可以替換為 gawk。快速切換到熟悉的程式,每個程式就像一個零件一樣,任意插拔。
自定義環境變量
比如,Java 編譯環境有很多版本,你可能用到的有版本 8、11 和 14,通過自定義 JAVA_HOME 環境變量,你就可以快速啟用不同的編譯環境。
這充分說明了 Unix 哲學的組合思維:把軟體設計成獨立元件并能随意地組合,才能真正應對更多變化的需求。
然而,在實際工作中,你很多時候可能都隻是在做“定制功能驅動”式的程式設計。比如,使用者需要一個“上傳檔案的紅色按鈕”,你就實作了一個叫“紅色上傳按鈕功能”的元件,過幾天變為需要一個“上傳檔案的綠色按鈕”時,你再修改代碼滿足要求……這不是組合設計,而是直接映射設計,看似使用者是需要“上傳”這個功能,但實際上使用者隐藏了對“不同顔色”的需求。
很多時候看上去我們是一直在設計不同的程式,實際上對于真正多變的需求,我們并沒有做到組合設計,隻是通過不斷地修改代碼來掩飾爛設計罷了。
要想做到組合設計,Unix 哲學其實給我們提供了兩個解決思路。
第一個是解耦
這是 Unix 哲學最核心的原則。代碼與代碼之間的依賴關系越多,程式就越複雜,隻有将大程式拆分成小程式,才能讓人容易了解它們彼此之間的關系。也就是我們常說的在設計時應盡量分離接口與實作,程式間應該耦合在某個規範與标準上,而不是耦合在具體代碼實作邏輯上。
第二個是子產品化
你可能已經非常熟悉這個詞語了,不過子產品化還有更深層的含義——可替換的一緻性。什麼叫可替換的一緻性?比如,你想使用 Java RPC 協定,可以選擇 Dubbo、gRPC 等架構,RPC 協定的本質是一樣的,就是遠端過程調用,但是實作的元件架構卻可以不同,對于使用者來說,隻要是支援 Java RPC 協定的架構就行,可随意替換,這是可替換。而不同的架構需要實作同一個功能(遠端過程調用)來保持功能的一緻性(Dubbo 和 gRPC 的功能是一緻的),這是一緻性。
實際上,這兩個解決思路就是現在我們常說的高内聚、低耦合原則:子產品内部盡量聚合以保持功能的一緻性,子產品外部盡量通過标準去耦合。
換句話說,就是提供機制而不是政策,就像上傳檔案那個例子裡,分析時應該找出使用者隐含的顔色變化的需求,并抽象出一個可以自定義顔色的功能子產品,解耦上傳檔案子產品,最後将顔色變化子產品組合到上傳檔案子產品來對外提供使用。這樣當使用者提出修改顔色時(修改政策),隻需要修改自定義顔色子產品就行了,而不是連同上傳檔案的機制也一起修改。
啟示三:重拾資料思維,重構優化程式設計
再高大上的架構設計,如果系統對資料的組織是混亂的,那麼可以輕松預見随着系統的演進,系統必然會變得越來越臃腫和不可控。
Unix 哲學在出現之初便提出了“資料驅動程式設計”這樣一個重要的程式設計理念。也就是說,在 Unix 的理念中,程式設計中重要的是資料結構,而不是算法。
當資料結構發生變化時,通常需要對應用程式代碼進行修改,比如,添加新資料庫字段、修改程式讀寫字段等。但在大多數應用程式中,代碼變更并不是立即完成的。原因有如下:
對于服務端應用程式而言,可能需要執行增量更新,将新版本部署到灰階環境,檢查新版本是否正常運作,然後再完成所有的節點部署;
對于用戶端應用程式來說,升不更新就要看使用者的心情了,有些使用者可能相當長一段時間裡都不會去更新軟體。
這就意味着新舊版本的代碼以及新舊資料格式可能會在系統中同時共存。這時,處理好資料的相容性就變得非常重要了。如果不具備資料思維,很可能會假設資料格式的變更不會影響代碼變更。
而 Unix 哲學提出的“資料驅動程式設計”會把代碼和代碼作用的資料結構分開,這樣在改變程式的邏輯時,就隻要編輯資料結構,而不需要修改代碼了。
軟體程式通常有兩個層面的需求:
功能性需求,簡單來說,就是一個程式能為使用者做些什麼,比如,檔案上傳、查詢資料等;
非功能性需求,這個是指除功能性需求以外的其他必要需求,比如,性能、安全性、容錯與恢複、本地化、國際化等。
事實上,非功能性需求所建構起來的正是我們所熟知的軟體架構。什麼是軟體架構?簡單來說,就是軟體的基本結構,包括三要素:代碼、代碼之間的關系和兩者各自的屬性。
如果把軟體比作一座高樓,那麼軟體架構就是那個鋼筋混凝土的架構,代碼就是那個架構裡的磚石,正是因為有了那個架構,才能讓每一個代碼都能很好地運作起來。
其中,最為經典的軟體架構就是分層架構, 分層架構越是流行,我們的設計越容易僵化。這背後到底有哪些值得我們深思的地方呢?
從架構角度來聊聊為什麼代碼要做分層、主要用于解決什麼問題,以及存在優勢和劣勢有哪些。