程式的腐化

codebay發表於2018-05-27

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

程式碼是有一定的週期的,這個沒有錯。為什麼有的程式碼跑上幾十年任然好用,而現在網際網路公司的很多程式碼,每年都要做好幾次重構?一個成立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模組的程式碼需求,就是把輸入的內容列印出來,永遠都是這樣,沒有任何新需求,沒有新需求就代表沒有程式設計師需要深入內部去改它的程式碼,不需要改的程式碼,我們是沒有動力去解決它的腐化問題的。有必要經常深入解決腐化問題的程式碼,必然是業務需求很多,經常要變更的程式碼。不要等到它已經腐化到程式碼生命週期都快走到頭了,才想起來去維護清理它,要經常維修一下,才能更好的讓它發揮作用,是吧?一輛不打算開的老爺車,發動機壞了就壞了,一輛經常開的二手賓士,定期維護少不了。

相關文章