標籤:

讓代碼變立體

最近刻意回答了一組問題,用來支持本文的討論:

1. 編程中把變數以二進位存儲在文件中和變數在內存中的結構是一樣的么,C語言中的結構體2. 涉及位元組對齊的問題,存儲為二進位文件時還有這些問題么? - in nek 的回答

C語言中的 最受限制類型 和 最小的對齊限制 什麼意思?對象的對齊要求,是只有在結構或聯合中才有的嗎? - in nek 的回答

3. CPU檢測到中斷信號時,怎麼知道是發給哪個進程的? - in nek 的回答

4. Linux 中 mmap() 函數的內存映射問題理解? - in nek 的回答

其實這個也是這個問題:KVM他說的基於內核的虛擬機是什麼意思呢? - in nek 的回答

這些問題,全部都是名稱定義問題,人很容易被來自另一個名稱空間的東西影響了自己對本名稱空間的事情的判斷,這就是為什麼原題的題主和下面一些答案在問題上糾纏不清,不少人不注意每個定義的範圍,或者對名字的唯一性有不可動搖的依賴感,這樣的結果就是,名和名糾結在一起,然後你就看不到(名不出)其中的「道」了。

我解決這個問題的方法是,把平直的代碼「立」起來。上面我列出的回答中,我都嘗試先讓名字在一個簡單的語境中被解釋,然後談它的下一個變體,然後再談下一個變體……這樣,一個這樣的代碼:

可以變成這樣:

同一個代碼,從架構的視角中,就是完全不一樣的,我把這稱為,把實現「立」起來。這實際是實現過程的反向拆解,因為架構的生成過程就是這樣的。你一個內存訪問,本來就僅僅是在地址匯流排上發起一個讀寫請求,但加上多層的cache,語義就有一次變化,加上匯流排原來的基於時序的讀寫改為基於Burst的讀寫,語義又有一次變化,編譯器對讀寫過程進行合併,又是一次變化,CPU執行多發射,又是一次變化。你非要用一個邏輯把這些所有的變化描述出來,你完全沒有機會清楚地把這個邏輯表達出來。

軟體很多概念空間的化簡,都基於這種技術,比如分層,就是一個典型,Ethernet頭,不認識IP頭,IP頭,不認識TCP頭,這樣,每一層可以聚焦一個邏輯,把那層邏輯的優勢充分利用起來。這樣其實是影響系統的性能的,但這樣同時降低了熵增(參考前一篇《反者道之動》),留給了軟體更多生存下去的餘量。我前面的博文中舉了不少例子了,很多人在分層間加飛線來提升軟體的效率,這是一種「使用軟體」的方法,並非完全是一種開發軟體的方法。當然這兩者的界限不是那麼清楚就是了。

但分層是一種很簡單的立起來的方法,表達能力遠遠不夠。

前段時間有一種新的編程概念很流行,叫面向「角度」的編程。大概的意思是讓程序分不同的角度來寫,比如主流程的功能邏輯寫在一個模塊中,日誌和跟蹤邏輯寫在另一個模塊中,備份和恢復的邏輯又寫在一個模塊中。這其實更貼近人的思維,你看代碼的時候,其實也總希望看望懂了主流程,然後才開始以此為基礎看跟蹤功能怎麼做的,看備份功能怎麼做的。當然,我認為面向角度的編程現在發展得並不成功,需求是存在的,但如何展現為線性代碼的形式,是個非常困難的問題,現在我認為真正解決得比較好的是git,我們不要簡單把git類比為cvs一樣的版本管理工具,實際上git背後是Linux的版本管理策略,它要求每個Patch都是一個獨立成型的「角度」,說明的是一個增量的過程,所以這個Patch可以不是一個線性增長過程的一部分,而可以在一定程度上被拉到任何同源分支上的一組獨立描述。這時最接近面向角度編程原始訴求的實現了。

但這還遠遠不夠。

在編程語言可以解決這個問題前,現在真正能把代碼立起來的,就是架構設計文檔了。所以,這裡想討論的是構架文檔的寫作策略問題。我們已經看到了,真正能解構一個設計的是把代碼立起來,而軟體架構本身就是這樣發展的,比如我們前面舉的那個訪存的地址,又比如Linux的softirq,最初它的含義僅僅是bh,表示開中斷控制器到CPU中斷返回的一段中斷執行過程,後來變成了一組不可重入的多CPU中斷處理常式(理論上處理壓力可以被分解到其他CPU核上),後來又變成可以線程化,softirq就不再具有原來那個簡單的語義了,它是獨一無二的,softirq就是softirq,不看代碼誰都說不清它是什麼,而且這個代碼還不斷升級,沒有人承諾它一定會如何,但我們總是保證它基本上不會違背原先的承諾。

從這個角度來說,我們根本就不應該像代碼一樣維護構架文檔,讓它變成一個不斷被修改的對象,這樣得到的架構設計文檔就是平的,和我們想解構的代碼一樣。我們需要它是立體的,就需要把構架文檔分層,和分版本。就是說,我們就不應該認為構架設計文檔是和代碼一樣升級的,我們是通過寫新的增量設計文檔,來描述我們如何升級原來的設計,而之前版本構架設計,僅僅應該作為第一層模型來使用,並僅僅做一些Bugfixing的修改。

這將稱為在軟體飛速發展以後,構架設計的新常態。現在新的標準,比如ARM的GIC的標準,PICEv3的標準,已經都是這樣寫的了。而我們如果嘗試用一份設計文檔對應一份代碼這樣的形式組織我們的構架設計,最終只會把整個架構維護成一團亂麻。

這是一方面。

另一方面,從這個角度看這個問題,我們也更容易控制我們在當前版本的構架設計中應該寫什麼——它應該寫——為了滿足用戶核心需求,某些設計部件的最小承諾。

我們常常有人糾結於「架構設計應該寫到多細」,但如果從這個角度看,這個根本就不是問題。架構設計是架構師,根據所認知的核心需求(也應該表示在架構設計中),對模塊的第一層分解,表述這些被分解的視角(或者模塊)為實現需求需要滿足的最小承諾。

架構設計寫到什麼程度呢?它有兩個終止條件:

1. 這些約束要進一步分解的內容,用代碼比用文檔表述能力更強

2. 這些約束,不是本名稱空間的一部分,對那個約束進行細化,可以在這個約束之內自恰地被分解。就如同TCP只需要懂IP層給它的承諾就可以了,它不需要知道IBTA層(RoCEv1的傳輸層)的細節,這樣,在架構設計的這一層(或者多層),就可以形成立體結構的其中一個位面了。


推薦閱讀:

再談什麼是軟體架構
停下來,歇口氣,造輪子
從微服務聊起
不要嘗試同步代碼和設計文檔

TAG:软件架构 |