架構控制的從權問題
來自專欄軟體架構設計54 人贊了文章
前幾天在這個問題下面吐槽了幾句:in nek:為什麼有些大公司技術弱爆了?,估計不是一直看我的隨筆的人很難看懂那個吐槽指向的是什麼。正好今天颱風天,飛機飛不了了,我來補充一下那個吐槽背後的架構思維。
架構好不好,外行是看不出來的,甚至就算你是內行,不花時間你也是看不出來的。比如我遇到過這麼一個案例:老代碼支持新硬體平台——我簡單一點舉例——比如這個新硬體平台初始化的命令字從HW_CMD_START(1),修改成了HW_CMD_START_V2(2)了,這個修改從架構師的角度來說,我們要考慮這麼些問題:
i. 舊平台是否還要兼容?
ii. V1到V2的這個修改,是否僅包含了這個命令字的改變,這些改變,是否對硬體抽象層和軟體應用層之間的語義造成了影響?
iii. V1和V2的代碼是要共二進位版本(需要啟動時動態配置),還是源代碼版本(需要靜態配置),還是可以同時使用(需要運行時動態配置)?
等等。
不考慮這些問題,老油條或者無經驗的新手怎麼寫這個程序呢?——很簡單,直接把HW_CMD_START修改成2,就可以了。你不驗老平台的硬體,你都不可能知道,等你知道的時候,成本已經投出去了,你也沒有回頭的餘地了。
也許你覺得這個是個小問題,很容易就改回去了,但把問題稍想複雜一點,硬體的修改從init-start-work這樣的初始化過程,變成了pre_init-start-config-work這樣的初始化過程呢?你的數據結構必須根據這個過程放到不同的stage上去,這個問題只要不綜合考量。而直接根據V2的硬體修改代碼,改為以後你發現V1的模式還需要繼續使用,你這個代碼已經變成一團麻了。
我前段時間還遇到過這麼一個問題(抽象過以便好理解,和任何事實無關):一個PCIE設備pdev,分出部分硬體資源,用軟體模擬另一個設備vdev,虛擬設備為進程提供服務。方法是每次分配了部分資源給一個硬體文件(file),打開文件的時候分配資源。
好了,現在file被進程打開了,它要鎖住vdev不能被釋放,而vdev要鎖住pdev不能被釋放。很顯然,這個設計應該是file->open()負責get_device(vdev),vdev->create()負責get_device(pdev)。但有人就可以為了方便,直接在file->open()中get_device(vdev), get_device(pdev)。這樣結構的結果就是中間這個vdev層其實根本沒有意義。因為三個層次的「語義」完全交聯在一起了,要改一起改,不看另一個模塊的代碼實現,只看介面,你改得對不對,這要看天。
這種情形發展到極致,就是interlace:比如這樣,把硬體A1, A2, A3抽象為A,A給B提供介面,B的實現裡面還要判斷如果是A1如何,是A2如何,是A3如何……然後,B給C提供介面,C裡面也要做這個判斷。看起來分了三層,其實三層和涼席一樣,分層和語義完全正交地交織在一起。不深入分析它的流程,你以為是三層,看進去……你只想操他祖宗十八代。
還有一些更隱性的,表現為一種Careless的文化。我遇到過一個這樣的例子:有人做一個Linux解決方案,方案中有一個硬體,需要做一個驅動,這個驅動要交付的時候要交付3個分支,比如3.27-customized,4.10-customized,4.17-cusomized。你讓我做這個方案,我會有兩個選擇:
方案1,先上主線(Linux主線也行,自己的上游主線也罷),然後落地到3個分支上,維護4個分支。
方案2,獨立一個驅動目錄,編譯後在三個分支的某個版本上測試。保證質量。
這兩個方案都是程序員要為一個自恰的名稱負責的。無論是分支作為一個主題,程序員為整個分支的邏輯自恰負責,還是驅動作為一個整體,程序員為這個驅動的整體邏輯自恰負責。他考慮這個特性的邏輯的時候,都是可以面面俱到的。
但有人會做出這樣的方案:
方案3:獨立一個驅動目錄,可以基於腳本拷貝到三個customized分支目錄中,這個驅動同時支持三個分支。
很多人都看不出這個方案背後那個careless的態度:這個方案有人為驅動負責,也有人為沒有這個驅動的customized分支負責,但沒有人為這兩者的「聯動」負責。因為你決定怎麼拷貝驅動到Kernel Tree的時候,並沒有任何機制保證Customized Kernel的Maintainer得到通知(前面的方案2配套的是死分支,是一個tag)。兩者沒有一個嚴格的「細節配套」邏輯,維護下去一定會發生滑動,優化不會考慮發展和細節介面(因為根本沒得想,對方是動態的)。覺得這個方案可行的人,背後其實根本沒有打算做一個嚴密的語義邏輯,這種爛架構,你也只能見一步走一步,架構控制就沒有了。
這些情形,我稱為「壓扁」。架構本來是立體的:
in nek:讓代碼變立體壓扁了,它能跑,但它不再具有活性了,這種代碼沒有未來。沒有未來不表示它活不下去(有錢繼續投資,什麼都能活下去),沒有未來表示它沒有辦法面對更多的需求。所以,一個架構不好的代碼,只要市場還在,它就能活,但你要它做出更多的變化,它就是不行(但這個團隊就是會找出各種理由,證明「這個不行是應該的」,「這是我們這個架構的『特點』,和對手『各有優勢』」。
很多人討論中國操作系統怎麼發展不起來,有一個點是說它沒有生態,這句話沒有錯,但你以為解決生態了這個問題就沒有問題了,這完全是幼稚。就算你有很大的投資,很多的用戶,但你的代碼早早就壓扁了,你就只能停在那個版本上不動了,怎麼可能有未來?整個Android的代碼都給你,專利版權都給你,你以為你能維護下去?你看看Ubuntu,Redhat,Suse,Windows哪個商業發行版沒有「生命周期」的概念的?就算是號稱無縫滾動的版本,在細節上都是有生命周期的。但你再看看哪個「國產操作」系統有這個概念?跟這些人談,他們連「生命周期」是什麼都沒有搞明白呢,他們只能等現在這個版本實在活不下去了,找別人的版本重新來過。以前加進去得意洋洋的特性還能不能用?還是那句話,看天。
扯遠了,回到壓扁代碼這個問題。
這些問題,在你面對著每天幾十個Patchset的日常工作中,你根本不可能深入進去一個個看,你就只能挑重點,挑不到的,遇到這樣的人(不上心,只要現在能跑就行的人),你就只好忍了。你只能把有擔當的放在maintainer的位置,負責做gatekeeper。你才有可能做有效的控制。如果都是這樣的人,架構師就只能從權,從架構師變成項目經理,只負責抽鞭子,不要架構,要「結果」,要能運行,這樣,短時間內你會有結果(先忽悠住投資人再說吧,投資人是天然的外行,就算他不是外行,在海量細節面前,他也得給我變成外行)。
後面的,如果團隊的水平起來了,有未來目標的架構師可能會開始基於前面這筆投資得到的經驗,開始整理架構,如果都是些爛泥,那就趕緊找個強勢的維護經理接盤吧,反正系統成型以後,就無所謂「架構控制」了。架構控制只能發生在構築的前期,房子都蓋好了,那時是個裝飾房間的問題,你再跟我說什麼承重結構,什麼暗管布線防水?我不會比一個普通的工程師表現更好的,或者說,也許比一個表現更好,但也不會比2個,或者3個的表現更好的。一個架構師的工資頂五六個普通工程師是少不了的,架構師干這個,基本上就是浪費錢,不如把他撤了完犢子,否則這傢伙一定會進行各種表演(架構重構啦,清lint告警啦,降低圈複雜度啦,代碼鋤奸團啦……),基本上有破壞沒建設。
所以,所謂一個「求道」(事實成功)的架構師,大部分時候我們要根據團隊來「從權」,對這個團隊來說,說到底就是求仁得仁,團隊從上到下,都沒有情懷,都是能跑就好,架構師要成事,就只能一起從權。只有這個團隊心裡有點星辰大海,你才有可能發生一點點改變。這些東西,領導是「外行」,他們是改變不了的,只有一個個的工程師自己有心,才有可能從星星之火,變成一種文化。
這就叫虛心實腹,九層之台,起於累土,總耍小聰明,誰都救不了你。
好了,前面說的這個邏輯,其實只是我的邏輯的一部分。這裡這些頭頭是道的道理,是「架構」這件事的邏輯根本。現在我們來談「變數」。
很多領導、投資者,特別是國內的,對「架構目標」不怎麼在乎,有一個很重要的原因是:「架構是針對未來的設計」,考量未來是有風險的,如果沒有未來,這個根本就是浪費。在團隊能力不足,經驗不足的時候,強行要求進行架構考量,這很容易造成浪費。這就叫「絕學無憂」——你一堆的理論,成本高昂,但這個理論是否真的可以實現目標,從「結果」上根本無從判斷,不如一開始就不要聽你這些理論,而是要求你出結果。
這是個平衡的問題。
我現在寫模塊設計,基本上是先確定API介面,進行功能和成本上的推演後,才進入編碼的,這樣不容易在參數和介面眾多的時候把架構「拍扁」(功能在幾個模塊之間調配的時候,如果一開始不考慮清楚模塊的角色,很容易放錯地方,就形成「拍扁」的局面了)。但剛參加工作的時候,我很少這樣干,因為我根本就寫不出來。我對實現一個API到底需要什麼東西都不清楚,定義一個API,到實現的時候發現參數不夠,或者發現某個索引隔著兩個對象,根本拿不到索引,這些前期的定義都失去意義了。
即使是現在,面對複雜的面向對象系統,我在前期定義API的時候,都不會把全部參數定死,而更關注當前對象的「角色」。「角色」對了,邏輯放錯的可能性就低,少一兩個索引,調整一下幾個介面的參數,就可以關聯起來了,重點還是決定某個代碼邏輯到底應該放到那個模塊中,作為那個模塊「身份」的一部分。
而對於初次構建的大型複雜系統,比如某些加速器,在一個很複雜的軟體計算模型中,抽出其中一部分到硬體上,首先CPU的流水線就發生了變化,接著是系統匯流排,內存系統(包括Cache系統)的壓力也發生了變化,Thoughput和Latency的控制變數也跟著變了。可能你原來的控制要素是memcpy的效率,現在變成了smp_mb的boardcast的效率。比如,原來你用1024個線程進行分布計算,每個可以並行的流程長,鎖衝突不嚴重,現在很多數據流到了加速器上,CPU線程的核間衝突就變得很嚴重。這種「經驗數據」還沒有出來的時候,架構控制就無從談起了。
這種情況下,對軟體介面的要求是要等這些變數都相對穩定了,才會變得重要。
所以,有一些階段的產品,架構師只能在前期進行最大流量,最大時延,客戶群關鍵需求判定這些方面的推演,之後就只能退化為前面說的拿鞭子的項目經理,保證結果先出來,在出來一波後,才能談軟體方面的架構控制和長遠發展。
這種,是需要折騰一兩個版本以後才能確定方向的。
這始終是個度的問題,沒有什麼明顯的Pattern可以從表面上看出來的。
說了半天,好像怎麼都沒說,說到底,對於一個複雜系統,控制要素在不同的階段可以控制的東西是不同的。對於連運行都沒有運行過的新產品,軟體架構控制很弱,重點是系統的可行性分析,這時系統工程師邏輯(就是計算「能跑」,「可能」的邏輯的人)會佔據優勢。等有一定的數據了。系統的長遠發展的問題就變得越來重要,這時架構控制的作用才會顯現出來。到開始上量賣出規模了,這時這些控制的作用就越來越弱了(但仍然可以有控制,因為控制不好還是可以一次把立體的架構拍扁),看在原來的基礎上能活多久了。
所以我才說,強行把不同階段的產品拼在一起,對兩個產品都是傷害。
推薦閱讀:
※調用第三方介面的架構優化
※互聯網架構的變遷
※架構師之路:剛入IT行業的人,該不該學架構?
※iOS高級架構師是如何養成?我來教你方法!
※MySQL高可用架構之MHA(1)