軟件:防止代碼變質的思考與方法

本文轉自:http://impd.tencent.com/?p=100

1、軟件長期運營存在什麼問題

一個大規模的客戶端軟件的生命週期中,我們可以把它分爲兩個比較粗的時期。一個是前期的搭建軟件的時期,即從無到有的時期;第二個是搭建完成之後,進入的一個穩定的運營時期。第二個時期纔是最關鍵的,在這個時期我們會持續的迭加需求,持續的優化功能,而且第二個時期也是代碼在慢慢變質的時期。 在這個時期,你可能會發現:我們的軟件慢慢出現模塊耦合嚴重,牽一髮而動全身;每個版本都會涌現出老功能的BUG,你沒動過的模塊也會出BUG;或者改了一個小問題了,帶出來很多其他問題;缺乏擴展性,往老模塊加新功能非常痛苦;程序的崩潰率越來越高;新員工接手老模塊經常不能理解原來的設計思想而改壞;移植一個DLL到另一個軟件時,發現必須連帶也移植十幾個DLL。本文將分享對於這些問題的思考與方法。 

2. 軟件的積木模型

一個運營型的客戶端軟件,做出來就是爲了長期運營,需要不斷的迭加功能。而不是做出來,兩三年就重寫一次。那麼這樣一個軟件就像堆積木一樣。一個軟件剛開始寫了兩千行代碼,感覺設計得非常好,模塊化擴展性都非常好,性能也非常快,都能很好的面向運營。寫了兩三年之後,就會出現像這種積木一樣的結構,很容易崩塌。所謂重構,形象的說,可以看做是某個積木不穩定,要往裏塞一塞。那麼整個開發過程,就是一個不斷迭代、不斷優化、不斷重構的過程。對於我們這個積木模形,有什麼辦法不讓一些木條跑出來,這也是我們需要想的思路。我們是不是可以先圍四面牆,然後在牆裏面再去塔積木?

3. 導致代碼變質的兩大因素

團隊中總是會存在這樣那樣的問題,這些問題最終總是影響到我們的代碼朝着不良的方向發展。對於這些因素,我可以將它們抽象爲兩大類。一類是人的因素:比如架構設計不合理,需求沒考慮清楚,項目進度壓力,溝通問題,缺少文檔、培訓,等等。另一類是時間的因素:比如人員的變動,需求的長期迭加和變更,等等。人的因素是由於人本身的素質或疏忽導致,時間因素是由於時間的長期推進導致,即使人的素質很高也必然會出現時間因素的問題。 

4. 代碼變亂的微觀原因

在上述兩大類因素的長期作用下,最終會導致代碼越來越亂。如果從微觀的角度來剖析,這跟依賴有着很大的關係。代碼的變亂,根本原因就是由於太多不良依賴或者模塊失去單一性所致。我們來看一下依賴是如何產生的。
  • 依賴的方式

如下圖所示,如果組件A依賴於B,B依賴於C,A也是隱含的依賴於C的。組件A不能單獨使用,必須同B和C一起使用。在現實的代碼中,可能存在着非常長的依賴鏈。

依賴的方式也可能是多種多樣的,單向依賴、雙向依賴、環狀依賴或者一個依賴於多個。下圖也是一些示例,現實的代碼中可能是由各種依賴方式組成的非常複雜的網狀結構。

  • 依賴的變化

在兩大類因素的作用下,依賴會發生變化。最常見的變化應該是依賴的箭頭越來越多,網狀結構變得越來越複雜。如果沒有增加新的組件,下圖中左邊的圖往往會變成右邊的圖。起初設計好的很好的代碼,可能是左邊的樣子,模塊具有很好的獨立性和可移植性。隨着時間、需求、人的變化,很可能由開發人員很隨意的一行代碼,就變成了右邊的圖,一條紅線就出來了。兩個模塊變成相互依賴,上面那個模塊就不再有獨立性和可移值性。

我們的代碼從設計之初到現在,中間經過了幾年的時間,代碼變得越來越亂很大的原因是因爲這種紅線的持續出現。本來有很多獨立性很好的模塊,變成了錯綜複雜的網狀結構。

前面是沒有引入新組件的情況,如果引入了新組件,必然會引入新的引賴,那麼就要好好的去界定,引入的新組件是屬於哪個層面的。像下面第一個圖,新引入的組件依賴於原來兩個組件是在最上層,第二個圖新引入的組件是在中間層,第三個圖新引入的組件被另外兩個組件依賴在最底層。

引入新組件,其實應該做好充份的考慮,而不是讓開發人員隨意的引入。需要充份思考引入的新組件應該放在哪一層面纔是最合理的,才有利於以後的擴展和移植。 可能讀者會遇到這種情況,一個功能編譯沒有問題,測試也沒有問題,發佈後一兩年也沒有問題。當我們要把這個功能移植出來的時候,才發現問題大了。你想移植一個組件到另一個軟件時,必須連帶也移植十幾個組件。

5. 如何解決依賴

  • 組件網圖

要解決依賴,首先要發現哪些是不正確的依賴。下圖就是一個具有良好層次的依賴關係圖,我們稱之爲“組件網圖”。對於我們現實的軟件中,我們非常需求這樣一張圖將整個軟件所有組件的依賴關係繪製出來,以便於我們發現其中的錯誤依賴進行解決。 

如果組件網圖中存在錯誤的依賴關係,或者如果有需求要求圖中的組件h依賴於g,應該怎麼辦?可以通過下面的“分解適配”和“升級降級”的方法進行解決。

  • 分解適配(單一職責)

分解適配是指將一個功能複雜的模塊分解爲多個具有單一職責的模塊,那麼模塊間的依賴關係也會變得單純。讀者可以結合下面的案例理解這個方法。
  • 升級降級

我們經常會做重構,對於上面那張組件網圖來說,重構就是將不合理的依賴斷開,把更通用的邏輯抽出來放在底層,將不能用的邏輯放在上層。重構其實就是不斷升級和降級的過程。 比如說我們前面的圖,如果H依賴於G了,那麼可能考慮將G進行分解適配,將G分爲G1和G2,將G2和H合併爲一個新組件。這樣就完成了一個分解適配和升級降級的過程。 

6. 處理依賴的方法論

  • 通用的模塊不要依賴於不通用的模塊

我們進行層次劃分,通常是通用的模塊放到底層,不通用的模塊放在上層,不通用的模塊依賴通用的模塊是合情合理的。反過來,如果通用的模塊依賴於不通用的模塊,那麼這個通用的模塊也會變得不通用。
  • 之前的創建模塊儘量不要依賴於後創建的模塊

根據時間軸以及產品的發展,較早開發的需求一般都是通用的或者是基礎性質的需求,而後開發的需求是業務型的需求爲主。根據這個性質,後開發的需求應該大部分依賴於之前的特性,比較少的情況是讓之前的需求依賴於一個後來的需求,當然一些需求變更可能會引發這個現象。後創建的模塊雖然可以依賴之前創建的模塊,但是儘量不要去修改原來創建的模塊,如果出現這種情況,也要考慮一下這個修改是不是合理的。
  • 需要進行微觀分層(組件網圖)

日常開發中,需要有一張組件網圖展現在開發人員的面前,使得開發人員在能意識到哪些依賴是不應該出現的。當然,在開發一個功能之前,也應該進行微觀層次的設計,之後再進行代碼的編寫。 

7. 增加功能三步法

我們拿到一份需求,需要增加一個功能,應該怎麼做?如果新功能與原先的模塊有依賴的時候,如果是經驗欠缺的同事,他們會怎麼去做呢?會不會考慮說架構會不會合理?經驗欠缺的同事可能通常都不會這麼考慮,他們只是集中於能不能把需求實現,而不是考慮這樣用架構上合不合理。團隊就應該有規範去約束經驗欠缺的同事不去犯錯誤。這裏有一個增加功能的三步法供讀者參考,這些方法可能不完善,讀者可能有更好的方法,應該尋找適合自己團隊的解決辦法。
  • 不修改依賴,不修改或增加接口

假設原來就有兩個模塊,一個在上層一個在底層,如果需要新寫一個功能,第一步需要先考慮的是,我能不能在上層寫代碼,不修改兩個模塊的依賴,不修改也不增加接口,我的需求能不能滿足。假如說已經有現成的接口和現成的依賴,首先就要考慮能不能利用現成的接口來完成需求。在沒有規範約束的情況下,可能很多時候這個模塊改一下,那個模塊也改一下,就把需求做完了。
  • 不修改依賴,但增加接口

如果第一步不滿足需求的情況下,我們才考慮第二步,不要修改依賴,但是修改接口,這個接口可能就是一個比較通用的,而不是針對特定需求的,新增接口需要考慮擴展性和通用性。很多場景其實到這一步都可能滿足的。
  • 修改內部依賴

如果第二步還不能滿足需求,必然會導致模塊的耦合,底層如果依賴於上層,就要重新考慮將組件依賴圖進行一些調整,就必須做一些重構,進行升級降級,完全耦合的兩個模塊甚至可以合二爲一。 

8. 組件網圖的自動化監控

隨時時間的推移,代碼中的依賴越來越多,如何將代碼依賴的變化有效的監控起來。建議團隊開發一個監控組件網圖變化的工具,一旦有開發人員把依賴搞亂,工具就會發出郵件進行報警。一個依賴層次正常的組件網圖,是不會出現環狀依賴的。我們可以將環狀依賴作爲代碼變亂的一個客觀依據。所以組件網圖工具可以做成只要發現環狀依賴,就發出郵件報告給開發人員進行重構。組件網圖工具應該每天夜裏定期運行,找到當天新修改的代碼中是否引出新的依賴和環狀依賴,及時修改。 

9. 讓架構去保證開發人員不犯錯

防止代碼變亂,我們可以進行各種培訓提高開發人員的素質,開發前的設計評審,開發後的代碼檢視,或者是監控工具每天的檢查。更重要的應該是從架構上去保證開發人員不會犯錯誤,就像前面提到的積木模型,先將四面牆圍起來再進行積木的搭建。 我們怎麼在架構上讓開發人員方便的進行解耦?比如我們有一個通用的界面,界面上會插入各種業務圖標,我們不能讓一個通用的界面去依賴於各個具體的業務,所以應該設計一套插入體系:在界面上留了一些位置,讓業務插進來。這就從架構上訪止這種耦合,後續開發人員需要繼續加圖標,他不會在通用界面上去調用業務的接口獲取圖標,因爲現有機制很難這樣做。所以只要架構上設計考慮充份,是可以讓後來的開發人員不要犯錯誤的。

發佈了205 篇原創文章 · 獲贊 30 · 訪問量 143萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章