作者 | 段和塵
文章標題很隨意,些微有一些騙點擊的“賊意”;但內(nèi)容卻是充滿了誠意,想必你已經(jīng)感受到了。
這是一次源于頭條 Android 客戶端軟件架構(gòu)問題的探討,之所以冠上“嘴炮”之名,是因為它有一些務(wù)虛;同時又夾雜了一些方法論,不僅適用于客戶端軟件架構(gòu),也適用于其他工作場景,希望對大家有所幫助。
為了拉滿讀者的帶入感,且以“我們”為主語,來看架構(gòu)的挑戰(zhàn)、判斷和打法。
我們的挑戰(zhàn)期望高
優(yōu)秀的公司對架構(gòu)都有著很高的期許,都希望有一個良好的頂層設(shè)計,從上到下有統(tǒng)一的認知,遵循共同的規(guī)范,寫出讓人舒適的代碼,甚至有那么一丟偷懶,有沒有“一勞永逸”的架構(gòu)設(shè)計可保基業(yè)長青?
然而高期望意味著高落差,面對落差,我們?nèi)菀捉箲]:
代碼什么時候能寫的看上去本就應(yīng)該是那個樣子;而現(xiàn)在怎么就像是在攀登“屎山”呢?
文檔什么時候能寫的既簡明又詳細;而現(xiàn)在怎么就簡明的看不懂,詳細的很多余呢?
工具什么時候能更好用更強大一點;而現(xiàn)在怎么就動不動掉鏈子,沒有想要的功能常年等排期呢?
“我”什么時候能從架構(gòu)工作中找到成就感,而不是搞一搞就想著跑路呢?
責任大
大量問題的最終歸因都是代碼問題:設(shè)計不合理、使用不規(guī)范、邏輯太晦澀、編碼“坑”太多。
沒有一個單一的團隊能承擔這些問題的責任,我們收到過很多“吐槽”:
這尼瑪誰寫的,簡直不堪入目,看小爺我推倒重來展現(xiàn)一把真正的實力
XX 在這里埋了顆雷,但 XX 已經(jīng)不管了,事到如今,我也只能兜底搞一把
這壓根就不應(yīng)該這么用,本來的設(shè)計又不是為了這個場景,亂搞怪我咯?
臥槽,這特么是隱藏技能啊,編譯時悄悄改了老子的代碼,找瞎了都沒找到在哪過環(huán)節(jié)滲透進來的
一方面,口嗨一時爽,我們“吐槽”歷史代碼得到了一時的舒緩;另一方面,也意味著責任也傳遞到了我們:處理得好,我們的產(chǎn)出可能還是一樣會被當作糟粕,但如果處理不好,我們就斷送了業(yè)務(wù)發(fā)展的前程。
事情難
架構(gòu)面臨的從來不是單一的業(yè)務(wù)問題,而是多個業(yè)務(wù)多人協(xié)作的交叉問題,負重前行是常態(tài)。
業(yè)務(wù)歷久彌新,歷史包袱疊加新的場景,隨便動動刀子就拔出蘿卜帶出泥。譬如:頭條 2021 年 10 月的版本有 XXXX 組件,相比一年前已經(jīng)翻倍;類個數(shù) XXXXX;插件 XX 個;倉庫數(shù)量 XX 個;ttmain 倉庫權(quán)限 XXX 人。(XX 代表數(shù)量級,隱去了具體數(shù)字,^_^)
技術(shù)棧層出不窮,一方面要保持成熟穩(wěn)定,一方面要積極探索落地。架構(gòu)的同學要熟悉多種技術(shù)棧,譬如:跨端技術(shù)在客戶端業(yè)務(wù)中通常都是多種共存(H5/Hybrid/小程序/Lynx/Flutter),一個業(yè)務(wù)到底選用哪種技術(shù)棧進行承載,需要耗費多少成本?選定技術(shù)棧后存在什么局限,是否存在不可逾越的障礙?
療效慢
我們經(jīng)常說代碼復(fù)雜度高,并把降復(fù)雜度作為架構(gòu)方向的重點工作之一;但影響復(fù)雜度的因子眾多,從外部來看,有主觀感受、客觀指標、行業(yè)對標三個角度;從內(nèi)部來看,有工程組織、代碼實現(xiàn)和技術(shù)棧三個角度。即便我們很好的優(yōu)化了工程結(jié)構(gòu)這個因子,短時間內(nèi)也很難感受到復(fù)雜度有一個明顯的下降。
我們常說治理,其實是設(shè)計一種機制,在這種機制下運轉(zhuǎn)直到治愈。
就像老中醫(yī)開方子,開的不是特效藥,而是應(yīng)對病癥的方法,是不是有用的方子,終究還是需要通過實踐和時間的檢驗。希望我們不要成為庸醫(yī),瞎抓幾把藥一燉,就吹噓藥到病除。
我們的判斷
架構(gòu)問題老生常談
誰來復(fù)盤架構(gòu)問題,都免不了炒一炒“冷飯”;誰來規(guī)劃架構(gòu)方向,都逃不出了“減負”、“重構(gòu)”、“復(fù)用”、“規(guī)范”這些關(guān)鍵詞。難點在于把冷飯炒熱,把方向落實。
架構(gòu)方向一直存在
架構(gòu)并不只局限于一個產(chǎn)品的初始階段,而是伴隨著產(chǎn)品的整個生命周期。架構(gòu)也不是一成不變的,它只適合于特定的場景,過去的架構(gòu)不一定適合現(xiàn)在,當下的架構(gòu)不一定能預(yù)測未來,架構(gòu)是隨著業(yè)務(wù)不斷演進的,不會出現(xiàn)架構(gòu)方向做到頭了、沒有事情可搞了的情況,架構(gòu)永遠生機勃勃。
強制遵循規(guī)范: 通常會要求業(yè)務(wù)公共的組件逐漸下沉到基礎(chǔ)組件層,但隨著時間的推移,這個規(guī)范很容易被打破
需要成熟的團隊: 領(lǐng)域?qū)<?對業(yè)務(wù)細節(jié)非常熟悉的角色)和開發(fā)團隊需緊密協(xié)作,構(gòu)建出核心領(lǐng)域模型是關(guān)鍵。但盲目嘗試 DDD 往往容易低估領(lǐng)域驅(qū)動設(shè)計這套方法論的實踐成本,譬如將簡單問題復(fù)雜化、陷入過分強調(diào)技術(shù)模型的陷阱
迄今為止,用于商業(yè)應(yīng)用程序的最流行的軟件架構(gòu)設(shè)計模式是大泥球(Big Ball of Mud, BBoM),BBoM 是“......一片隨意構(gòu)造、雜亂無章、凌亂、任意拼貼、毫無頭緒的代碼叢林。”
泥球模式將扼殺開發(fā),即便重構(gòu)令人擔憂,但也被認為是理所應(yīng)當。然而,如果還是缺乏對領(lǐng)域知識應(yīng)有的關(guān)注和考量,新項目最終也會走向泥球。沒有開發(fā)人員愿意處理大泥球,對于企業(yè)而言,陷入大泥球就會喪失快速實現(xiàn)商業(yè)價值的能力。
——《領(lǐng)域驅(qū)動設(shè)計模式、原理與實踐》Scott Millett & Nick Tune
復(fù)雜系統(tǒng)熵增不斷
只要業(yè)務(wù)繼續(xù)發(fā)展,越來越復(fù)雜就是必然趨勢,這貼合熱力學的熵增定律。
可以從兩個維度來看復(fù)雜度熵增的過程:理解成本變高和預(yù)測難度變大。
理解成本:規(guī)模和結(jié)構(gòu)是影響理解成本的兩個因素
宏大的規(guī)模是不好理解的,譬如:在城市路網(wǎng)中容易迷路,但在鄉(xiāng)村中就那么幾條道
復(fù)雜的結(jié)構(gòu)是不好理解的,譬如:一個鐘表要比一條內(nèi)褲難以理解
當需求增多時,軟件系統(tǒng)的規(guī)模也會增大,且這種增長趨勢并非線性增長,會更加陡峭。倘若需求還產(chǎn)生了事先未曾預(yù)料到的變化,我們又沒有足夠的風險應(yīng)對措施,在時間緊迫的情況下,難免會對設(shè)計做出妥協(xié),頭疼醫(yī)頭、腳疼醫(yī)腳,在系統(tǒng)的各個地方打上補丁,從而欠下技術(shù)債(Technical Debt)。當技術(shù)債務(wù)越欠越多,累積到某個臨界點時,就會由量變引起質(zhì)變,整個軟件系統(tǒng)的復(fù)雜度達到巔峰,步入衰亡的老年期,成為“可怕”的遺留系統(tǒng)。
正如飼養(yǎng)場的“奶牛規(guī)則”:奶牛逐漸衰老,最終無奶可擠;然而與此同時,飼養(yǎng)成本卻在上升。
——《實現(xiàn)領(lǐng)域驅(qū)動設(shè)計 - 深入理解軟件的復(fù)雜度》張逸
預(yù)測難度:當下的籌碼不足以應(yīng)對未來的變化
業(yè)務(wù)變化不可預(yù)測,譬如:頭條一開始只是一個單端的咨詢流產(chǎn)品,5 年前誰也不會預(yù)先設(shè)計 Lite 版、抖音、懂車帝等,多端以及新的業(yè)務(wù)場景帶來的變化是無法預(yù)測的。很多時候,我們只需要在當下做到“恰當”的架構(gòu)設(shè)計,但需要盡可能保持“有序”,一旦脫離了“有序”,那必將走向混亂,變得愈加不可預(yù)測
技術(shù)變化不可預(yù)測,譬如:作為一個 Java 開發(fā)人員,Lambda 表達式的簡潔、函數(shù)式編程的快感、聲明式/響應(yīng)式 UI 的體驗,都是“真香”的技術(shù)變化,而陳舊的 Java 版本以及配套的依賴都需要升級,一旦升級,伴隨著的就是多版本共存、依賴地獄(傳遞升級)等令人膽顫的問題。很多時候,我們不需要也沒辦法做出未來技術(shù)的架構(gòu)設(shè)計,但需要讓架構(gòu)保持“清晰”,這樣我們能更快的擁抱技術(shù)的變化
既然注定是逆風局,那跑到最后就算贏。
過多的流程規(guī)范反倒會讓大家覺得是自己是牽線木偶,牽線木偶注定會隨風而逝。
我們應(yīng)該更多“強調(diào)”一些原則,譬如:分而治之、控制規(guī)模、保持結(jié)構(gòu)的清晰與一致,而不是要求大家一定要按照某一份指南進行架構(gòu)設(shè)計,那既降低不了復(fù)雜度,又跟不上變化。“強調(diào)”并不直接解決問題,而是把重要的問題凸顯出來,讓大家在一定的原則下自己找到問題的解決辦法。
我們的打法我們的套路是:定義問題 → 確定架構(gòu) → 方案落地 → 結(jié)果復(fù)盤。越是前面的步驟,就越是重要和抽象,也越是困難,越能體現(xiàn)架構(gòu)師的功力。所以,我們打法的第一步就是要認清問題所在。
認清問題
問題分類
架構(gòu)的問題是盤根錯節(jié)的,將所有問題放在一起,就有輕重緩急之分,就有類別之分。區(qū)分問題的類別,就能在一定的邊界內(nèi),匹配上對應(yīng)的人來解決問題。
工程架構(gòu):
業(yè)務(wù)架構(gòu):
基礎(chǔ)能力:
標準化:
問題分級
挑戰(zhàn)、問題、手段這些經(jīng)常混為一談,哪些是挑戰(zhàn)?哪些是問題?那些是手段?其實這些都是一回事,就是矛盾,只是不同場景下,矛盾所在的層級不同,舉一個例子:
我們判斷當前的研發(fā)體驗不能滿足業(yè)務(wù)日漸延伸的需要,這是一個矛盾,既是當下的挑戰(zhàn),也是當下的一級問題。要處理好這個矛盾,我們得拆解它,于是就有了二級問題:我們的代碼邏輯是否已經(jīng)足夠優(yōu)化?研發(fā)流程是否已經(jīng)足夠便捷?文檔工具是否已經(jīng)足夠完備?二級問題也是矛盾,解決好二級問題就是我們處理一級矛盾的手段。這樣層層遞進下去,我們就能把握住當前我們要重點優(yōu)化和建設(shè)的一些基礎(chǔ)能力:插件化、熱更新、跨端能力。
在具體實踐過程中,基礎(chǔ)技術(shù)能力還需要繼續(xù)拆解,譬如:熱更新能力有很多(Java 層的 Robust/Qzone 超級補丁/Tinker 等,Native 層的 Sophix/ByteFix 等),不同熱更方案各有優(yōu)劣,適用場景也不盡相同。我們要結(jié)合現(xiàn)狀做出判斷,從眾多方案汲取長處,要么做出更大的技術(shù)突破創(chuàng)新,要么整合已有的技術(shù)方案進行組合創(chuàng)新。
勤于思考問題背后的問題
亨利福特說,如果我問客戶需要什么,他們會告訴我,他們需要一匹更快的馬。從亨利福特的這句話,我們可以提煉出一個最直接的問題:客戶需要一匹更快的馬。立足這個問題本身去找解決方案,可能永遠交不出滿意的答卷:尋找更好的品種,更科學的訓馬方式。
思考問題背后的問題,為什么客戶需要一匹更快的馬?可能客戶想要更快的日常交通方式,上升了一個層次后,我們立刻找到了更好的解決方案:造車。
我們不能只局限于問題本身,還需要看到問題背后的問題,然后才能更容易找到更多的解決方案。
認知金字塔
引用認知金字塔這個模型,謹以此共勉,讓我們能從最原始數(shù)據(jù)中,提煉出解決問題的智慧。
- DATA:金字塔的最底層是數(shù)據(jù)。數(shù)據(jù)代表各種事件和現(xiàn)象。數(shù)據(jù)本身沒有組織和結(jié)構(gòu),也沒有意義。數(shù)據(jù)只能告訴你發(fā)生了什么,并不能讓你理解為什么會發(fā)生。
- INFORMATION:數(shù)據(jù)的上一層是信息。信息是結(jié)構(gòu)化的數(shù)據(jù)。信息是很有用的,可以用來做分析和解讀。
- KNOWLEDGE: 信息再往上一層是知識。知識能把信息組織起來,告訴我們事件之間的邏輯聯(lián)系。有云導(dǎo)致下雨,因為下雨所以天氣變得涼快,這都是知識。成語典故和思維套路都是知識。模型,則可以說是一種高級知識,能解釋一些事情,還能做預(yù)測。
- WISDOM:認知金字塔的最上一層,是智慧。智慧是識別和選擇相關(guān)知識的能力。你可能掌握很多模型,但是具體到這個問題到底該用哪個模型,敢不敢用這個模型,就是智慧。
這就是“DIKW 模型”。
循序漸進
架構(gòu)的問題不能等,也不能急。一個大型應(yīng)用軟件,并非要求所有部分都是完美設(shè)計,針對一部分低復(fù)雜性的區(qū)域或者不太可能花精力投入的區(qū)域,滿足可用的條件即可,不需要投入高質(zhì)量的構(gòu)建成本。
以治理頭條復(fù)雜度為例:
- 長期的架構(gòu)目標:更廣(多端復(fù)用)、更快(單端開發(fā)速度)、更好(問題清理和前置攔截)
- 當下的突出問題:業(yè)務(wù)之間耦合太重、缺少標準規(guī)范、代碼冗余晦澀
細節(jié)已打碼,請讀者不要在意。重點在于厘清問題之后,螺旋式上升,做到長期有方向,短期有反饋。
最后
一頓輸出之后,千萬不能忘卻了人文關(guān)懷,畢竟謀事在人。架構(gòu)獅得供起來,他們高瞻遠矚,運籌帷幄;但架構(gòu)人,卻是更需要被點亮的,他們可能常年在“鏟屎”,他們期望得到認可,他們有的還沒有對象...干著干著,架構(gòu)的故事還在,但人卻仿佛早已翻篇。