如何評定一個系統的質量?什麼樣的系統或者軟體可以稱之為高質量?可以從三個角度來看,一是架構設計,例如技術選型、分散式系統中的資料一致性考慮等,二是專案管理,無論是敏捷開發還是瀑布式開發,都應當對技術負債進行清理,對程式碼進行重構等,最後離不開的是程式碼質量,程式碼質量的高低直接影響系統的可維護性和可延伸性。好比一輛汽車,內飾高階,外觀漂亮,但是底盤不行,動力孱弱,也難以稱得上是一輛好車。本文將從主觀和客觀的角度,和大家探討一下,作為程式設計師,應該如何寫出整潔高質量的程式碼。
點開京ME頭像可以看到,咱們內部的title是「xx開發工程師」,而不是「xx程式碼編寫員」,這個師可以理解為大師的師(master),大師級程式設計師把系統當作故事來講,而不是當作程式來寫。作為開發者,應該對自己寫下的程式碼負責,當在一個類上@author冠名的時候,應當有一種成就感,在未來的某一天,可能一年,兩年甚至五年之後,其他同事讀到這段程式碼,會由衷的發出感嘆「牛B」,而不是吐槽「寫的什麼玩意」,Doug Lea寫的並行包,時隔多年,他的大名依然如雷貫耳。
首先應當達成共識的是,程式碼是寫給人看的,給機器執行那只是基礎,優秀的程式碼,不需要過多的註釋,程式碼本身就是註釋。可讀性指的是,其他開發人員能夠迅速理解程式碼的意圖和功能,簡潔的程式碼更易於理解、測試和維護。其次,避免重複,程式碼重複是軟體中一切邪惡的根源,許多原則和設計模式都是為了消除重複而建立。
接下來從客觀角度,例舉日常程式碼評審中的常見問題,給大家作一些參考。
1. try/catch塊應該儘量的小,只針對無法預料的情況進行try/catch,可以預見的異常情況應該在try之前被校驗住。
無法預料的情況:遠端呼叫,IO讀寫,第三方API等。 可以預見的情況:某個引數為空,某個列表無元素等。
try/catch塊小有什麼好處?
2. 如果確實需要對整段方法進行tryCatch,不如新寫一個方法,將業務邏輯處理和例外處理區分開來。
例如:
try/catch程式碼塊中的內容多,說明對程式碼掌控能力不夠好 如果存在try,那麼儘量保證try在第一行,並且在catch/finally後不該有其他內容
方法行數應該儘可能的短,越短越好。能夠讓讀者點進來的一瞬間就明白這個方法做了什麼事情。舉個例子:
這是一個查詢價格的方法,這個方法長度截圖已經一頁截不下了。如果要想搞明白這個方法做了什麼事,憑藉方法體中的註釋是沒法輕易梳理清楚。
只需要做簡單的一些處理的包裝,這個方法就能變得通俗易懂,而且並不用寫一行註釋。
簡單重構後:
從上面的程式碼上看,第一段將價格查詢的程式碼分成了兩個部分,首先解析查詢的審批狀態,然後根據審批狀態分別查詢價格。第二段將價格查詢分成了兩個部分,一個是查詢審批中的價格,另一個是查詢審批通過的價格,最後將二者組合起來返回。第三段是截了審批中價格查詢的程式碼,顯而易見地,查詢審批中價格有兩種模式,百分比模式和底價模式,查詢到結果後,組合返回即得到商品的渠道價。
對比一下不難看出,原始碼塊篇幅過長,巢狀較深(for迴圈巢狀if再巢狀if),導致可讀性下降。如果我想改動某塊的程式碼,都需要深思熟慮。但是在拆分之後,就可以快速定位到目的碼塊,並且不用擔心對其他方法產生影響,另外,對其進行單元測試也會比較方便編寫測試用例。
從系統概念上來看,一般可以將系統分為兩類,一類是給一個輸入,得到一個輸出,這類稱之為響應式,另一類是給一個輸入,但是沒有輸出,這類被認為是命令式。同理,對於方法而言,響應式方法即給定一個入參,得到一個出參,比較常見的比如模型轉換、資料查詢等,命令式方法有傳送訊息、run一個執行緒。
但是無論怎麼變化,這些都有一個共同點,他們不會去修改入參的資料。
引起意外的副作用:如果方法修改了傳入的引數,而呼叫者依賴於原始的引數值,那麼呼叫者可能會在不知情的情況下受到影響。這可能導致程式的行為變得難以預測,增加程式碼的複雜性和錯誤的可能性。
破壞資料的一致性:如果多個方法共用同一個物件作為引數,並且這些方法都可以修改該物件,這些方法可能會相互干擾,導致資料狀態變得混亂和不可預測。
可能增加程式碼的維護難度:當方法修改傳入的引數時,呼叫該方法的程式碼可能需要依賴於這種修改行為。這樣會增加程式碼的耦合性,使得程式碼更難以理解、維護和重用。當需要對方法進行修改時,可能需要同時修改呼叫該方法的所有地方,增加了程式碼的維護成本。
舉個例子:
這裡呼叫了3個set方法,但是誰能一眼看出來,它把值set到哪個object裡了?還得挨個點進去看看具體實現,才知道在哪裡賦值了。
如果確實需要修改引數的值,可以建立一個新的物件來儲存修改後的結果,並將其返回給呼叫者。這樣可以保持程式碼的清晰性、可維護性和可測試性,減少意外的副作用和資料一致性問題。
方法的引數也是越少越好,最理想的引數是無參,其次是單參,應該儘量避免三個或者三個以上的引數
繼續上面的程式碼,最後一行「設定渠道型別」,set方法裡有兩個入參,完全可以將其賦值物件提取出來,減少一個入參,如下:
如果方法的處理邏輯,只依賴於某一區域性變數object的資料,那麼完全可以將這個方法放在物件內部,進一步減少方法引數,提高程式碼複用性,例如:
這樣來看,就成功將兩個入參的方法,縮減成了無參方法,和原始程式碼相比,原始程式碼方法的作用域僅限於類內部,而最終的無參方法,只需要拿到目標物件範例即可在任何地方呼叫,還能減少重複程式碼的編寫。
程式碼中出現大量的縮排顯然是影響閱讀的,在可能的情況下,我們應該儘可能的減少程式碼中的縮排和層次。好的程式碼讀起來應該是和報紙一樣,排版整齊優雅,而不是爬樓梯。
來看看下面的程式碼:
程式碼中出現兩層if巢狀,首先我們可以對第一層if巢狀做一個簡化,簡單將if的判斷條件做一個反轉得到下面程式碼:
這樣就減少了一層縮排,然後再將變數和引數進行一些微調:
這樣,和之前的程式碼比起來,是不是可讀性提高了?
在物件導向程式設計中,"單一職責原則"(Single Responsibility Principle,SRP)是指一個類或模組應該只有一個引起它變化的原因,即一個類或模組應該只有一個主要責任或目標。通過細化功能和職責,可以提高程式碼的可維護性、複用性和可延伸性。
看下面的程式碼:
這個方法是從宣告上來看,是用於儲存任務資料,但是實際上如果入參中的某些資料不為空(實際上看這個方法的實現也很難一眼看出來具體是哪些資料),會進行更新update操作。這種寫法不僅容易讓讀者感到困惑,也會降低程式碼的可延伸性。
Q:AtomicInteger的compareAndSet方法違反了單一職責嗎? A:並不違反單一職責原則。compareAndSet 方法是 AtomicInteger 類提供的一種原子操作,用於比較當前值與給定期望值是否相等,如果相等則將當前值設定為新值。這個方法的職責是實現原子性的比較和設定操作,確保在多執行緒環境下的執行緒安全性。compareAndSet 方法作為其中的一種操作,是為了滿足特定的需求而設計的,它並不違反單一職責原則。單一職責原則要求一個類或模組只有一個主要責任,而 AtomicInteger 類的主要責任是提供原子整數操作,compareAndSet 方法是其中的一部分,屬於該類主要責任的一部分。因此,compareAndSet 方法並不違反單一職責原則。
下面的程式碼,checkParam的返回值是布林值,但是作為讀者,我不知道是應該check通過了返回True,還是check失敗的情況下返回True。
一般而言,check作為開頭的方法名稱,沒有返回值,對於check不通過的情況以異常的形式丟擲。返回布林值的方法通常以is作為起始,換種寫法看看是不是會好讀很多?
這裡比較典型的是flow編排檔案裡的條件判斷節點,使用0,1這類沒有意義的數位,很難一眼看出來說了什麼,需要挨個點進去看邏輯,才能梳理明白,改成有意義的命名,便於理解。
類名使用名詞或者名詞短語 方法命名使用動詞或者動名詞結構
這個比較好理解,暫不舉例了。
在發起MR之前,檢查一遍程式碼裡有沒有「被註釋掉的程式碼」以及TODO。
對於註釋掉的程式碼而言,如果後續有其他開發人員介入,他們會覺得非常困惑,這兩行程式碼為什麼要註釋掉?它們重要嗎?它們放在那裡,是因為在未來的某一天需要用?還是說只是當時忘記刪除了?還是說為了給未來修改做提示?這些顧慮可能讓其他開發人員也不敢動手清理這些被註釋的程式碼,進而造成這些程式碼被永久的保留下來,形成「幽靈程式碼」。
對於TODO來說,建議在寫todo的時候,要求在後面跟上自己的ERP,誰來解決,怎麼解決,deadLine是什麼時候。因為在大多數情況下,Later equals never。久而久之,連自己都忘了這個todo,忘了當時為啥寫這個todo,應該怎麼處理,給系統埋下隱患。
如上,當年寫下這個TODO的同事已經離職了,現在只有上帝才知道todo什麼東西。
養成良好的習慣,在用列舉定義的變數,拉一條參照指向列舉,方便其他開發人員閱讀。
例如:
這是一個可窮舉的變數(因為狀態是有限的),但是讀者不知道具體都有哪些狀態。這裡其實是有一個列舉關聯上來的.
為了方便閱讀,可以在變數定義的地方,使用javaDoc的@see/@link方式,說明這個變數的列舉範圍。
單測的重要性不言而喻,這裡先挖個坑,後續單開一篇寫。
寫在最後,給大家推薦一本經典書籍《程式碼整潔之道》,實際上該書的出版時間較早,書中的某些知識或許有些過時,但是前面幾章內容仍然值得一讀。整潔的程式碼需要團隊的共同努力,團隊成員應該遵循一致的編碼風格和標準,進行程式碼審查和知識分享,共同維護和改程序式碼質量。它不僅僅是一種編碼風格,更是一種思維方式和價值觀。優雅的程式碼,就像是藝術品,正如標題所言,應當像一名鋼琴家一樣編寫程式碼。
作者:京東零售 譚磊
來源:京東雲開發者社群 轉載請註明來源