程序的腐化

寫代碼如同打掃屋子,有句話叫一屋不掃何以掃天下。如果單個的一個模塊代碼都不能管好,如何成就一個完善的軟件系統?今天我們來說說,一個代碼模塊的代碼是如何一步步腐化變質,到最後程序員都不願意去維護它,然後要麼重構,要麼廢棄換新模塊的?

代碼是有一定的週期的,這個沒有錯。爲什麼有的代碼跑上幾十年任然好用,而現在互聯網公司的很多代碼,每年都要做好幾次重構?一個成立2年的互聯網公司,做一個支付系統,可以做了4-5代,每次重構,這樣的代價有多大?如何才能讓原有的代碼生命週期更加長,而不增加很多的學習維護成本,開發一次使用更久呢? 大部分程序員是沒有很多機會從0開始搭建一個新程序的,更多的時候是接手別人寫的代碼。有代碼移交還好一點,往往因爲各種因素,這些因素你懂的,沒有產品文檔,沒有設計文檔,沒有程序說明,程序裏可能連註釋都沒有。然後,程序員更新換代又極其的快,互聯網時代,程序員在一個公司的平均年資也就1年多,程序就又被傳給下一任維護者。很大可能的情況是,最終到你手裏的程序各種問題,卻能實現基本的功能需求,但代碼內部各種問題讓程序員總有一個衝動,重構它。今天不想說重構的問題,而是從根源角度分析,程序爲什麼會變成這個樣子?

什麼是程序的腐化?

什麼是一個軟件的質量?一個分類標準是軟件外部質量與軟件內部質量的統一,外部質量是對外表現是否正常,內部質量是對後續開發有沒有坑,就是我在這裏說的軟件有沒有腐化。內部質量標準有:可維護性,靈活性,可移植性,可重用性,可測試性,可理解性(摘錄自代碼大全)。不符合以上標準都可以稱之爲代碼腐化,形象的理解就是一個蘋果,從內部開始爛了,爛到原本應該負責內部代碼的程序員拒絕去維護了。

實際的代碼腐化的例子:

代碼混亂,沒有代碼規範不該連數據庫的模塊連了數據庫模塊間的調用混亂:模塊內部的調用混亂,例如C#代碼已經使用了EntityFramework,代碼中跳過EntityFramework,直接用數據庫連接修改數據。框架與其他不一致,不統一:有的包管理使用gradle管理,有的使用maven。有的後臺用.Net,有的用Node,有的用Java。用了HttpClient,又使用Feign去連接其他應用模塊有一些設計前後不一致:有的代碼使用了統一的錯誤定義CommonException,有的用原生的Exception。微服務模塊,有resource層接口,定義訪問的路徑,resource的Impl,service的接口提供具體的數據接口,serviceImpl提供具體數據獲取的實現。而在具體編碼時,將大量的業務邏輯寫入了resource的實現中。太複雜的抽象不能做方便的變更:一開始設計的Job系統,上面是2-3張圖片,下面是動態生成的問題。代碼層面對於此設計做了很細緻的抽象。突然產品提出了某一個Job的圖片有特別,要求顯示10張圖片,就對抽象的圖片部分做了if-else的處理……無用代碼,廢棄的接口沒有標明

代碼腐化的原因

沒有代碼會是init commit的時候就開始腐化的,腐化都是循序漸進的,要一個過程。我總結了一些代碼腐化的原因:

沒有統一標準,或者沒有嚴格執行

  • 統一標準之代碼規範

每個程序員都是有自己的審美的,例如即使是縮進長度這種代碼裏不影響任何功能的東西,有的喜歡空4格,有的喜歡2格。有的喜歡黑色的編程背景,有的喜歡白色的編程背景。有的喜歡if後直接跟上左括號,有的就喜歡另起一行。代碼規範還是要有的,包含各種格式定義,大小寫規範,命名規範等。前端有各種lint工具(jslint,tslint)可以幫助規範,後臺的ide也有一些方法幫助。像Baidu,Google這樣的公司還有構建時的自開發的檢查工具,所以常常一個資深程序員第一次開發的代碼要花上1-2天才能提交通過。代碼規範的混亂,直接導致代碼可讀性的降低。可讀性直接影響後續的生產力。一個程序員天天對着看不順眼的代碼,怎麼可能高效?

  • 統一標準之基礎規範

除了代碼規範外,項目命名,通訊方式,基本的程序框架,後端Java的springboot,sprintMVC,前端的angular,vue,react等都需要統一,還有統一的基礎環境(eureka,elk,redis,apigateway等)。不統一的後果是各種部署,管理,編碼的低效。例如搭一個jenkins,然後部署服務A用的Maven,服務B用的gradle,就導致編譯代碼寫2套,如果寫一套基本一樣的,當然會快一些。 我統計的java代碼中可以統一的部分(包含但不限於)Http調用格式,統一用content-type:application/json,response也統一要求這樣。HttpClient的標準化框架,如SpringBoot項目管理工具:Maven,Gradle項目的CI,CD配置管理模式,例如統一成一個配置文件application.properties環境變量配置方式,qa,stag,prod。不要有的人寫stage,staging,也不要寫成production等等細節代碼基礎結構:例如標準的maven目錄的結構

程序的腐化

項目命名方式:com.(公司名).(開發組名).(系統名).(模塊名)例如:com.omniprimeinc.cosmetic.application.server;Restful接口設計統一:大小寫,命名方式,Body的最大大小例如,Post接口是否可以加PathParameter和QueryParameter。Post接口是否可以不帶Body。其他配套功能的統一性:調用鏈,動態配置管理,緩存,分佈式事物數據庫的統一:統一數據庫,數據庫版本,是否可以使用存儲過程等。關於數據庫統一性不在這裏展開,這點也非常的重要。

  • 統一規範之公司統一框架

剛纔說的統一,很多是從公司層面的統一,如果大家都只用springboot,都沿用統一的後端框架,前端統一用angular。那麼這個時候,爲了方便統一,就需要有代碼相關的腳手架工具,直接生成基本的統一項。這樣一個工具的好處是可以直接一鍵完成許多基礎工作,並完成了底層的統一工作。

多頭維護

代碼腐化的一個很重要的因素是多頭維護,甚至是多代維護。一個公共項目,多個開發團隊都在維護,那就很難統一標準。初始版本有一個架構,然後換了一個架構,開發更是換了幾批。人多手雜說的就是這樣的情況。任何開發團隊接手一箇舊項目時,其實都是有學習和適應的成本的。頻繁的變更開發人員帶來的壞處就是反覆的人爲製造這個成本,其次就是有機率丟失之前的一部分標準和架構規劃。

架構沒有落地

代碼模塊的功能設計規劃,制定的標準,沒有詳細的落地。架構定了一套,開發沒有嚴格執行。每天寫代碼這麼忙,架構只管架構,不管細節。開發每天擼代碼,只管功能,不管架構和代碼質量(這個質量不是指功能實現上的質量,而是說嚴格執行各項統一標準的程度)。甚至說,一個高層服務,不能調用同級服務,只能調用底層服務。因爲開發的沒有嚴格執行,甚至加了數據庫連接,直接去取了數據庫,這樣的事一旦開了口子,就像黃河決堤,不可收拾了。所以,以上說的是架構的落地落實很重要,讓所有具體的開發參與者落實同一個標準。架構就需要落實相關的設計,相關的文檔,相應的執行檢查。現實的情況從來沒有靠文檔解決一切問題的,可它能解決80%的問題,另外就儘量減少開發人員的變動,以減小換人帶來的代碼腐化問題。

防止代碼腐化的建議:

代碼規範標準化,統一化治理

代碼的內部質量其實很難保證,規範執行也更多的靠人治,甚至個別標準化的東西,只能通過代碼層面去檢驗,無法通過測試或其他手段進行。另外,雖然有一些通行的默認標準,更多的標準是代碼的負責人自行確定的標準,完全根據喜好來,就像前面說的縮進的長度。好比是老媽和丈母孃都跑來你家裏幫你打掃衛生,老媽喜歡把廚房裏的鍋都一路洗好掛起來,丈母孃喜歡找一個櫥櫃,都放在櫥櫃裏,你能怎麼辦?沒關係,只要確定下一套標準,不要經常改就好了。

架構嚴格落地

像前面說的標準,特別是自定義的標準,都需要落地。第一優先文檔,第二是團隊內部達成共識,第三是執行。

嚴格的codereview

防代碼的腐化執行是一個關鍵點,這個關鍵點就是codereview。在這個時間點,再次與開發強調標準的重要性,讓開發知曉執行,讓測試監督,讓架構嚴格檢查。對於不符合的,開發要再次去學習執行標準。

減少開發人員的變動

如果一個團隊內,原本標準就是統一的,團隊內實行敏捷開發,任何一個開發都可以替代其他開發工作,那麼兩個人交換任務就沒有問題。如果團隊內都不統一,這個變動就會嚴重影響開發,代碼腐化的可能會變的很大。而團隊間,標準統一的可能性比在團隊內更難,要避免團隊間的項目移交纔是最優的方法。

代碼模塊架構Keep it Simple&Stupid,使用一眼就明白的架構

現實世界中,業務需求永遠的跑在技術需求前面,很可能架構相關的設計沒有文檔,沒有說明,一旦架構師不在,原來的開發換人了,導致原先的標準和設計無法繼續下去,一旦有交接,標準的丟失和架構的變化不可避免。這時,此條原則就能發揮作用。要使用傻瓜化的架構,就是任何一個新來的程序員,一眼就能看明白的架構。例如使用公司統一的項目代碼生成器,腳手架,生成統一的代碼結構,不需要程序員再投入代碼結構學習的成本。而且通用架構就意味着,後續的架構師接受原先設計的可能性大增。

程序的腐化

分層的概念很早就提出來了,爲什麼MVC的概念會這麼受歡迎?我覺得是因爲它足夠傻瓜化。數據庫的架構結構中,定義repository,service接口,serviceImpl的實現等也成爲了很通用性的設計。有一個挺經典的例子就是asp.net.MVC的程序組織架構三個文件夾:Controllers,Models,Views直接約定,並強調約定大於配置。只要是開發過這類程序的程序員,都知道從Controller看路徑和RestApi入口,Models看數據結構,View看Html視圖。就不需要額外的學習成本。Maven的標準目錄結構也是一個例子。(見上圖)

定期清理維護

如同像打掃房間一樣,一段時間不打掃,自然會有邊邊角角的髒東西出來。那麼有沒有定期的去清理呢?還是不管他,每次看到地上的一團紙,都繞過,而不是去扔了?定期的清理維護不單是一個維護代碼的過程,更是一個重新梳理和統一標準的過程,讓現有的開發和架構,再次的達成一致,以提高戰鬥力

防止代碼膨脹

微服務概念的提出後,很好的解決了一個問題,我們的一個代碼模塊應該寫多少大的問題?一個模糊的建議是一個Sprint能重寫的大小,如果更大,就應該要差分。有時候代碼的清理維護工作也要以這個原則來處理,不能出現過大的代碼模塊。因爲過大的代碼模塊,首先帶來的是程序的複雜度,讓程序員理解起來要更多成本。其次,模塊內部的耦合度必然也提高了,增加了難度。這時,需要的是切分出一個新模塊出來。

總結

代碼腐化是程序開發的一個經典問題。代碼內部質量的降低,外部質量確可以被客戶接受。程序員經常想着,等業務沒那麼忙的時候,做點清理,做點模塊局部的微重構,我的經驗告訴你,這些都是假的。如果定下了定期清理,定期檢查拆分,就必須立馬去做。做這些事的優先級要遠高於業務需求。 爲什麼呢?想象一個程序C++的printf,裏面的代碼寫的非常的爛,變量命名都是p,m,k完全不能直觀理解含義,我們需要鄭重的去處理這個模塊的代碼腐化問題嗎?其實不需要,因爲printf模塊的代碼需求,就是把輸入的內容打印出來,永遠都是這樣,沒有任何新需求,沒有新需求就代表沒有程序員需要深入內部去改它的代碼,不需要改的代碼,我們是沒有動力去解決它的腐化問題的。有必要經常深入解決腐化問題的代碼,必然是業務需求很多,經常要變更的代碼。不要等到它已經腐化到代碼生命週期都快走到頭了,纔想起來去維護清理它,要經常維修一下,才能更好的讓它發揮作用,是吧?一輛不打算開的老爺車,發動機壞了就壞了,一輛經常開的二手奔馳,定期維護少不了。

相关文章