成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

阿里研究員:警惕軟件復(fù)雜度困局

開(kāi)發(fā) 開(kāi)發(fā)工具
對(duì)于大型的軟件系統(tǒng)如互聯(lián)網(wǎng)分布式應(yīng)用或企業(yè)級(jí)軟件,為何我們常常會(huì)陷入復(fù)雜度陷阱?如何識(shí)別復(fù)雜度增長(zhǎng)的因素?

 [[339003]]

 

阿里妹導(dǎo)讀:對(duì)于大型的軟件系統(tǒng)如互聯(lián)網(wǎng)分布式應(yīng)用或企業(yè)級(jí)軟件,為何我們常常會(huì)陷入復(fù)雜度陷阱?如何識(shí)別復(fù)雜度增長(zhǎng)的因素?在代碼開(kāi)發(fā)以及演進(jìn)的過(guò)程中需要遵循哪些原則?本文將分享阿里研究員谷樸關(guān)于軟件復(fù)雜度的思考:什么是復(fù)雜度、復(fù)雜度是如何產(chǎn)生的以及解決的思路。較長(zhǎng),同學(xué)們可收藏后再看。

文末福利:免費(fèi)下載《2020年微服務(wù)領(lǐng)域開(kāi)源數(shù)字化報(bào)告》。

寫(xiě)在前面

軟件設(shè)計(jì)和實(shí)現(xiàn)的本質(zhì)是工程師相互通過(guò)“寫(xiě)作”來(lái)交流一些包含豐富細(xì)節(jié)的抽象概念并且不斷迭代過(guò)程。

另外,如果你的代碼生存期一般不超過(guò)6個(gè)月,本文用處不大。

一 軟件架構(gòu)的核心挑戰(zhàn)是快速增長(zhǎng)的復(fù)雜性

越是大型系統(tǒng),越需要簡(jiǎn)單性。

大型系統(tǒng)的本質(zhì)問(wèn)題是復(fù)雜性問(wèn)題。互聯(lián)網(wǎng)軟件,是典型的大型系統(tǒng),如下圖所示,數(shù)百個(gè)甚至更多的微服務(wù)相互調(diào)用/依賴,組成一個(gè)組件數(shù)量大、行為復(fù)雜、時(shí)刻在變動(dòng)(發(fā)布、配置變更)當(dāng)中的動(dòng)態(tài)的、復(fù)雜的系統(tǒng)。而且,軟件工程師們常常自嘲,“when things work, nobody knows why”。

 

圖源:https://divante.com/blog/10-companies-that-implemented-the-microservice-architecture-and-paved-the-way-for-others/

如果我們只是寫(xiě)一段獨(dú)立代碼,不和其他系統(tǒng)交互,往往設(shè)計(jì)上要求不會(huì)很高,代碼是否易于使用、易于理解、易于測(cè)試和維護(hù),根本不是問(wèn)題。而一旦遇到大型的軟件系統(tǒng)如互聯(lián)網(wǎng)分布式應(yīng)用或者企業(yè)級(jí)軟件,我們常常陷入復(fù)雜度陷阱,下圖the life of a software engineer是我很喜歡的一個(gè)軟件cartoon,非常形象的展示了復(fù)雜度陷阱。

圖源:http://themetapicture.com/the-life-of-a-software-engineer/

 

做為一個(gè)有追求的軟件工程師,大家肯定都思考過(guò),我手上的項(xiàng)目,如何避免這種似乎難以避免的復(fù)雜度困境?

然而對(duì)于這個(gè)問(wèn)題給出答案,卻出乎意料的困難:很多的文章都給出了軟件架構(gòu)的設(shè)計(jì)建議,然后正如軟件領(lǐng)域的經(jīng)典論著《No silver bullet》所說(shuō),這個(gè)問(wèn)題沒(méi)有神奇的解決方案。并不是說(shuō)那么多的架構(gòu)文章都沒(méi)用(其實(shí)這么方法多半都有用),只不過(guò),人們很難真正去follow這些建議并貫徹下去。為什么?我們還是需要徹底理解這些架構(gòu)背后的思考和邏輯。所以我覺(jué)得有必要從頭開(kāi)始整理這個(gè)邏輯:什么是復(fù)雜度,復(fù)雜度是如何產(chǎn)生的,以及解決的思路。

二 軟件的復(fù)雜度為什么會(huì)快速增長(zhǎng)?

要理解軟件復(fù)雜度會(huì)快速增長(zhǎng)的本質(zhì)原因,需要理解軟件是怎么來(lái)的。我們首先要回答一個(gè)問(wèn)題,一個(gè)大型的軟件是建造出來(lái)的,還是生長(zhǎng)出來(lái)的?BUILT vs GROWN,that is the problem.

1 軟件是長(zhǎng)出來(lái)的,不是建造出來(lái)的

軟件不是建造出來(lái)的,甚至不是設(shè)計(jì)出來(lái)的。軟件是長(zhǎng)出來(lái)的。

這個(gè)說(shuō)法初看上去和我們平時(shí)的認(rèn)識(shí)似乎不同,我們常常談軟件架構(gòu),架構(gòu)這個(gè)詞似乎蘊(yùn)含了一種建造和設(shè)計(jì)的意味。然而,對(duì)于軟件系統(tǒng)來(lái)說(shuō),我們必須認(rèn)識(shí)到,架構(gòu)師設(shè)計(jì)的不是軟件的架構(gòu),而是軟件的基因,而這些基因如何影響軟件未來(lái)的形態(tài)則是難以預(yù)測(cè),無(wú)法完全控制。

為什么這么說(shuō)?所謂建造和“生長(zhǎng)”差異在哪里?其實(shí),我們看今天一個(gè)復(fù)雜的軟件系統(tǒng),確實(shí)很像一個(gè)復(fù)雜的建筑物。但是把軟件比作一棟摩天大樓卻不是一個(gè)好的比喻。原因在于,一個(gè)摩天大樓無(wú)論多么復(fù)雜,都是事先可以根據(jù)設(shè)計(jì)出完整詳盡的圖紙,按圖準(zhǔn)確施工,保證質(zhì)量就能建造出來(lái)的。然而現(xiàn)實(shí)中的大型軟件系統(tǒng),卻不是這么建造出來(lái)的。

 

例如淘寶由一個(gè)單體PHP應(yīng)用,經(jīng)過(guò)4、5代架構(gòu)不斷演進(jìn),才到今天服務(wù)十億人規(guī)模的電商交易平臺(tái)。支付寶,Google搜索,Netflix微服務(wù),都是類似的歷程。

是不是一定要經(jīng)過(guò)幾代演進(jìn)才能構(gòu)建出來(lái)大型軟件,就不能一次到位嗎?如果一個(gè)團(tuán)隊(duì)離開(kāi)淘寶,要拉開(kāi)架勢(shì)根據(jù)淘寶交易的架構(gòu)重新復(fù)制一套,在現(xiàn)實(shí)中是不可能實(shí)現(xiàn)的:沒(méi)有哪個(gè)創(chuàng)業(yè)團(tuán)隊(duì)能有那么多資源同時(shí)投入這么多組件的開(kāi)發(fā),也不可能有一開(kāi)始就朝著超級(jí)復(fù)雜架構(gòu)開(kāi)發(fā)而能夠成功的實(shí)現(xiàn)。

 

也就是說(shuō),軟件的動(dòng)態(tài)“生長(zhǎng)”,更像是上圖所畫(huà)的那樣,是從一個(gè)簡(jiǎn)單的“結(jié)構(gòu)”生長(zhǎng)到復(fù)雜的“結(jié)構(gòu)”的過(guò)程。伴隨著項(xiàng)目本身的發(fā)展、研發(fā)團(tuán)隊(duì)的壯大,系統(tǒng)是個(gè)逐漸生長(zhǎng)的過(guò)程。

2 大型軟件的核心挑戰(zhàn)是軟件“生長(zhǎng)”過(guò)程中的理解和維護(hù)成本

復(fù)雜軟件系統(tǒng)最核心的特征是有成百上千的工程師開(kāi)發(fā)和維護(hù)的系統(tǒng)(軟件的本質(zhì)是工程師之間用編程語(yǔ)言來(lái)溝通抽象和復(fù)雜的概念,注意軟件的本質(zhì)不是人和機(jī)器溝通)。如果認(rèn)同這個(gè)定義,設(shè)想一下復(fù)雜軟件是如何產(chǎn)生的:無(wú)論最終多么復(fù)雜的軟件,都要從第一行開(kāi)始開(kāi)發(fā)。都要從幾個(gè)核心開(kāi)始開(kāi)發(fā),這時(shí)架構(gòu)只能是一個(gè)簡(jiǎn)單的、少量程序員可以維護(hù)的系統(tǒng)組成架構(gòu)。隨著項(xiàng)目的成功,再去逐漸細(xì)化功能,增加可擴(kuò)展性,分布式微服務(wù)化,增加功能,業(yè)務(wù)需求也在這個(gè)過(guò)程中不斷產(chǎn)生,系統(tǒng)滿足這些業(yè)務(wù)需求,帶來(lái)業(yè)務(wù)的增長(zhǎng)。業(yè)務(wù)增長(zhǎng)對(duì)于軟件系統(tǒng)迭代帶來(lái)了更多的需求,架構(gòu)隨著適應(yīng)而演進(jìn),投入開(kāi)發(fā)的人員隨著業(yè)務(wù)的成功增加,這樣不斷迭代,才會(huì)演進(jìn)出幾十,幾百,甚至幾千人同時(shí)維護(hù)的復(fù)雜系統(tǒng)來(lái)。

大型軟件設(shè)計(jì)核心要素是控制復(fù)雜度。這一點(diǎn)非常有挑戰(zhàn),根本原因在于軟件不是機(jī)械活動(dòng)的組合,不能在事先通過(guò)精心的“架構(gòu)設(shè)計(jì)”規(guī)避復(fù)雜度失控的風(fēng)險(xiǎn):相同的架構(gòu)圖/藍(lán)圖,可以長(zhǎng)出完完全全不同的軟件來(lái)。大型軟件設(shè)計(jì)和實(shí)現(xiàn)的本質(zhì)是大量的工程師相互通過(guò)“寫(xiě)作”來(lái)交流一些包含豐富細(xì)節(jié)的抽象概念并且相互不斷迭代的過(guò)程[2]。稍有差錯(cuò),系統(tǒng)復(fù)雜度就會(huì)失控。

所以說(shuō)了這么多是要停留在形而上嗎?并不是。我們的結(jié)論是,軟件架構(gòu)師最重要的工作不是設(shè)計(jì)軟件的結(jié)構(gòu),而是通過(guò)API,團(tuán)隊(duì)設(shè)計(jì)準(zhǔn)則和對(duì)細(xì)節(jié)的關(guān)注,控制軟件復(fù)雜度的增長(zhǎng)。

  • 架構(gòu)師的職責(zé)不是試圖畫(huà)出復(fù)雜軟件的大圖。大圖好畫(huà),靠譜的系統(tǒng)難做。復(fù)雜的系統(tǒng)是從一個(gè)個(gè)簡(jiǎn)單應(yīng)用 一點(diǎn)點(diǎn)長(zhǎng)出來(lái)的。
  • 當(dāng)我們發(fā)現(xiàn)自己的系統(tǒng)問(wèn)題多多,別怪“當(dāng)初”設(shè)計(jì)的人,坑不是一天挖出來(lái)的。每一個(gè)設(shè)計(jì)決定都在貢獻(xiàn)復(fù)雜度。

三 理解軟件復(fù)雜度的維度

1 軟件復(fù)雜度的兩個(gè)表現(xiàn)維度:認(rèn)知負(fù)荷與協(xié)同成本

我們分析理解了軟件復(fù)雜度快速增長(zhǎng)的原因,下面我們自然希望能解決復(fù)雜度快速增長(zhǎng)這一看似永恒的難題。但是在此之前,我們還是需要先分析清楚一件事情,復(fù)雜度本身是什么?又如何衡量?

代碼復(fù)雜度是用行數(shù)來(lái)衡量么?是用類的個(gè)數(shù)/文件的個(gè)數(shù)么?深入思考就會(huì)意識(shí)到,這些表面上的指標(biāo)并非軟件復(fù)雜度的核心度量。正如前面所分析的,軟件復(fù)雜度從根本上說(shuō)可以說(shuō)是一個(gè)主觀指標(biāo)(先別跳,耐心讀下去),說(shuō)其主觀是因?yàn)檐浖?fù)雜度只有在程序員需要更新、維護(hù)、排查問(wèn)題的時(shí)候才有意義。一個(gè)不需要演進(jìn)和維護(hù)的系統(tǒng)其架構(gòu)、代碼如何關(guān)系也就不大了(雖然現(xiàn)實(shí)中這種情況很少)。

 

既然 “軟件設(shè)計(jì)和實(shí)現(xiàn)的本質(zhì)是工程師相互通過(guò)寫(xiě)作來(lái)交流一些包含豐富細(xì)節(jié)的抽象概念并且不斷迭代過(guò)程” (第三次強(qiáng)調(diào)了),那么,復(fù)雜度指的是軟件中那些讓人理解和修改維護(hù)的困難程度。相應(yīng)的,簡(jiǎn)單性,就是讓理解和維護(hù)代碼更容易的要素。

“The goal of software architecture is to minimize the manpower required to build and maintain the required system.” Robert Martin, Clean Architecture [3].

因此我們將軟件的復(fù)雜度分解為兩個(gè)維度,都和人理解與維護(hù)軟件的成本相關(guān):

  • 第一,認(rèn)知負(fù)荷 cognitive load :理解軟件的接口、設(shè)計(jì)或者實(shí)現(xiàn)所需要的心智負(fù)擔(dān)。
  • 第二,協(xié)同成本Collaboration cost:團(tuán)隊(duì)維護(hù)軟件時(shí)需要在協(xié)同上額外付出的成本。

我們看到,這兩個(gè)維度有所區(qū)別,但是又相互關(guān)聯(lián)。協(xié)同成本高,讓軟件系統(tǒng)演進(jìn)速度變慢,效率變差,工作其中的工程師壓力增大,而長(zhǎng)期難以取得進(jìn)展,工程師傾向于離開(kāi)項(xiàng)目,最終造成質(zhì)量進(jìn)一步下滑的惡性循環(huán)。而認(rèn)知負(fù)荷高的軟件模塊讓程序員難以理解,從而產(chǎn)生兩個(gè)后果:(1) 維護(hù)過(guò)程中易于出錯(cuò),bug 率故障率高;(2) 更大機(jī)率 團(tuán)隊(duì)人員變化時(shí)被拋棄,新成員選擇另起爐灶,原有投入被浪費(fèi),甚至更高糟糕的是,代碼被拋棄但是又無(wú)法下線,成為定時(shí)炸彈。

2 影響到認(rèn)知負(fù)荷的因素

認(rèn)知負(fù)荷又可以分解為:

  • 定義新的概念帶來(lái)認(rèn)知負(fù)荷,而這種認(rèn)知負(fù)荷與 概念和物理世界的關(guān)聯(lián)程度相關(guān)。
  • 邏輯符合思維習(xí)慣程度:正反邏輯差異,邏輯嵌套和獨(dú)立原子化組合。繼承和組裝差異。

(1)不恰當(dāng)?shù)倪壿嫀?lái)的認(rèn)知成本

看以下案例[7]:

A. Code with too much nesting

  1. response = server.Call(request) 
  2.   
  3. if response.GetStatus() == RPC.OK: 
  4.   if response.GetAuthorizedUser(): 
  5.     if response.GetEnc() == 'utf-8'
  6.       if response.GetRows(): 
  7.         vals = [ParseRow(r) for r in 
  8.                 response.GetRows()] 
  9.         avg = sum(vals) / len(vals) 
  10.         return avg, vals 
  11.       else
  12.         raise EmptyError() 
  13.     else
  14.       raise AuthError('unauthorized'
  15.   else
  16.     raise ValueError('wrong encoding'
  17. else
  18.   raise RpcError(response.GetStatus()) 

B. Code with less nesting

  1. response = server.Call(request) 
  2.   
  3. if response.GetStatus() != RPC.OK: 
  4.   raise RpcError(response.GetStatus()) 
  5.  
  6. if not response.GetAuthorizedUser(): 
  7.   raise ValueError('wrong encoding'
  8.  
  9. if response.GetEnc() != 'utf-8'
  10.   raise AuthError('unauthorized'
  11.   
  12. if not response.GetRows(): 
  13.   raise EmptyError() 
  14.  
  15. vals = [ParseRow(r) for r in 
  16.         response.GetRows()] 
  17. avg = sum(vals) / len(vals) 
  18. return avg, vals 

比較A和B,邏輯是完全等價(jià)的,但是B的邏輯明顯更容易理解,自然也更容易在B的代碼基礎(chǔ)上增加功能,且新增的功能很可能也會(huì)維持這樣一個(gè)比較好的狀態(tài)。

而我們看到A的代碼,很難理解其邏輯,在維護(hù)的過(guò)程中,會(huì)有更大的概率引入bug,代碼的質(zhì)量也會(huì)持續(xù)惡化。

(2)模型失配:和現(xiàn)實(shí)世界不完全符合的模型帶來(lái)高認(rèn)知負(fù)荷

軟件的模型設(shè)計(jì)需要符合現(xiàn)實(shí)物理世界的認(rèn)知,否則會(huì)帶來(lái)非常高的認(rèn)知成本。我遇到過(guò)這樣一個(gè)資源管理系統(tǒng)的設(shè)計(jì),設(shè)計(jì)者從數(shù)學(xué)角度有一個(gè)非常優(yōu)雅的模型,將資源賬號(hào) 用合約來(lái)表達(dá)(下圖左側(cè)),賬戶的balance可以由過(guò)往合約的累計(jì)獲得,確保數(shù)據(jù)一致性。但是這樣的設(shè)計(jì),完全不符合用戶的認(rèn)知,對(duì)于用戶來(lái)說(shuō),感受到的應(yīng)該是賬號(hào)和交易的概念,而不是帶著復(fù)雜參數(shù)的合約。可以想象這樣的設(shè)計(jì),其維護(hù)成本非常之高。

 

(3)接口設(shè)計(jì)不當(dāng)

以下是一個(gè)典型的接口設(shè)計(jì)不當(dāng)帶來(lái)的理解成本。

  1. class BufferBadDesign { 
  2.  
  3.   explicit Buffer(int size);// Create a buffer with given sized slots 
  4.   void AddSlots(int num);// Expand the slots by `num` 
  5.   // Add a value to the end of stack, and the caller need to 
  6.   // ensure that there is at least one empty slot in the stack before 
  7.   // calling insert 
  8.   void Insert(int value); 
  9.  
  10.   int getNumberOfEmptySlots(); // return the number of empty slots 

希望我們的團(tuán)隊(duì)不會(huì)設(shè)計(jì)出這樣的模塊。這個(gè)問(wèn)題可以明顯看到一個(gè)接口設(shè)計(jì)的不合理帶來(lái)的維護(hù)成本提升:一個(gè)Buffer的設(shè)計(jì)暴露了內(nèi)部?jī)?nèi)存管理的細(xì)節(jié)(slot維護(hù)),從而導(dǎo)致在調(diào)用最常用接口 “insert”時(shí)存在陷阱:如果不在insert前檢查空余slot,這個(gè)接口就會(huì)有異常行為。

但是從設(shè)計(jì)角度看,維護(hù)底層的Slot的邏輯,也外部可見(jiàn)的buffer的行為其實(shí)并沒(méi)有關(guān)聯(lián),而只是一個(gè)底層的實(shí)現(xiàn)細(xì)節(jié)。因此更好的設(shè)計(jì)應(yīng)該可以簡(jiǎn)化接口。把Slot數(shù)量的維護(hù)改為內(nèi)部的實(shí)現(xiàn)邏輯細(xì)節(jié),不對(duì)外暴露。這樣也完全消除了因?yàn)槭褂貌划?dāng)帶來(lái)問(wèn)題的場(chǎng)景。同時(shí)也讓接口更易于理解,降低了認(rèn)知成本。

class Buffer { explicit Buffer(int size); // Create a buffer with given sized slots // Add a value to the end of buffer. New slots are added // if necessary. void Insert(int value);}

事實(shí)上,當(dāng)我們發(fā)現(xiàn)一個(gè)模塊在使用時(shí)具備如下特點(diǎn)時(shí),一般就是難以理解、容易出錯(cuò)的信號(hào):

  • 一個(gè)模塊需要調(diào)用者使用初始化接口才能正常行為:對(duì)于調(diào)用者來(lái)說(shuō),需要調(diào)用初始化接口看似不是大的問(wèn)題,但是這樣的模塊,帶來(lái)了多種后患,尤其是當(dāng)存在多個(gè)參數(shù)需要設(shè)置,相互關(guān)聯(lián)關(guān)系復(fù)雜時(shí)。配置問(wèn)題應(yīng)該單獨(dú)解決(比如通過(guò)工廠模式,或者通過(guò)單獨(dú)的配置系統(tǒng)來(lái)管理)。
  • 一個(gè)模塊需要調(diào)用者使用后做清理/ finalizer才能正常退出。
  • 一個(gè)模塊有多種方式讓調(diào)用者實(shí)現(xiàn)完全相同的功能:軟件在維護(hù)過(guò)程中,出現(xiàn)這種狀況可能是因?yàn)槌跏荚O(shè)計(jì)不當(dāng)后來(lái)修改設(shè)計(jì) 帶來(lái)的冗余,也可能是設(shè)計(jì)原版的缺陷,無(wú)論如何這種模塊,帶著強(qiáng)烈的“壞味道”。

完全避免這些問(wèn)題很難,但是我們需要在設(shè)計(jì)中盡最大努力。有時(shí)通過(guò)文檔的解釋來(lái)彌補(bǔ)這些問(wèn)題是必要的,但是好的工程師/架構(gòu)師,應(yīng)該清醒的意識(shí)到,這些都是“壞味道”。

(4)一個(gè)簡(jiǎn)單的修改需要在多處更新

簡(jiǎn)單修改涉及多處更改也是常見(jiàn)的軟件維護(hù)復(fù)雜度因素,而且主要影響的是我們的認(rèn)知負(fù)荷:維護(hù)修改代碼時(shí)需要花費(fèi)大量的精力確保各處需要修改的地方都被照顧到了。

最簡(jiǎn)單的情形是代碼當(dāng)中有重復(fù)的“常數(shù)”,為了修改這個(gè)常數(shù),我們需要多處修改代碼。程序員也知道如何解決這一問(wèn)題,例如通過(guò)定義個(gè)constant 并處處引用避免magic number。再例如網(wǎng)頁(yè)的風(fēng)格/色彩,每個(gè)頁(yè)面相同配置都重復(fù)設(shè)置同樣的色彩和風(fēng)格是一種模式,而采用css模版則是更加易于維護(hù)的架構(gòu)。這在架構(gòu)原則中對(duì)應(yīng)了數(shù)據(jù)歸一化原則(Data normalization)。

稍微復(fù)雜一些的是類似的邏輯/或者功能被copy-paste多次,原因往往是不同的地方需要稍微不同的使用方式,而過(guò)去的維護(hù)者沒(méi)有及時(shí)refactor代碼提取公共邏輯(這樣做往往需要更多的時(shí)間精力),而是省時(shí)間情況下選擇了copy-paste。這就是常說(shuō)的 Don't repeat yourself原則:

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system[8]

(5)命名

軟件中的API、方法、變量的命名,對(duì)于理解代碼的邏輯、范圍非常重要,也是設(shè)計(jì)者清晰傳達(dá)意圖的關(guān)鍵。然而,在很多的項(xiàng)目里我們沒(méi)有給Naming /命名足夠的重視。

我們的代碼一般會(huì)和一些項(xiàng)目關(guān)聯(lián),但是需要注意的是項(xiàng)目是抽象的,而代碼是具體的。項(xiàng)目或者產(chǎn)品可以隨意一些命名,如阿里云喜歡用中國(guó)古代神話(飛天、伏羲、女?huà)z)命名系統(tǒng),K8s也是來(lái)自于希臘神話,這些都沒(méi)有問(wèn)題。而代碼中的API、變量、方法不能這樣命名。

一個(gè)不好的例子是前一段我們的Cluster API 被命名為T(mén)rident API(三叉戟),設(shè)想一下代碼中的對(duì)象叫Trident時(shí),我們?nèi)绾卫斫庠谶@個(gè)對(duì)象應(yīng)該具備的行為?再對(duì)比一下K8s中的資源:Pod, ReplicaSet, Service, ClusterIP,我們會(huì)注意到都是清晰、簡(jiǎn)單、直接符合其對(duì)象特征的命名。名實(shí)相符可以很大程度上降低理解該對(duì)象的成本。

有人說(shuō)“Naming is the most difficult part of software engineering[9][10]”,或許也不完全是個(gè)玩笑話:Naming的難度在于對(duì)于模型的深入思考和抽象,而這往往確實(shí)是很難的。

需要注意的是:

(a)Intention vs what it is

需要避免用“是什么”來(lái)命名,要用“for what / intention”。“是什么”來(lái)命名是會(huì)很容易將實(shí)現(xiàn)細(xì)節(jié)。比如我們用 LeakedBarrel做rate limiting,這個(gè)類最好叫 RateLimiter,而不是LeakedBarrel:前者定義了意圖(做什么的),后者 描述了具體實(shí)現(xiàn),而具體實(shí)現(xiàn)可能會(huì)變化。再比如 Cache vs FixedSizeHashMap,前者也是更好的命名。

(b)命名需要符合當(dāng)前抽象的層級(jí)

首先我們軟件需要始終有清晰的抽象和分層。事實(shí)上我們Naming時(shí)遇到困難,很多就是因?yàn)檐浖呀?jīng)缺乏明確的抽象和分層帶來(lái)的表象而已。

(6)不知道一個(gè)簡(jiǎn)單特性需要在哪些做修改,或者一個(gè)簡(jiǎn)單的改動(dòng)會(huì)帶來(lái)什么影響,即unknown unknowns

在所有認(rèn)知復(fù)雜度的表現(xiàn)中,這是最壞的一種,不幸的是,所有人都曾經(jīng)遇到過(guò)這樣的情況。

 

一個(gè)典型的unknown unknown是一部分代碼存在這樣的情況:

  • 代碼缺乏充分的測(cè)試覆蓋,一些重要場(chǎng)景依賴維護(hù)者手工測(cè)試。
  • 代碼有隱藏/不易被發(fā)現(xiàn)的行為或者邊界條件,與文檔和接口描述并不符合。

對(duì)于維護(hù)者來(lái)說(shuō),改動(dòng)這樣的代碼(或者是改動(dòng)影響到了這樣代碼 / 被這樣代碼影響到了)時(shí),如果按照接口描述或者文檔進(jìn)行,沒(méi)發(fā)現(xiàn)隱藏行為,同時(shí)代碼又缺乏足夠測(cè)試覆蓋,那么就存在未知的風(fēng)險(xiǎn)unknown unknowns。這時(shí)出現(xiàn)問(wèn)題是很難避免的。最好的方式還是要盡量避免我們的系統(tǒng)質(zhì)量劣化到這個(gè)程度。

上線時(shí),我們最大的噩夢(mèng)就是unknown unknowns:這類風(fēng)險(xiǎn),我們無(wú)法預(yù)知在哪里或者是否有問(wèn)題,只能在軟件上線后遇到問(wèn)題才有可能發(fā)現(xiàn)。其他的問(wèn)題 尚可通過(guò)努力來(lái)解決(認(rèn)知成本),而unknown unknowns可以說(shuō)已經(jīng)超出了認(rèn)知成本的范圍。我們最希望避免的也是unknown unknowns。

(7)認(rèn)知成本低要不易出錯(cuò),而不是無(wú)腦“簡(jiǎn)化”

從認(rèn)知成本角度來(lái)說(shuō),我們還要認(rèn)識(shí)到,衡量不同方案/寫(xiě)法的認(rèn)知成本,要考慮的是不易出錯(cuò),而不是表面上的簡(jiǎn)化:表面上簡(jiǎn)化可能帶來(lái)實(shí)質(zhì)性的復(fù)雜度上升。

例如,為了表達(dá)時(shí)間段,可以有兩種選擇:

  1. // Time period in seconds. 
  2. void someFunction(int timePeriod);  
  3. // time period using Duration.  
  4. void someFunction(Duration timePeriod); 

在上面這個(gè)例子里面,我們都知道,應(yīng)該選用第二個(gè)方案,即采用Duration作time period,而不是int:盡管Duration本身需要一點(diǎn)點(diǎn)學(xué)習(xí)成本,但是這個(gè)模式可以避免多個(gè)時(shí)間單位帶來(lái)的常見(jiàn)問(wèn)題。

3 影響協(xié)同成本的因素

協(xié)同成本則是增長(zhǎng)這塊模塊所需要付出的協(xié)同成本。什么樣的成本是協(xié)同成本?(1)增加一個(gè)新的特性往往需要多個(gè)工程師協(xié)同配合,甚至多個(gè)團(tuán)隊(duì)協(xié)同配合;(2) 測(cè)試以及上線需要協(xié)調(diào)同步。

(1)系統(tǒng)模塊拆分與團(tuán)隊(duì)邊界

在微服務(wù)化時(shí)代,模塊/服務(wù)的切分和團(tuán)隊(duì)對(duì)齊,更加有利于迭代效率。而模塊拆分和邊界的不對(duì)齊,則讓代碼維護(hù)的復(fù)雜度增加,因這時(shí)新的特性需要在跨多個(gè)團(tuán)隊(duì)的情況下進(jìn)行開(kāi)發(fā)、測(cè)試和迭代。

另外一個(gè)角度,則是:

Any piece of software reflects the organizational structure that produces it.

或者就是我們常說(shuō)的“組織架構(gòu)決定系統(tǒng)架構(gòu)”,軟件的架構(gòu)最后會(huì)圍繞組織的邊界而變化(當(dāng)然也有文化因素),當(dāng)組織分工不合理時(shí),會(huì)產(chǎn)生重復(fù)的建設(shè)或者沖突。

(2)服務(wù)之間的依賴,Composition vs Inheritance/Plugin

軟件之間的依賴模式,常見(jiàn)的有Composition 和Inheritance模式,對(duì)于local模塊/類之間的依賴還是遠(yuǎn)程調(diào)用,都存在類似模式。

 

上圖左側(cè)是Inheritance(繼承或者是擴(kuò)展模式),有四個(gè)團(tuán)隊(duì),其中一個(gè)是Framework團(tuán)隊(duì)負(fù)責(zé)框架實(shí)現(xiàn),框架具有三個(gè)擴(kuò)展點(diǎn),這三個(gè)擴(kuò)展點(diǎn)有三個(gè)不同的團(tuán)隊(duì)實(shí)現(xiàn)插件擴(kuò)展,這些插件被Framework調(diào)用,從架構(gòu)上,這是一種類似于繼承的模式。

右側(cè)是組合模式(composition):底層的系統(tǒng)以API服務(wù)的方式提供接口,而上層應(yīng)用或者服務(wù)通過(guò)調(diào)用這些接口來(lái)實(shí)現(xiàn)業(yè)務(wù)功能。

這兩種模式適用于不同的系統(tǒng)模型。當(dāng)Framework偏向于底層、不涉及業(yè)務(wù)邏輯且相對(duì)非常穩(wěn)定時(shí),可以采用inheritance模式,也即Framework被集成到團(tuán)隊(duì)1,2,3的業(yè)務(wù)實(shí)現(xiàn)當(dāng)中。例如RPC framework就是這樣的模型:RPC底層實(shí)現(xiàn)作為公共的base 代碼/SDK提供給業(yè)務(wù)使用,業(yè)務(wù)實(shí)現(xiàn)自己的RPC 方法,被framework調(diào)用,業(yè)務(wù)無(wú)需關(guān)注底層RPC實(shí)現(xiàn)的細(xì)節(jié)。因?yàn)镕ramework代碼被業(yè)務(wù)所依賴,因此這時(shí)業(yè)務(wù)希望Framework的代碼非常穩(wěn)定,而且盡量避免對(duì)framework層的感知,這時(shí)inheritance是一種比較合適的模型。

然而,我們要慎用Inheritance模式。Inheritance模式的常見(jiàn)陷阱:

(a)要避免出現(xiàn)管理倒置

即Framework層負(fù)責(zé)整個(gè)系統(tǒng)的運(yùn)維(framework團(tuán)隊(duì)負(fù)責(zé)代碼打包、構(gòu)建、上線),那么會(huì)出現(xiàn)額外的協(xié)同復(fù)雜度,影響系統(tǒng)演進(jìn)效率(設(shè)想一下如果Dubbo的團(tuán)隊(duì)要求負(fù)責(zé)所有的使用Dubbo的應(yīng)用的打包、發(fā)布成為一個(gè)大的應(yīng)用,會(huì)是多么的低效)。

(b)要避免破壞業(yè)務(wù)邏輯流程的封閉性

Inheritance模式如果使用不當(dāng),很容易破壞上層業(yè)務(wù)的邏輯抽象完整性,也即“擴(kuò)展實(shí)現(xiàn)1”這個(gè)模塊的邏輯,依賴于其調(diào)用者的內(nèi)部邏輯流程甚至是內(nèi)部實(shí)現(xiàn)細(xì)節(jié),這會(huì)帶來(lái)危險(xiǎn)的耦合,破壞業(yè)務(wù)的邏輯封閉性。

如果你所在的項(xiàng)目采用了插件/Inheritance模式,同時(shí)又出現(xiàn)上面所說(shuō)的管理倒置、破壞封閉性情況,就需要反思當(dāng)前的架構(gòu)的合理性。

而右側(cè)的Composition是更常用的模型:服務(wù)與服務(wù)之間通過(guò)API交互,相互解耦,業(yè)務(wù)邏輯的完整性不被破壞,同時(shí)框架/Infra的encapsulation也能保證。同時(shí)也更靈活,在這種模型下,Service 1, 2, 3 如果需要也可以產(chǎn)生相互調(diào)用。

另外《Effective Java》一書(shū)的Favor composition over inheritance有很好的分析,可以作為這個(gè)問(wèn)題的補(bǔ)充。

(3)可測(cè)試性不足帶來(lái)的協(xié)同成本

交付給其他團(tuán)隊(duì)(包括測(cè)試團(tuán)隊(duì))的代碼應(yīng)該包含充分的單元測(cè)試,具備良好的封裝和接口描述,易于被集成測(cè)試的。然而因?yàn)? 單測(cè)不足/模塊測(cè)試不足,帶來(lái)的集成階段的復(fù)雜度升高、失敗率和返工率的升高,都極大的增加了協(xié)同的成本。因此做好代碼的充分單元測(cè)試,并提供良好的集成測(cè)試支持,是降低協(xié)同成本提升迭代效率的關(guān)鍵。

可測(cè)試性不足,帶來(lái)協(xié)同成本升高,往往導(dǎo)致的破窗效應(yīng):上線越來(lái)越靠運(yùn)氣,unknown unknowns越來(lái)越多。

(4)文檔

降低協(xié)同成本需要對(duì)接口/API提供清晰的、不斷保持更新一致的文檔,針對(duì)接口的場(chǎng)景、使用方式等給出清晰描述。這些工作需要投入,開(kāi)發(fā)團(tuán)隊(duì)有時(shí)不愿意投入,但是對(duì)于每一個(gè)用戶/使用方,需要依賴釘釘上的詢問(wèn)、或者是依靠ATA文章(多半有PR性質(zhì)或者是已經(jīng)過(guò)時(shí),沒(méi)有及時(shí)更新,畢竟ATA不是產(chǎn)品文檔),協(xié)同成本太高,對(duì)于系統(tǒng)來(lái)說(shuō)出現(xiàn)bug/使用不當(dāng)?shù)膸茁蚀鬄樵黾恿恕?/p>

最好的方式:(1)代碼都公開(kāi);(2)文檔和代碼寫(xiě)在一起(README.md, *.md),隨著代碼一起提交和更新,還計(jì)算代碼行數(shù),多好。

4 軟件復(fù)雜度生命周期

 

復(fù)雜度的惡化到一定程度,一定進(jìn)入有諸多unknown unknown的程度。好的工程師一定要能識(shí)別這樣的狀態(tài):可以說(shuō),如果不投入力氣去做一定的重構(gòu)/改造,有過(guò)多unknown unknowns的系統(tǒng),很難避免失敗的厄運(yùn)了。

這張圖是要表明,軟件演進(jìn)的過(guò)程,是一個(gè)“不由自主”就會(huì)滑向過(guò)于復(fù)雜而無(wú)法維護(hù)的深淵的過(guò)程。如何要避免失敗的厄運(yùn)?這篇文章的篇幅不容許我們展開(kāi)討論如何避免復(fù)雜度,但是首要的,對(duì)于真正重要的、長(zhǎng)生命周期的軟件演進(jìn),我們需要做到對(duì)于復(fù)雜度增量零容忍。

5 Good enough vs Perfect

軟件領(lǐng)域,從效率和質(zhì)量的折中,我們會(huì)提“Good enough”即可。這個(gè)理論是沒(méi)錯(cuò)的。只不過(guò)現(xiàn)實(shí)中,我們極少看到“overly good”,因?yàn)檫^(guò)于追求perfection而影響效率的情況。大多數(shù)情況下,我們的系統(tǒng)是根本沒(méi)做到Good enough。

四 對(duì)復(fù)雜度增長(zhǎng)的對(duì)策

每一份新的代碼的引入,都在增加系統(tǒng)的復(fù)雜度:因?yàn)槊恳粋€(gè)類或者方法的創(chuàng)建,都會(huì)有其他代碼來(lái)引用或者調(diào)用這部分代碼,因而產(chǎn)生依賴/耦合,增加系統(tǒng)的復(fù)雜度(除非之前的代碼過(guò)度復(fù)雜unncessarily complex,而通過(guò)重構(gòu)可以降低復(fù)雜度),如果讀者都意識(shí)到了這個(gè)問(wèn)題,并且那些識(shí)別增加復(fù)雜度的關(guān)鍵因素對(duì)于大家有所幫助,那么本文也就達(dá)到了目標(biāo)。

而如何Keep it simple,是個(gè)非常大的話題,本文不會(huì)展開(kāi)。對(duì)于API設(shè)計(jì),在[5]中做了一些總結(jié),其他的希望后續(xù)有時(shí)間能繼續(xù)總結(jié)。

有人會(huì)說(shuō),項(xiàng)目交付的壓力才是最重要的,不要站著說(shuō)話不腰疼。實(shí)際呢?我認(rèn)為絕對(duì)不是這樣。多數(shù)情況下,我們要對(duì)復(fù)雜度增長(zhǎng)采用接近于“零容忍”的態(tài)度,避免“能用就行”,原因在于:

  • 復(fù)雜度增長(zhǎng)帶來(lái)的風(fēng)險(xiǎn)(unknown unknowns、不可控的失敗等)往往是后知后覺(jué)的,等到問(wèn)題出現(xiàn)時(shí),往往legacy已經(jīng)形成一段時(shí)間,或者坑往往是很久以前埋的。
  • 當(dāng)我們?cè)诖a評(píng)審、設(shè)計(jì)評(píng)審時(shí)面臨一個(gè)個(gè)選擇時(shí),每一個(gè)Hack、每一個(gè)帶來(lái)額外成本和復(fù)雜度的設(shè)計(jì)似乎都顯得沒(méi)那么有危害:就是增加了一點(diǎn)點(diǎn)復(fù)雜度而已,就是一點(diǎn)點(diǎn)風(fēng)險(xiǎn)而已。但是每一個(gè)失敗的系統(tǒng)的問(wèn)題都是這樣一點(diǎn)點(diǎn)積累起來(lái)的。
  • 破窗效應(yīng)Broken window:一個(gè)建筑,當(dāng)有了一個(gè)破窗而不及時(shí)修補(bǔ),這個(gè)建筑就會(huì)被侵入住認(rèn)為是無(wú)人居住的、風(fēng)雨更容易進(jìn)來(lái),更多的窗戶被人有意打破,很快整個(gè)建筑會(huì)加速破敗。這就是破窗效應(yīng),在軟件的質(zhì)量控制上這個(gè)效應(yīng)非常恰當(dāng)。所以,Don't live with broken windows (bad designs, wrong decisions, poor code) [6]:有破窗盡快修。

零容忍,并不是不讓復(fù)雜度增長(zhǎng):我們都知道這是不可能的。我們需要的是盡力控制。因?yàn)檫M(jìn)度而臨時(shí)打破窗戶也能接受,但是要盡快補(bǔ)上。

當(dāng)然文章一開(kāi)始就強(qiáng)調(diào)了,如果所寫(xiě)的業(yè)務(wù)代碼生命周期只有幾個(gè)月,那么多半在代碼變得不可維護(hù)之前就可以下線了,那可以不用關(guān)注太多,能用就行。

最后,作為Software engineer,軟件是我們的作品,希望大家都相信:

  • 真正的工程師一定在意自己的作品:我們的作品就是我們的代碼。工匠精神是對(duì)每個(gè)工程師的要求。
  • 我們都可以帶來(lái)改變:代碼是最公平的工作場(chǎng)地,代碼就在那里,只要我們?cè)敢猓湍軒?lái)變化。

Reference

[1]John Ousterhout, A Philosophy of software design

[2]Frederick Brooks, No Silver Bullet - essence and accident in software engineering

[3]Robert Martin, Clean Architecture

[4]https://medium.com/monsterculture/getting-your-software-architecture-right-89287a980f1b

[5]API設(shè)計(jì)最佳實(shí)踐思考 https://developer.aliyun.com/article/701810

[6]Andrew Hunt and David Thomas, The pragmatic programmer: from Journeyman to master

[7]https://testing.googleblog.com/2017/06/code-health-reduce-nesting-reduce.html

[8]https://en.wikipedia.org/wiki/Don%27t_repeat_yourself

[9]http://www.multunus.com/blog/2017/01/naming-the-hardest-software/

 

[10]https://martinfowler.com/bliki/TwoHardThings.html

【本文為51CTO專欄作者“阿里巴巴官方技術(shù)”原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)聯(lián)系原作者】

 

戳這里,看該作者更多好文

 

責(zé)任編輯:武曉燕 來(lái)源: 51CTO專欄
相關(guān)推薦

2020-08-11 07:45:38

軟件測(cè)試

2020-08-10 09:14:50

軟件測(cè)試工具技術(shù)

2020-12-03 10:56:31

軟件開(kāi)發(fā)反饋弧

2018-12-18 10:11:37

軟件復(fù)雜度軟件系統(tǒng)軟件開(kāi)發(fā)

2021-02-21 00:18:47

惡意軟件研究職業(yè)技術(shù)

2024-04-25 08:33:25

算法時(shí)間復(fù)雜度空間復(fù)雜度

2021-01-05 10:41:42

算法時(shí)間空間

2019-01-02 05:55:30

領(lǐng)域驅(qū)動(dòng)軟件復(fù)雜度

2009-07-09 10:45:16

C#基本概念復(fù)雜度遞歸與接口

2015-10-13 09:43:43

復(fù)雜度核心

2022-08-16 09:04:23

代碼圈圈復(fù)雜度節(jié)點(diǎn)

2019-12-24 09:46:00

Linux設(shè)置密碼

2020-12-30 09:20:27

代碼

2020-02-06 13:59:48

javascript算法復(fù)雜度

2022-03-02 09:53:22

計(jì)算Transforme性能

2019-09-06 11:12:53

2022-06-15 18:57:43

人工智能

2020-09-21 14:25:26

Google 開(kāi)源技術(shù)

2020-06-01 08:42:11

JavaScript重構(gòu)函數(shù)

2014-07-01 15:49:33

數(shù)據(jù)結(jié)構(gòu)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 精品国产女人 | 理论片87福利理论电影 | 青青草一区二区三区 | 青青草一区二区 | 成人久久一区 | 一区二区三区四区av | 国产精品久久久久久久久久久久久久 | 中文字幕第一页在线 | 伊人网站在线观看 | 日韩欧美在线视频 | 欧美日韩国产一区二区 | 欧美在线视频一区 | 一区二区三区在线免费观看 | 亚洲毛片| 久久国产精品视频观看 | 成人欧美一区二区三区黑人孕妇 | 欧美电影网 | 国产精品国产三级国产a | 午夜www | 97人人澡人人爽91综合色 | 盗摄精品av一区二区三区 | 二区三区视频 | 视频一区二区中文字幕 | 亚洲欧洲日韩精品 中文字幕 | 精品免费视频一区二区 | 在线成人一区 | 亚洲毛片在线观看 | 久热精品在线 | 亚洲免费在线 | 国产一级淫片免费视频 | 狼人伊人影院 | 欧美一级二级三级视频 | 99久热在线精品视频观看 | 男人天堂久久久 | 天堂久久久久久久 | 午夜精品久久久 | 成人国产在线视频 | 日韩一区二区三区av | 91视在线国内在线播放酒店 | 亚洲精品99999 | 日韩第一区 |