今天一位同事在斯坦福的博士生導師John Ousterhout (注,Tcl語言的設計者)來公司做了他的新書《A Philosophy of Software Design》的演講,介紹了他對於軟件設計的思考。這裏我把本書的讀書筆記和心得分享給大家,歡迎大家來和我交流探討。
大家也可以去看作者在google演講時的視頻和他演講的slides
複雜性的本質
軟件設計應該簡單,避免複雜,關於複雜性的定義,作者認爲主要有兩個量度
- 系統是不是難以理解
- 系統是不是難以修改
關於複雜性的症狀:
- 當新增特性時,需要修改大量的代碼
- 當需要完成一個功能時,開發人員需要了解許多知識
- 當新增/修改功能時,不能明顯的知道要修改那些代碼
引起複雜性的原因:依賴和晦澀。
最後,複雜性不是突然出現的,它是隨着時間和系統的演進逐漸增加的。
我的解讀:這本書講的是軟件設計的哲學,哲學要解決的是最根本的問題。作者認爲軟件設計要解決的最根本的問題就是避免複雜性,依賴和晦澀是造成軟件負責的主要原因。依賴很多時候是無法避免的,但是應該儘可能的減少依賴,去除不必要的依賴。軟件設計應該容易理解,晦澀是引起複雜性增加的另一個原因。這個核心觀點是這本書的主旨,借用老愛的話“Simple,but not simpler!”
我曾經就職某存儲巨頭,其中有一塊代碼因爲是收購的產品,代碼已經非常陳舊了,因爲沒有人能看懂,所以也就沒有人敢修改。你看,這個產品不是也賣的挺好的。
僅僅可工作的代碼還遠遠不夠
在第二章,作者提出了“戰術性編程”和“戰略性編程”的對立。
“戰術性編程”最求以最快的速度完成可工作的功能。這看上去無可厚非。但是這種行爲往往會增加系統的複雜性。引發大量的技術債。可以說這種做法以犧牲長遠利益來獲得眼前的利益。
“戰略性編程”不僅僅要求可工作的代碼,以好的設計爲重,未來的功能投資,認爲現階段在設計上的投入會在將來獲得回報。
好的設計是有代價的,問題是你願意投入多少?
我的解讀:很有趣的是,我司之前的產品的負責人在公司推行大規模的敏捷(LeSS),當時有一個顧問給我們上課,他也說設計要儘可能簡單,但是不要爲了未來做設計。以最小的代價實現可用的功能。以John的觀點,這樣做無疑會增加系統變複雜的可能性。我比較認同John這裏的觀點,好的設計是有價值的,投入在軟件設計上的,對功能毫無影響的東西,是有價值的。但是如何取捨和權衡,投入多少是需要開發團隊達成共識。 軟件有它的生命週期,爲了未來的投入也不是越多越好。
模塊要有深度
深度其實是對模塊封裝的度量,模塊應該提供儘可能簡單的接口和儘可能強大的功能。這樣的模塊稱之爲深度模塊。
我的解讀:這一部分沒有什麼新東西,傳統的面向對象和如今的微服務架構都是對這一哲學的應用。好的封裝可以減少依賴,簡單的接口可以避免晦澀。也就是減少了複雜性。
信息的隱藏和泄漏
關於信息的隱藏和泄漏,這一部分對於熟悉面向對象的猿們來說不是新東西。基於SOLID,這就是Open,軟件應該是對於擴展開放的,但是對於修改封閉的。信息隱藏使得修改變的封閉。
具有通用功能的模塊更具深度
更通用功能的接口意味着更高層級的抽象,隱藏更多的實現細節,按照John的觀點,也就更具深度。那麼如何在通用接口和特殊接口之間做權衡呢?
- 能夠實現所需功能的最簡單接口是什麼?
- 該接口會被用於那些不同場景?
- 該接口對於我的當前是否容易使用?
我的解讀:通用的接口和之前的“戰略性編程”是一致的,更通用的接口在面對未來可能發生的需求變化的時候,更容易使用。這裏的藝術在於能夠找到需求到軟件接口之間的最佳映射。抽象到哪一個層級,是主要問題。
不同的層,不同的抽象
軟件系統通常有不同的層次組成,每一層都通過和它之上和之下的層的接口來交互。每一層都具有自己不同的抽象。例如典型的數據庫,服務器和客戶端模型中,數據庫層的抽象是數據表和關係,服務器層是應用對象和應用邏輯而客戶端的抽象是用戶接口視圖和交互。如果你發現不同的層具有相同的抽象,那也許你的分層有問題。
把複雜性向下移
在軟件分層的鄙視鏈中,最高層是用戶,接着的一層的UI工程師,然後是後臺工程師,數據庫工程師,等等。用戶是上帝不能得罪,如果一定要在某個層次處理複雜性,那麼這個層次越低越好,反正苦逼程序員也不會抱怨,對得,就是這個道理。
合併還是分離
“天下大事,分久必合,合久必分”。軟件設計中經常要問的問題就是這兩個功能模塊是合併好,還是分開好?不論是合併還是分離,目標都是降低複雜性,那麼把系統分離成更多的小的單元模塊,每一個模塊都更簡單,系統的複雜性會降低麼?答案是不一定:
- 複雜性可能來源於系統模塊的數量
- 更多的模塊也許意味着需要額外的代碼來管理和協調
- 更多的模塊可能帶來許多依賴
- 更多的模塊可能帶來重複的代碼,而重複的代碼是惡魔
在以下的情況下,需要考慮合併:
- 模塊之間共享信息
- 合併後的接口更簡單
- 合併後減少了重複的代碼
確保錯誤終結
異常和錯誤處理是造成軟件複雜的罪魁禍首之一。程序員往往錯誤的認爲處理和上報越多的錯誤,就越好。這也就導致了過度防禦性的編程。而很多時候,程序員捕獲了異常並不知道該如何處理,乾脆往上層扔,這就違背了封裝原則。
用戶一臉懵逼,“你叫我幹啥?”
降低複雜度的一個原則就是儘可能減少需要處理的異常可能性。而最佳實踐就是確保錯誤終結,例如刪除一個並不存在的文件,與其上報文件不存在的異常,不如什麼都不做。確保文件不存在就好了,上層邏輯不但不會被影響,還會因爲不需要處理額外的異常而變得簡單。
今天就先分享到這裏,後面有空,我會繼續分享本書的後半部分,祝大家開學愉快!