鵝廠萬人熱議|如何理解業務系統的複雜性?

圖片

圖片

👉騰小云導讀

業務系統複雜性一直是令開發者頭痛的問題。複雜的不是增加一個需求需要耗費多少時間,而是在增加一個需求後帶來的蝴蝶效應:其它功能會不會受到影響、要如何去找到這些影響,最終如何實現系統正常運行......功能之間隱祕增加的耦合、不可避免的代碼腐化在導致業務複雜性增加。大家都在說的軟件開發提效到底在提什麼?程序員日常工作中應該如何提升開發效率?敏捷開發、瀑布流式開發孰是孰非?歡迎閱讀。

👉看目錄,點收藏

1 業務背景與目標

2 軟件開發提效

3 業務系統複雜的根本原因

3.1 功能之間隱蔽增加的耦合

3.2 不可避免的代碼腐化

4 總結

01、 業務背景與目標

我依然非常清晰地記得去年的某個時候,我所在團隊 Leader 曾講過一段話:

“我擔憂的是,我們團隊規模的擴張並不是因爲用戶規模或營收規模的增長,僅僅是因爲我們有越來越多的事情要做導致人手緊缺。”

這個擔憂相信很多開發者都能感同身受,也許你所在的團隊也正面臨同樣的問題:爲什麼用戶規模或者營收規模不增加,事情反而越來越多呢?出現這個現象的原因其實也不難想到:由於業務規模停滯或者下滑,產品側不得不做更多的事情來止住頹勢甚至想要以此力挽狂瀾。要麼是不斷地拓展產品的邊界,在一個應用里加入更多的功能,也就是所謂的交付更多的用戶價值,從而吸引更多潛在用戶;要麼是不斷地優化現有功能,例如通過排版來從心理學角度提高用戶停留時長和點擊率,亦或是進一步優化產品的交互流程,也就是所謂的提升用戶體驗,從而提升口碑,穩固用戶基本盤。

這些要做的事情對應到開發側,那自然就是有更多的需求。 一個需求不能提升指標,那就要兩個;一個策略效果不及預期,那下次就得 A、B 兩個策略同時做實驗。看起來,這勢必會加劇團隊開發人員的壓力。

圖片

大版本上線前同時趕多個需求.gif

所以 Leader 希望開發團隊能夠提升效率,提升需求的吞吐率。但問題是,需求的增多,就一定要伴隨着開發團隊人力的增多嗎?

對其他行業來說,這個問題的答案是顯而易見的。你要修更多的房子,勢必要更多的工人;你要送更多的物資,勢必需要更多的車;你要打贏一波團戰,勢必需要更多的隊友…然而這個問題到了軟件行業,答案卻變得模糊了。

因爲軟件行業有個特有的性質——幾乎 0 成本的可複用性。某項功能別人實現了,那其他人就能立刻拿過來用。這對於建築等行業,那真的就是降維打擊。即使工地要修 10 棟一模一樣的宿舍樓,它也得一棟一棟地修,每一棟的人力和物力都是一樣的。而對於軟件行業,你只需要修一棟,剩下的 9 棟就是 0 成本的複製粘貼。

那對於一個在軟件工程上有追求的團隊來說,大家不是一直在追求可複用嗎?前期開發了那麼多需求和功能,想必也沉澱了非常多的能力吧?如果有那麼多能力可以 0 成本複用,那後續是不是開發效率會大大提高?

因此對很多人來說,理想中的軟件開發團隊,隨着功能的不斷增多,開發成本應該至少保持一個線性的關係。而且如果引入了一些重大的新技術,開發成本的線性增長率還會更低。

圖片

這種 functionalities-cost 的線性關係對很多團隊來說是習以爲常的。在很多團隊的項目管理中,每次迭代的需求數量都應不少於上次迭代,否則就需要覆盤。而對於開發效率的提升,則可以用本次迭代的需求數量較之前有多少比例的提升來判斷。**如果團隊有一個好的技術架構和合理的模塊劃分方案,考慮到可複用性可以減少成本,functionalities-cost 的關係會更平緩。**類似這樣:

圖片

然而,現實可能會給你沉重一擊。如果你去問問一線的開發人員,他們可能會告訴你真實的感受是這樣的:

圖片

隨着向系統中添加越來越多的功能,實現每個功能會變得越來越困難而不是越來越簡單。複用?基本不存在的。像上圖中的綠色線性關係都不太可能實現,更不必說優秀架構理想中類似於對數函數的曲線了。

過去 20 年中,軟件開發行業中部分人推崇敏捷開發,其終極目標也不過是爲了追求上圖中的黑色線條這種線性關係——也就是現在很多團隊習慣的,每個迭代完成固定數量的需求。在敏捷團隊看來,你必須要付出很多額外的努力,不斷地提取知識和重構,纔有可能維持住這種恆定的需求吞吐率。然而現實是很多團隊什麼都沒做,卻對這種穩定的吞吐率習以爲常了。到底是敏捷開發那一套是畫蛇添足,還是 996 太好用了?這是一個值得深思的問題。

那到底爲什麼現實世界中的 functionalities-cost 曲線是一個指數型,而不是理想中的線性或者對數型呢?這其實涉及到軟件模型的根本複雜性問題。在展開講這個概念之前,我們先講一些軟件開發的現狀。

02、軟件開發提效

過去一年,許多的公司都在大力推動降本增效。一般降本增效理解爲降本增效兩件事情,但過去一年大部分公司大部分還是降本。也許作爲開發者的你,過去的一年被拉進了無數的“成本歸屬羣”,收到了來自中臺的各種賬單。對中臺來說,確實是降本了,不過這些降掉的本又加到了業務方頭上。

所以項目組內部肯定也要做各種降本,比如更精細化地使用服務器和存儲資源,投入更多精力去關注雲上賬單,該省的省。在需求的技術評審環節加上成本預估,讓那些提極低的 ROI 需求的產品經理知難而退。上面這些手段都是降本。

更進一步地講,就是減少不必要的浪費。這種減少浪費的降本手段在短期很有用,但很快就會達到收益天花板。更大的收益還是要提升效率,但增效相關的工作,也許你感知到的是寥寥無幾。

其實說到軟件開發的增效,大多數人首先想到的就是工程效能(EP),也就是開發工具。利用各種好用的工具,來提升寫代碼、構建服務以及協同開發的效率。例如擁有可以極速處理幾百 G 大倉的代碼託管平臺,擁有高度可配置的流水線來自動化一些日常繁瑣的構建任務,有良好設計的 RPC 開發框架,還有先進的可觀測平臺,還有無數其他的效能平臺……

這一切的一切,最終目標或許大家也經常聽到——讓程序員可以專注於業務的開發。但問題是,在整個軟件的開發中,到底是業務開發工作量的佔比高,還是非業務開發工作量佔比高?

IBM 大型機之父、圖靈獎得主、軟件行業聖經《人月神話》的作者 Fred P. Brooks ,在他的一篇著名的文章 《沒有銀彈:軟件工程的本質性與附屬性工作》 中提到:

把一個複雜的軟件系統分成兩個部分:。

  • Essential Complexity 是說軟件要實現某種功能,而這種功能本身內在就具有複雜性。
  • Accidental Complexity 則代表了程序員在用代碼實現功能時,由於各種軟硬件等的限制以及人和人的溝通不暢而額外引入的工程上的複雜性。

如果你對上面提到的《沒有銀彈》感興趣,可以在騰訊雲開發者公衆號後臺回覆 「銀彈」,領取離線PDF閱讀材料。

開發軟件的目的,就是要交付某項功能。那麼這項功能的 Essential Complexity 就是不可避免的。即使你消除了所有的Accidental Complexity,Essence Complexity 依然存在。

所以沒有銀彈,No Silver Bullet。

回到上面的問題,我們可以發現:EP 和各種中臺爲開發者提供工具和服務,其實就是在儘量減少 Accidental Complexity。然後讓大家可以專注於業務本身的開發,也就是 Essence Complexity。如果二八法則適用於大部分的現實場景,那麼在軟件開發中,到底 Accident Complexity 是八還是 Essence Complexity是八?如果 Essence Complexity 是八。那我們一直只在 EP 上做文章,是否有點“隔靴搔癢”?

對於這個問題,我個人很喜歡《鳳凰項目》這本書中的一種思考方法:當你遇到比較複雜想不清楚的問題時,你可以假設你有一個無所不能的魔法棒,它能實現你的任何願望。所以你不用糾結具體問題怎麼解決,你可以直接想象“你期望的狀態是什麼”。所以,假如開發者也有這麼一個魔法棒,魔法棒揮一下,公司的各種基礎設施和工具立刻達到了太陽系的頂級水準,幾萬 PB 的大倉毫秒級克隆,用羅永浩的 TNT 口述就是能得到業界最牛的 CI Pipeline、業界最頂級的觀測系統、最頂級的發佈系統,全是頂級。不過然後呢?即使IT 和 EP 系統已經到了完美的程度,還是不得不面對這樣一個現實:終於可以專注於開發業務了。但是業務到底怎麼開發呢?

無數現實中的例子,由於業務建模的不合理、由於需求的倉促上線、由於接口設計的不合理、由於各種無謂的耦合……建立在最牛的基礎設施之上的業務系統,一段時間之後又將變成一座廢山。代碼看得令人眩暈,改功能不知道去哪裏改,不知道會影響哪些功能,不知道需要改動的點是否都被覆蓋到了,改個小功能要改無數的地方……

圖片

接手別人的項目.gif

然後,functionality-cost 曲線又變成了這樣。

圖片

不管基礎設施多麼優秀,業務代碼依然是廢山。所以只靠工具提效是遠遠不夠的,還需要關注業務本身,Essential Complexity。

這種想法其實大家早就該有,畢竟真正交付用戶價值,幫助項目組賺錢給大家發工資、發獎金的,就是這段業務代碼。只是業務類型千千萬,又必須時刻根據市場反饋去變化,看起來永遠是個開放命題。所以對於大部分人來說,在業務中去歸納一些 pattern,遠比去做一些 scope 較爲固定的工具要困難和難以落地,這可能也是很多開發者喜歡做工具或者 infra 、而不喜歡做業務的重要原因。因爲業務看起來真的太縹緲了,似乎沒什麼可總結和沉澱的。並且,很多人會錯誤的估計業務系統的複雜性,總覺得做業務開發就是單純的增刪查改,沒什麼技術含量。

對業務複雜性的錯誤認知和低估,會進一步加劇廢山的形成,從而讓 functionality-cost 曲線變得更加陡峭。接下來我們聊聊業務系統複雜的根本原因。

03、業務系統複雜的根本原因

結合過去參與過的很多業務系統開發以及近期閱讀書籍的一些思考,拋開人的原因(假設人都是理智的有追求的),個人認爲導致業務系統複雜的根本原因有兩個:功能之間隱祕增加的耦合不可避免的代碼腐化。

3.1 功能之間隱蔽增加的耦合

相信絕大部分開發者在項目一開始的時候,都有一顆“整潔架構”的心,都希望把代碼寫好。尤其是項目一開始,需求做的飛快,每天幾千行代碼也不在話下。大家會關注函數的顆粒度,會關注模塊的劃分和職責是否單一,也會關注單元測試情況和代碼的可測性。即使這樣,隨着時間推移,大家還是會發現代碼改起來越來越痛苦——總會牽一髮而動全身,或者明明是修改功能 A,卻不得不關注功能B是否受影響。這是爲什麼呢?

答案就是——耦合。 很多人一說到耦合,就會心生厭惡。確實,很多時候不合理的耦合是萬惡之源。但是耦合又是不可避免的。因爲 Essential Complexity的存在。如果某個功能本來就需要多個模塊共同參與,不論你怎麼分解這些模塊,只有把它們“集成”到一起,才能實現有意義的功能。把它們集成到一起,A 依賴於 B、B 又依賴 C、C 又會反饋給 A,這不就是耦合嗎?

軟件工程中有句話每個人都爛熟於心:高內聚,低耦合。但很多人只記住了後面三個字:低耦合,卻忘記了前面的三個字:高內聚

在高內聚的邊界內,各個模塊是不是就是強耦合的呢?即使你認真的進行架構設計去拆分模塊,這種耦合也是難以避免的。 下面我們舉兩個例子。

某團隊開發了一個社區類應用。社區應用大家應該也用過不少,大體架構是差不多的,一般都會包含如下業務模塊:

  • 資訊:可以簡單理解爲 feeds 流,主要以左文右圖的方式來展示。

  • 社區:用戶開放交流的地方,可以類比於新浪微博或者 twitter,用戶可以髮帶圖片的內容。

  • 評論區:資訊、動態都可以發評論。

  • ……

上述這幾個模塊都是比較獨立的業務,產品形態也有差異,因此一般都會由不同的小 Team 來負責。

有一天,產品經理希望做一個新功能,叫作“名片系統”。簡單來說就是,它允許用戶自定義自己頭像後展示哪些名片或者標籤(可以有多個),以突顯身份特徵,例如:

圖片

這個需求其實初看起來沒有多複雜,閉上眼睛琢磨大概就能想到。首先需要做一個配置頁面讓用戶選擇要展示的標籤並保存起來,同時需要在 App 中各種需要展示頭像的位置去讀取用戶的相關配置,來讓客戶端進行展示。看起來不難對吧?

但是再深入想一想,你就會發現其實並沒有想象的那麼簡單。如果沒有意識到由 Essential Complexity 引入的耦合,開發者很可能在排期的時候少估算了天數,最後不得不需要用各種“責任感”、“Ownership”這種精神力量通過加班來儘量保證不 delay。下面我們來看看爲什麼。

首先配置頁面需要從不同的系統去獲取用戶擁有的標籤。例如用戶勳章,需要從成就係統去獲取“連續簽到 30 天”,“連續創作一週”之類的成就,會員信息需要從會員系統獲取,主要就是用戶會員的 VIP 等級。個人/企業認證,類似於微博的黃 V 和藍 V,需要從認證系統去獲取。

這裏的重點不是要去不同的系統查數據麻煩,重點是這裏引入了新的耦合。 這些原本設計之初毫不相關的概念,被這個需求關聯在一起了。這種後來的再建立關聯關係,任誰在系統設計之初也不可能在架構層面去設計。並且,考慮到產品功能的完整性,這會帶來一個問題,就是這個需求會變得很 長尾。

後續如果一個負責資訊板塊的產品經理想要增強平臺優質內容的豐富度,要做一個簽約作者的體系。這時開發者除了要讓推薦系統對該簽約作者發的內容在推薦流量上做些傾斜調整,還不能忘了要到和那個需求「基本沒啥關係」的名片系統上來做些修改,從而讓這部分用戶能對外展示自己是“簽約作者”的標籤。

後續只要是和身份相關的內容,都會和這個功能耦合。當然,也許有人會說:“其實也可以不全支持,又不是不能用。”

圖片

週五傍晚產品經理找你改需求.gif

但是這就會帶來負向的用戶體驗,進一步引發的負面輿論處理不及時,很可能這個需求的收益反而不如它帶來的負面影響。

除了上述配置端,展示端其實也被耦合了。原本資訊的 feed 流、社區的動態列表、評論區以及資訊詳情頁的作者頭像展示部分的樣式都是不一樣的。有的只展示一個名字,有的展示名字加頭像,有的還要展示個人簡介等等。

但現在它們都要額外考慮名片的展示,問題是有的場景位置不夠,放不下那麼多標籤怎麼辦?哪個標籤更重要?有沒有權重?到底是名片系統統一來處理每個場景展示什麼標籤,還是各自場景自行決定?這也是個兩難的選擇。如果分發邏輯做在名片系統,那每增加一個露出場景,名片系統也要跟着改。如果是不同場景各自負責,那它們除了實現自己的邏輯,還不能忘了名片系統。但不論怎麼選,這裏也引入了強耦合。

並且即使這次梳理完了所有的現有場景,開發者把所有的位置都改一遍,這就完了嗎?顯然沒有,它是一個長尾的需求。後續只要是某個需求涉及到展示用戶頭像,是不是就需要考慮名片?萬一這個需求開發者之前沒參與過名片需求的開發怎麼辦,他怎麼知道要考慮名片?免不了上線後又是一通緊急 bug fix…很多開發者怕的不是這個功能本身有多複雜,怕的就是不知道改了這裏會影響其他什麼地方,或者別的地方也需要一起改。

圖片

bug.gif

我們可以看到,當系統變得複雜,功能之間會逐漸產生耦合,它們的關聯關係也會變得複雜。這些無意間引入的耦合,會給後續所有的需求開發增加一些額外的負擔。

所以,當你在做新需求時,還必須考慮它和一些舊的特性怎麼結合。 當系統的功能不斷膨脹,這些額外負擔會不斷增加,想讓每個迭代的需求吞吐率還能保持恆定簡直是癡人說夢,更別說想象中的需求交付速度越來越快了。

再舉個例子。做App肯定都希望看到用戶的裂變增長,引流就是一件非常重要的事情,尤其是從微信這個巨大的流量池引流。我們想把App上部分優質的內容分享到微信,這樣就能在微信中裂變傳播,吸引更多的用戶來下載和安裝。這個非常合理的需求,其實也引入了業務上的強耦合。

大部分手機App都是用原生的方式開發,例如 IOS 用 Swift/OC、Android用 Java/KT。但微信中只能分享 H5 的 Web 頁面。這就意味着同樣一個需求,除了要用原生做一遍,還需要用 H5 再做一遍。不僅如此,由於分享到微信的H5 頁面,用戶打開後肯定都是沒有登錄態,因此還需要讓 H5 依賴的後臺接口支持無登錄態調用。

實際上,這沒那麼好支持。有些接口邏輯強依賴於用戶登錄態怎麼辦?例如查看資訊詳情的接口,接口內部除了要返回資訊內容,還要記錄用戶的瀏覽記錄,還需要給資訊的瀏覽量+1。如果你沒有關注資訊的作者,可能頭像旁邊要展示一個關注按鈕……這些都需要依賴於用戶的登錄態才能完成。因此在沒有登錄態的情況下,就必須閹割一部分現有功能。

那要怎麼閹割呢? 在原接口中各種 if else?太 bad taste 了,不僅代碼亂成一鍋粥,統一的鑑權網關也很難處理。最好就是新開接口專門處理來自 H5 的調用,把它當成另一個獨立的需求,而不是強行和之前的接口邏輯寫在一起。但這還不夠,還有很多問題,例如像文章瀏覽量這種數據怎麼處理?沒有登錄態,就沒法對瀏覽量進行去重。如果每次請求都累加,就會被灰產利用來刷數據。如果不累加,似乎對作者又不太公平?這可能會導致產品側需要同時記錄有效瀏覽量和無登錄態瀏覽量,這又是一個新需求了。

圖片

嘗試調整一些邏輯.gif

此後,如果一個功能頁要支持分享到微信,客戶端要做一版完整的雙端接口,H5 要做一版簡化的。後臺要給客戶端提供一套接口,還要給 H5 提供一套無登錄態的定製版接口。就這樣一個分享到微信的功能,它又變成了長尾的需求,還讓後續所有的開發者工作量乘以 2 。

這些是技術架構不合理或者代碼寫得不好導致的嗎?顯然不是,這就是隨着產品功能不斷疊加,各種 Essential Complexity 帶來的天然耦合導致的。 到項目後期,每新增一個變更,除了修改這個變更本身,可能還要修改和它耦合的n+1 個位置。而且沒有辦法通過軟件上的優化來消除這種複雜性,因爲複雜性是不滅的。工程上的任何架構或者設計模式的引入,只會把複雜性從一個位置轉移到另一個位置,但永遠不會消失,No Silver Bullet。

3.2 不可避免的代碼腐化

除了業務本身的耦合帶來的複雜性,代碼腐化也是另一個讓業務系統變得複雜的重要原因。

相信大部分開發者都經歷過這樣的心路路程:

項目剛開始時雄心勃勃:維護前人的廢山是無奈,從零開始,要讓大家看看什麼纔是整潔架構的時代!

開發過程中:時間都是倒排,CR別人的代碼就是 5 秒後在企微回覆一個 d。需求改來改去,業務邏輯扭扭曲曲,辛苦寫好的單測又失效了,算了不裝了,爲什麼跟自己過不去呢?

後期:吶,搞出廢山大家都不想的咯,這次不算,下次一定,我煮碗麪給你喫

圖片

嘗試CR別人的代碼.gif

作者自己就是一個很典型的例子。大學畢業剛工作時,我負責維護了一個非常誇張的項目,沒有任何文檔,一個 PHP 文件幾萬行、一個函數上千行、一個接口能返回幾種完全不同的 JSON 作爲 response。每天還有幾十號人在瘋狂 push代碼。倉庫不斷膨脹,每次修改個功能,心中都是一萬個不情願,一不小心就會出現線上事故,至今我對 PHP 等沒有類型的語言開發項目還心有餘悸。

後來有個機會從零開始負責一個公司重量級的運營系統的開發,內心非常的激動。終於可以按照自己工作之餘看書學到的最佳實踐方法來構建項目了,這下要讓所有人刮目相看。開發過程中,也是恪盡職守,每天晚飯後都花至少1個小時拉着團隊另外幾個開發人員做 Code Review,經常還爭執得面紅耳赤,對 Bad Taste 堅決抵制。

項目整體推進得很順利,上線後取得了很大的成功,只是後來由於組織架構變動,去了另一個團隊,不再負責那個項目了。不過本人一直覺得,自己給接盤方打下了一個非常好的基礎,對方一定會感謝自己……直到一年後的某天,和一個同事無意間聊起來,他們就負責了我之前的那個項目(他不知道我之前負責那個項目)。本以爲能從他那得到些正向的評價,結果全是吐槽,諸如代碼看不懂、風格奇葩、擴展困難等等。最後補了一句,後來實在受不了,他們重寫了。

這就是發生在作者身上的真實故事,一個滿腔熱血,熟讀《整潔架構》《重構》《設計模式》《領域驅動》 《演進式架構》的人,從零開始開發系統,卻依然避免不了舊代碼走向腐化,成了後人口中的廢山始作俑者。

圖片

偶然間看見自己多年前寫的代碼.gif

到底代碼是如何腐化的? 這是一個非常大的話題,這裏就不展開了,因爲上述提到的書中幾乎都是講這些 Bad Taste 和相應的應對之道的,作者也沒有能力和自信能比它們講得更清楚。因此,本文只講爲什麼我覺得這種腐化是不可避免的。

核心原因:架構設計和模塊抽象只能面向當下,它天然是短視的或者說是有侷限性的。**這種侷限性即使是最優秀的架構師也是無法逾越的。

說到這個問題,先講兩個常見的開發模式。大家可能聽過,現在更提倡敏捷開發而不是瀑布流式的開發。 但到底什麼是敏捷,什麼是瀑布流呢?

  • 瀑布流式開發

瀑布流就是上個世紀比較傳統的開發模式。甲方提需求,我要做一個什麼樣的軟件,它要包含哪些功能 。軟件公司作爲乙方,來承接甲方的需求。它首先需要有人去調研甲方的需求,具象化每個功能點,然後形成最終的需求文檔和性能要求文檔。當甲方對需求認可並簽字後,就進入了架構師的設計階段。這個階段架構師能夠看到所有的需求,他擁有全局的視角,然後進行架構設計、方案設計和模塊的拆分。最後根據架構師的設計,開發部門就分模塊進行開發。開發完成之後進入測試階段,測試完成後再交給甲方去驗收,驗收通過就正式交付。

這就是瀑布流式的開發。必須前一步做完再交給下一步,就像瀑布一樣順流而下。這種開發方式現在看來是不好的,因爲這種開發方式週期很長,動輒就是以6 個月甚至 1 年起步,很多大項目甚至要 3 年以上。但商場如戰場,形勢瞬息萬變,等你做出來,黃花菜都涼了,再好的軟件又有什麼用呢?

很多軟件工程的書上都講過“項目失敗”的案例,大多就是這種用瀑布流開發方式開發的項目,由於開發週期太長,預算嚴重超支,或者還沒做完就發現市場已經不需要了,或者還沒做完發現技術方案已經過時了等等。並且,瀑布流式開發實際上在後期有非常多的問題。大家現在開發完一個小需求之後多方一起聯調都覺得痛苦,你能想象某個大型項目,等所有的功能開發完再去測試會有多少問題嗎!即使好不容易處理完項目本身的問題,甲方驗收時還有更大的麻煩:“當初說的做 XXX,但是你們做出來是 YYY,根本不是我要的,不滿足需求”。這又會涉及大量的返工,進一步讓項目延期。

因爲瀑布流這種開發方式太過於笨重,無法適應現代軟件交付速度的預期,中間有大量的人力空轉和內耗,所以後來一羣大佬在一起做了一個“敏捷宣言”,提倡敏捷開發流程。敏捷開發其實就是對瀑布流式開發做出了修改,之前是收集好所有需求,再來做整體設計,再來開發,最後測試。任何一個環節出問題,都會導致後續環節出問題。比如需求沒整理對,那後續所有工作都白搭。架構沒設計好,開發就會很痛苦。開發的代碼難以測試,那測試進展就非常緩慢。

  • 敏捷開發

敏捷開發的解決方法就是小步快跑。先做最重要的部分。如果要造汽車,我先做發動機和4個輪子,只在駕駛員那綁個凳子,讓它能夠先跑起來。等跑起來了,再去逐步完善其它地方。我先做個後視鏡,如果沒人關心那就這樣了,不繼續投入了。我再試下給車加個擋風玻璃。如果市場反應非常好,那就加大投入繼續優化,除了前擋,四周上下都給圍上。我再試下多加幾個凳子,市場反應炸裂,那就加大投入,把凳子換成沙發......

這就是敏捷開發。小步快跑,在迭代中識別出更重要的需求,這樣才能快速響應市場的變化。

但這裏需要糾正很多人對敏捷開發的一個誤區。聽到敏捷開發,大家總以爲這種方式能提高開發效率和開發速度。其實不對。從上面的例子你應該可以看明白,敏捷交付的是半成品,它的解決方案就是不要一口喫個大胖子。小步快跑,做一點交付一點。

如果從完成品的角度來講,敏捷並不會提高交付速度,甚至它會更慢。你可以很直接地看到,這種開發方式缺失了對整體目標的把控,設計上天然有欠考慮的地方,後期要改就得花更多的成本。

但是敏捷的優勢在於,它能夠快速捕捉市場機會,讓自己活下來,活下來纔有機會談成本,再找到性價比高的地方去優化。

很多人曾經都在想,自己團隊能否嘗試一下敏捷開發? 其實,現在不就已經是了嗎?雖然在流程上和國外提倡的敏捷開發存在較大差異,可以稱之爲“中華田園敏捷開發”,但確實也是敏捷開發。現在互聯網公司基本上都是快節奏的發佈,做App 都是先發 MVP 版本,然後再持續優化。每個迭代,產品經理都是隻提幾個有限的需求,開發也只開發這幾個需求就上線。然後就進入不斷堆功能的小步快跑階段,縫縫補補又一年。產品經理也會用各種方式嘗試去識別功能的收益,埋點、報表、同比環比等等。

聊了這麼多關於瀑布流式開發和敏捷開發,這和代碼不可避免的腐化有什麼關係呢?

其實當大家知道現在這種“中華田園式敏捷開發”後,馬上就能意識到,每次大家在做技術方案設計時,能拿到的信息僅僅是宏大視圖中的小小一角,根本沒有全貌,並不能像瀑布流開發那樣拿到產品的整體視圖。僅僅憑藉這一點點信息,再牛的架構師設計出來的方案也是有侷限性的,這也是爲什麼前面說架構設計和模塊抽象只能面向當下,它天然是短視的。這不是人的問題,這是開發方式的問題。當然,現實情況是,這種局部的需求,很多人也沒有去做設計。拿到需求,直接從 controller 開始寫代碼解析入參,然後 service 組合一下RPC 和 DB 調用,DAO 再實現幾個數據庫查詢就可以了。根據作者的經驗,這種情況甚至能佔到 80%以上。在一個項目中只有少量的局部架構設計+這些架構設計還不一定合理+80%以上任何設計都沒有+有上千種讓代碼難以閱讀的編碼方式,如果說代碼不腐化,你信嗎?

這裏再舉個例子。

有個後臺管理系統需要做權限管理功能,所以開發者基於業界常見的 RBAC 模型開發了一個權限管理模塊。在做方案設計時,大家一直比較關注可複用性,因爲後續可能有別的系統也需要權限管理。

其實辦法也很簡單,在模型中加入了租戶的概念( appid ),所有的 Role 表和 Access 表都帶上 appid 字段。這樣,不同業務就可以自定義自己的 Role 和 Access 而不干擾其它的業務。這個設計按理說也還可以,只要是基於 RBAC 模型的權限管理,後續分配幾個 appid就可以用了。

然而,兩週後的一個需求直接就來打臉了。這個需求也要做權限管理,它表面上看也是基於 RBAC 模型的,但是有細微的區別。簡單說,這個需求類似於遊戲裏的幫派管理,幫主有所有權限。他還能夠設置任意多個管理組,比如副幫主、長老、堂主等;管理組成員可以管理幫派成員。管理組之間也有權重,權重高的管理組可以管理權重低的管理組,比如副幫主可以管理所有長老和堂主,長老可以管理所有堂主。

看起來依然是基於 RBAC 模型,不同管理組就是不同 Role 。但是這裏最大的區別就是,原始的 RBAC、Role 之間是互相無感知的,不同 Role 不需要知道別的 Role 的存在,它只需要知道它有哪些 Access。但是對於這個需求,Role 之間需要建立關係,有優先級,高級的 Role 可以管理低級的 Role。

這種 Role 之間的關聯關係,在一開始設計 RBAC 模塊時是沒想到的,所以大家當時的設計只能應對當時的需求,擴展性也只是多租戶,而對於新的需要修改模型的功能就無能爲力了。這也是爲什麼說在“中華田園敏捷開發”中,架構設計總是短視的。

圖片

功能先上了再說.gif

後來大家進行了一個小覆盤,爲什麼設計的通用權限管理第一個需求,就沒法複用?大家爲後臺管理設計的模型,誰能想到產品要做幫派管理。雖然擴展性設計只考慮多租戶也確實過於簡單了,但是如果考慮更多擴展性,工時是不是也會增加呢,會不會又有過度設計的嫌疑呢?……後來,那個業務又只能重新開發一套了,當然裏面還包含很多其它性能優化。因爲它們的請求量比較大,各種數據要緩存到 Redis。而一開始的面向後臺管理系統的 RBAC,一天也沒幾個人用,每次都直接讀 mysql。

這樣的例子其實還有很多,就不一一列舉了。大家也可以想想自己項目中的通用 XXX 系統,看看到底通不通用。很多時候看似類似的需求,其 Essential Complexity 是很不一樣的,對應的軟件建模也是有區別的。盲目地追求複用,在函數後不斷地加參數,可能適得其反。

說到複用,開源界在國外有兩個比較形象的說法:Free as Beer、Free as Puppy。

有些“可複用的能力”是像啤酒一樣免費的 Free as Beer,拿來就喝不給錢。

還有些“可複用的能力”是像小狗一樣免費。雖然你免費獲得了一隻可愛的小狗,在收穫短暫的快樂後,你需要各種鏟屎、各種照護。到底快樂多還是負擔多,就看你是不是愛狗人士了。

那些在設計之初就沒有經過精心考慮的“通用系統”,對於用戶來說就是 Free as Puppy。要用只得捏着鼻子用,後續要改動加功能還很困難。其實也不復雜,不如…...造個輪子吧——Yet Another Shit Comes!

當然也不是所有的系統都是短視的。業界也有很多 Free as Beer 的系統。這些系統大多都是面向特定的場景,例如 ERP、CRM,以及雲上各種 Saas Paas。要注意的是,它們都是面向特定場景的產品,有明確的邊界。只有這樣它們才能在內部進行充分的建模,從而構建出符合特定場景的通用產品。

因此建議各位開發者在想着做“通用”的時候,先想想自己的“通用”指什麼、邊界在哪裏。 一般來說,你要做的東西業界或者公司都有同類產品,你爲什麼不用?那些你不願意用的產品,它其實也是想做通用的,但是你有沒有想過爲什麼它沒有達到目的呢?你去做的話,你有自信可以讓你的東西 Free as Beer 嗎?請想清楚了再動手。

通過上面的例子,我們可以看到,腐化除了來自開發者低質量的代碼,更核心的是來自於系統架構的腐化。 而在“中華田園敏捷開發”的這種開發方式下,需求本身就是零散的,目標也是模糊的。在沒有全局視圖的情況下,架構自然就是有侷限的,只能適應當下。而隨着項目的發展,只能適應當下的架構就會失效。

如果意識不到這個問題,後續在這種失效的架構上進行任何修修補補和魔改可能都會進一步加劇它的腐化,導致代碼更難以看懂。

04、總結

由於 Essential Complexity 的存在,No Silver Bullet。爲了快速響應市場的“中國田園敏捷開發”的開發方式,帶來不可避免的代碼腐化。難道這就是程序員的黑暗森林嗎?

其實程序員並不害怕 Essential Complexity。只要狀態好,日敲千行代碼不在話下。程序員最害怕的還是代碼腐化。 很多設計上的決策和代碼爲什麼要這麼寫,是內隱的( Tacit Knowledge ),它只存在於最開始的開發者腦中,隨着那個人的遺忘或者離開,這些內隱知識將永久丟失。所以通過文檔沉澱內隱知識對於項目是非常重要的。道理各位開發者都懂,但誰又願意費勁寫文檔呢?

因此,代碼腐化+文檔缺失,會極大地增加後續的開發者認知負擔,使得某些功能的流程難以辨認,不知道從何下手。應對方法也很直接,要做的就是代碼防腐以及知識沉澱,但這些恰好又是很多人嫌麻煩又不願做的地方。 畢竟人都是自私的,誰願意幹前人栽樹後人乘涼的事呢,多堆點需求幫業務掙錢拿個五星去晉升不香嗎,我爲啥要防腐爲啥要寫文檔?

並且,做代碼防腐通過事前做一點 EPC 是遠遠不夠的,它只能提升代碼質量的一點點下限。結構性的腐化,只能靠重構。 而重構說白了,就是事後諸葛亮。

當你擁有了更多的信息後再回過頭來看當時設計的侷限性,然後再來對之前的設計進行歸納總結,該分離的分離,該提取公因式的就提取公因式。根據近期的經驗再預測未來產品的發展方向,再去刻意設計一些靈活性。

圖片

大神重構代碼.gif

但重構的收益到底是什麼?重構完能帶來多少需求吞吐率的提升,能給出數據嗎?講不出收益,怎麼和產品去 battle 和管理層要時間呢?

代碼腐化就是技術債務,但是債務不總是有害的。也許歷來每年貸款在北京、上海、深圳投資房產的人,甚至會後悔槓桿沒拉滿。所以技術債務也不是什麼洪水猛獸,它甚至是時代的紅利。但是債務總要還。例如現在大家想還房貸都還不了還要排隊。那到底什麼時候適合償還技術債務,償還多少合適,具體怎麼還呢?

文檔總是過時的。寫的信息量太少沒人看,想看的部分沒人寫,改了代碼還要同步改文檔容易忘記怎麼辦?寫文檔太費事怎麼辦?如果你感興趣,歡迎留言評論,我們會繼續推出關於業務複雜性的下篇。以上是本次分享全部內容,歡迎大家在評論區分享交流。如果覺得內容有用,歡迎轉發~在公衆號後臺回覆**「銀彈」**,領取Fred P. Brooks (IBM 大型機之父、圖靈獎得主)所著 《沒有銀彈:軟件工程的本質性與附屬性工作》。

-End-

原創作者|劉德恩

技術責編|劉德恩

圖片

研發提效近年飽受關注。你有什麼好的經驗?有什麼方法、書籍推薦?歡迎在公衆號評論區留言分享。我們將選取1則最有創意的分享,送出騰訊雲開發者-限定隨行杯1個(見下圖)。5月4日中午12點開獎。

圖片

在公衆號後臺回覆「銀彈」,領取Fred P. Brooks (IBM 大型機之父、圖靈獎得主)所著 《沒有銀彈:軟件工程的本質性與附屬性工作》閱讀材料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章