|
1、軟件長期運(yùn)營存在什么問題
一個大規(guī)模的客戶端軟件的生命周期中,我們可以把它分為兩個比較粗的時期。一個是前期的搭建軟件的時期,即從無到有的時期;第二個是搭建完成之后,進(jìn)入的一個穩(wěn)定的運(yùn)營時期。第二個時期才是最關(guān)鍵的,在這個時期我們會持續(xù)的迭加需求,持續(xù)的優(yōu)化功能,而且第二個時期也是代碼在慢慢變質(zhì)的時期。
在這個時期,你可能會發(fā)現(xiàn):我們的軟件慢慢出現(xiàn)模塊耦合嚴(yán)重,牽一發(fā)而動全身;每個版本都會涌現(xiàn)出老功能的BUG,你沒動過的模塊也會出BUG;或者改了一個小問題了,帶出來很多其他問題;缺乏擴(kuò)展性,往老模塊加新功能非常痛苦;程序的崩潰率越來越高;新員工接手老模塊經(jīng)常不能理解原來的設(shè)計思想而改壞;移植一個DLL到另一個軟件時,發(fā)現(xiàn)必須連帶也移植十幾個DLL。本文將分享對于這些問題的思考與方法。
2. 軟件的積木模型
一個運(yùn)營型的客戶端軟件,做出來就是為了長期運(yùn)營,需要不斷的迭加功能。而不是做出來,兩三年就重寫一次。那么這樣一個軟件就像堆積木一樣。一個軟件剛開始寫了兩千行代碼,感覺設(shè)計得非常好,模塊化擴(kuò)展性都非常好,性能也非常快,都能很好的面向運(yùn)營。寫了兩三年之后,就會出現(xiàn)像這種積木一樣的結(jié)構(gòu),很容易崩塌。所謂重構(gòu),形象的說,可以看做是某個積木不穩(wěn)定,要往里塞一塞。那么整個開發(fā)過程,就是一個不斷迭代、不斷優(yōu)化、不斷重構(gòu)的過程。對于我們這個積木模形,有什么辦法不讓一些木條跑出來,這也是我們需要想的思路。我們是不是可以先圍四面墻,然后在墻里面再去塔積木?
3. 導(dǎo)致代碼變質(zhì)的兩大因素
團(tuán)隊中總是會存在這樣那樣的問題,這些問題最終總是影響到我們的代碼朝著不良的方向發(fā)展。對于這些因素,我可以將它們抽象為兩大類。一類是人的因素:比如架構(gòu)設(shè)計不合理,需求沒考慮清楚,項目進(jìn)度壓力,溝通問題,缺少文檔、培訓(xùn),等等。另一類是時間的因素:比如人員的變動,需求的長期迭加和變更,等等。人的因素是由于人本身的素質(zhì)或疏忽導(dǎo)致,時間因素是由于時間的長期推進(jìn)導(dǎo)致,即使人的素質(zhì)很高也必然會出現(xiàn)時間因素的問題。
4. 代碼變亂的微觀原因
在上述兩大類因素的長期作用下,最終會導(dǎo)致代碼越來越亂。如果從微觀的角度來剖析,這跟依賴有著很大的關(guān)系。代碼的變亂,根本原因就是由于太多不良依賴或者模塊失去單一性所致。我們來看一下依賴是如何產(chǎn)生的。
1). 依賴的方式
如下圖所示,如果組件A依賴于B,B依賴于C,A也是隱含的依賴于C的。組件A不能單獨(dú)使用,必須同B和C一起使用。在現(xiàn)實的代碼中,可能存在著非常長的依賴鏈。
依賴的方式也可能是多種多樣的,單向依賴、雙向依賴、環(huán)狀依賴或者一個依賴于多個。下圖也是一些示例,現(xiàn)實的代碼中可能是由各種依賴方式組成的非常復(fù)雜的網(wǎng)狀結(jié)構(gòu)。
2). 依賴的變化
在兩大類因素的作用下,依賴會發(fā)生變化。最常見的變化應(yīng)該是依賴的箭頭越來越多,網(wǎng)狀結(jié)構(gòu)變得越來越復(fù)雜。如果沒有增加新的組件,下圖中左邊的圖往往會變成右邊的圖。起初設(shè)計好的很好的代碼,可能是左邊的樣子,模塊具有很好的獨(dú)立性和可移植性。隨著時間、需求、人的變化,很可能由開發(fā)人員很隨意的一行代碼,就變成了右邊的圖,一條紅線就出來了。兩個模塊變成相互依賴,上面那個模塊就不再有獨(dú)立性和可移值性。
我們的代碼從設(shè)計之初到現(xiàn)在,中間經(jīng)過了幾年的時間,代碼變得越來越亂很大的原因是因為這種紅線的持續(xù)出現(xiàn)。本來有很多獨(dú)立性很好的模塊,變成了錯綜復(fù)雜的網(wǎng)狀結(jié)構(gòu)。
前面是沒有引入新組件的情況,如果引入了新組件,必然會引入新的引賴,那么就要好好的去界定,引入的新組件是屬于哪個層面的。像下面第一個圖,新引入的組件依賴于原來兩個組件是在最上層,第二個圖新引入的組件是在中間層,第三個圖新引入的組件被另外兩個組件依賴在最底層。
引入新組件,其實應(yīng)該做好充份的考慮,而不是讓開發(fā)人員隨意的引入。需要充份思考引入的新組件應(yīng)該放在哪一層面才是最合理的,才有利于以后的擴(kuò)展和移植。
可能讀者會遇到這種情況,一個功能編譯沒有問題,測試也沒有問題,發(fā)布后一兩年也沒有問題。當(dāng)我們要把這個功能移植出來的時候,才發(fā)現(xiàn)問題大了。你想移植一個組件到另一個軟件時,必須連帶也移植十幾個組件。
5. 如何解決依賴
1). 組件網(wǎng)圖
要解決依賴,首先要發(fā)現(xiàn)哪些是不正確的依賴。下圖就是一個具有良好層次的依賴關(guān)系圖,我們稱之為“組件網(wǎng)圖”。對于我們現(xiàn)實的軟件中,我們非常需求這樣一張圖將整個軟件所有組件的依賴關(guān)系繪制出來,以便于我們發(fā)現(xiàn)其中的錯誤依賴進(jìn)行解決。
如果組件網(wǎng)圖中存在錯誤的依賴關(guān)系,或者如果有需求要求圖中的組件h依賴于g,應(yīng)該怎么辦?可以通過下面的“分解適配”和“升級降級”的方法進(jìn)行解決。
2). 分解適配(單一職責(zé))
分解適配是指將一個功能復(fù)雜的模塊分解為多個具有單一職責(zé)的模塊,那么模塊間的依賴關(guān)系也會變得單純。讀者可以結(jié)合下面的案例理解這個方法。
3). 升級降級
我們經(jīng)常會做重構(gòu),對于上面那張組件網(wǎng)圖來說,重構(gòu)就是將不合理的依賴斷開,把更通用的邏輯抽出來放在底層,將不能用的邏輯放在上層。重構(gòu)其實就是不斷升級和降級的過程。比如說我們前面的圖,如果H依賴于G了,那么可能考慮將G進(jìn)行分解適配,將G分為G1和G2,將G2和H合并為一個新組件。這樣就完成了一個分解適配和升級降級的過程。
6. 處理依賴的方法論
1). 通用的模塊不要依賴于不通用的模塊
我們進(jìn)行層次劃分,通常是通用的模塊放到底層,不通用的模塊放在上層,不通用的模塊依賴通用的模塊是合情合理的。反過來,如果通用的模塊依賴于不通用的模塊,那么這個通用的模塊也會變得不通用。
2). 之前的創(chuàng)建模塊盡量不要依賴于后創(chuàng)建的模塊
根據(jù)時間軸以及產(chǎn)品的發(fā)展,較早開發(fā)的需求一般都是通用的或者是基礎(chǔ)性質(zhì)的需求,而后開發(fā)的需求是業(yè)務(wù)型的需求為主。根據(jù)這個性質(zhì),后開發(fā)的需求應(yīng)該大部分依賴于之前的特性,比較少的情況是讓之前的需求依賴于一個后來的需求,當(dāng)然一些需求變更可能會引發(fā)這個現(xiàn)象。后創(chuàng)建的模塊雖然可以依賴之前創(chuàng)建的模塊,但是盡量不要去修改原來創(chuàng)建的模塊,如果出現(xiàn)這種情況,也要考慮一下這個修改是不是合理的。
3). 需要進(jìn)行微觀分層(組件網(wǎng)圖)
日常開發(fā)中,需要有一張組件網(wǎng)圖展現(xiàn)在開發(fā)人員的面前,使得開發(fā)人員在能意識到哪些依賴是不應(yīng)該出現(xiàn)的。當(dāng)然,在開發(fā)一個功能之前,也應(yīng)該進(jìn)行微觀層次的設(shè)計,之后再進(jìn)行代碼的編寫。
7. 增加功能三步法
我們拿到一份需求,需要增加一個功能,應(yīng)該怎么做?如果新功能與原先的模塊有依賴的時候,如果是經(jīng)驗欠缺的同事,他們會怎么去做呢?會不會考慮說架構(gòu)會不會合理?經(jīng)驗欠缺的同事可能通常都不會這么考慮,他們只是集中于能不能把需求實現(xiàn),而不是考慮這樣用架構(gòu)上合不合理。團(tuán)隊就應(yīng)該有規(guī)范去約束經(jīng)驗欠缺的同事不去犯錯誤。這里有一個增加功能的三步法供讀者參考,這些方法可能不完善,讀者可能有更好的方法,應(yīng)該尋找適合自己團(tuán)隊的解決辦法。
1). 不修改依賴,不修改或增加接口
假設(shè)原來就有兩個模塊,一個在上層一個在底層,如果需要新寫一個功能,第一步需要先考慮的是,我能不能在上層寫代碼,不修改兩個模塊的依賴,不修改也不增加接口,我的需求能不能滿足。假如說已經(jīng)有現(xiàn)成的接口和現(xiàn)成的依賴,首先就要考慮能不能利用現(xiàn)成的接口來完成需求。在沒有規(guī)范約束的情況下,可能很多時候這個模塊改一下,那個模塊也改一下,就把需求做完了。
2). 不修改依賴,但增加接口
如果第一步不滿足需求的情況下,我們才考慮第二步,不要修改依賴,但是修改接口,這個接口可能就是一個比較通用的,而不是針對特定需求的,新增接口需要考慮擴(kuò)展性和通用性。很多場景其實到這一步都可能滿足的。
3). 修改內(nèi)部依賴
如果第二步還不能滿足需求,必然會導(dǎo)致模塊的耦合,底層如果依賴于上層,就要重新考慮將組件依賴圖進(jìn)行一些調(diào)整,就必須做一些重構(gòu),進(jìn)行升級降級,完全耦合的兩個模塊甚至可以合二為一。
8. 組件網(wǎng)圖的自動化監(jiān)控
隨時時間的推移,代碼中的依賴越來越多,如何將代碼依賴的變化有效的監(jiān)控起來。建議團(tuán)隊開發(fā)一個監(jiān)控組件網(wǎng)圖變化的工具,一旦有開發(fā)人員把依賴搞亂,工具就會發(fā)出郵件進(jìn)行報警。一個依賴層次正常的組件網(wǎng)圖,是不會出現(xiàn)環(huán)狀依賴的。我們可以將環(huán)狀依賴作為代碼變亂的一個客觀依據(jù)。所以組件網(wǎng)圖工具可以做成只要發(fā)現(xiàn)環(huán)狀依賴,就發(fā)出郵件報告給開發(fā)人員進(jìn)行重構(gòu)。組件網(wǎng)圖工具應(yīng)該每天夜里定期運(yùn)行,找到當(dāng)天新修改的代碼中是否引出新的依賴和環(huán)狀依賴,及時修改。
9. 讓架構(gòu)去保證開發(fā)人員不犯錯
防止代碼變亂,我們可以進(jìn)行各種培訓(xùn)提高開發(fā)人員的素質(zhì),開發(fā)前的設(shè)計評審,開發(fā)后的代碼檢視,或者是監(jiān)控工具每天的檢查。更重要的應(yīng)該是從架構(gòu)上去保證開發(fā)人員不會犯錯誤,就像前面提到的積木模型,先將四面墻圍起來再進(jìn)行積木的搭建。
我們怎么在架構(gòu)上讓開發(fā)人員方便的進(jìn)行解耦?比如我們有一個通用的界面,界面上會插入各種業(yè)務(wù)圖標(biāo),我們不能讓一個通用的界面去依賴于各個具體的業(yè)務(wù),所以應(yīng)該設(shè)計一套插入體系:在界面上留了一些位置,讓業(yè)務(wù)插進(jìn)來。這就從架構(gòu)上訪止這種耦合,后續(xù)開發(fā)人員需要繼續(xù)加圖標(biāo),他不會在通用界面上去調(diào)用業(yè)務(wù)的接口獲取圖標(biāo),因為現(xiàn)有機(jī)制很難這樣做。所以只要架構(gòu)上設(shè)計考慮充份,是可以讓后來的開發(fā)人員不要犯錯誤的。
it知識庫:防止代碼變質(zhì)的思考與方法,轉(zhuǎn)載需保留來源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請第一時間聯(lián)系我們修改或刪除,多謝。