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