不斷思考總結提煉,持續迭代最佳化前進。
工作十餘年,對程式碼質量有過一段孜孜不倦的追求,同時也在實戰中有一些親身體會,總結下對程式碼質量的觀點和經驗。
關於程式碼質量,基本都總結在我的部落格上:程式碼修行。也寫過一本小小的電子書,再分享一次,https://pan.baidu.com/s/1noGsrkJto_CoAPxjXEmMSA?pwd=yvu6 。一年有效期。
概述
那麼,在我看來,高階程式設計師的程式碼質量應當達到什麼要求呢?兼顧時間成本,我認為適中即可。達到如下程式碼質量要求是可以的:
- 功能的深度正確實現
- 清晰性和可維護性
- 良好的健壯性
- 必要的日誌記錄
- 核心方法的充分單測
- 大資料量,考慮效能
- 大流量,考慮穩定性
- 適度的通用性
- 適度的可擴充套件性
- 程式碼安全性
要達到程式碼質量要求,程式設計師需要掌握程式碼重構技能,需要熟悉各種程式碼錯誤並有效規避。踩過的雷和坑越多,越有意識地規避,才能避免重犯錯誤。可閱:程式碼問題及對策。
程式碼質量
功能的深度正確實現
程式碼質量的最起碼要求是功能的正確實現。
要能完成程式功能的基本正確性,對於程式設計師來說是不難的。不過說到深度正確,何為深度正確呢?在我的工作經歷裡,有以下幾種情況需要考慮:
- 變更處理。比如你的表從其它APP冗餘了幾個欄位(比如主機名、主機業務組等),當這些欄位變更時,需要監聽訊息去更新它們;當主機解除安裝時,需要刪除對應的記錄;當租戶變更時,需要插入新的初始化記錄。
- 關聯處理。多個功能相互影響,比如告警加白、歸併和響應。歸併在加白流程之前,如果歸併了,告警就不能加白,可能與使用者預期不符(因為使用者設定了白名單,歸併卻是使用者並不感知的系統內建機制)。而告警歸併會導致後面被歸併的告警的元素無法響應;此外,多個告警會共享同一個元素,一個元素在一個告警裡響應了,也會展示在其它告警裡,導致其它告警展示響應資訊,但是告警列表的告警卻沒有響應資訊,可能導致客戶困惑。
- 大資料量處理。一個主機封禁一個IP好解決,十萬臺主機封禁上千個IP呢?
- 併發處理。引入OpenAPI之後,元素響應介面呼叫頻次就高了很多。對於元素響應介面來說,高頻重複傳送同一個元素的同一個操作(或者相近操作),操作可能落到不同機器上,你的程式能正確應對麼?如果第一次成功,後續失敗,會不會出現異常?
- 錯誤處理。一個普通的主機響應操作,如果主機不線上怎麼辦?如果資料庫操作失敗怎麼辦?如果主機線上但是處於空跑狀態無法響應任何請求怎麼辦?如果中介軟體異常怎麼辦?一個耗時操作分為同步和非同步兩部分,如果非同步流程出錯了,怎麼辦?
如果這些都不能考慮到,那就等著被測試同學提的BUG轟炸吧!我也是從各種BUG中提煉出這些場景的。
清晰性和可維護性
可維護性可以說是程式設計師耳熟能詳的關於程式碼質量的詞眼了。可維護性涉及多個方面,這裡不一一贅述。可以去知乎上搜尋下,或者閱讀各種程式碼質量相關的書籍。比如《程式設計珠璣》、 《 Effective Java 》、《程式碼整潔之道》、 《Writing solid code》、《編寫可讀程式碼的藝術》、《重構》、《敏捷技能修煉》。
清晰性是一個很重要的目標,但是卻不那麼容易達到。我們都寫過類似的特殊處理的程式碼,通常是為了繞過某些檢測或者適配某種流程的條件。但這種程式碼是很不清晰的,很容易留坑。如果沒有註釋的話,即使當初寫這段程式碼的人也很容易忘記為啥會有這段程式碼。
因此,要達到清晰性,要儘量避免這種原子性的判斷。用有語義的函式替代,並輔以必要的註釋。
// 反例
if shop_id != "" && shop_type == "plain" && other condition {
head_shop_id = ""
}
// 也不算好的正例
if isPlain(shop_id, shop_type) != "" {
// 為了適配某種流程,需要設定總店ID為空
head_shop_id = ""
}
func isPlain(shop_id, shop_type) bool {
return shop_id != "" && shop_type == "plain" && other condition
}
良好的健壯性
實現功能之後,健壯性是第一個必要的基本要求。要想線上不出各種奇奇怪怪的問題,健壯性一定要做好。實現健壯性的基本理念是防禦式程式設計和契約式程式設計。
健壯性檢查通常包括:
- 引數校驗:比如特定資訊(IP,埠,郵件等)的格式。
- NPE (nil point reference) 處理:物件為空。
- 數值溢位。比如有些 IPv6 地址轉成整數會超出 int64 能表示的最大數值。
- 陣列列表越界:陣列列表越界,尤其是使用 split 方法之後提取欄位的時候。
- JSON 解析出錯:json 格式不對、json 內容不全。
- 錯誤處理:立即失敗、重試、重續、根據條件判斷、丟擲並捕獲異常。
- 資源和鎖管理:確保資源和鎖及時被釋放,不會釋放錯誤的資源和鎖。
- 併發安全:確保多執行緒處理同一個共享可變物件加了同步措施。
以上所列,只是部分。更詳細的可以問ChatGPT。對於一般功能,能達到功能實現正確、適度的健壯性,就已經足夠了。易測、清晰、健壯,是我認為程式碼質量的基本要求。可閱:寫程式碼的指導思想:如何寫出易測、清晰、健壯的牢固程式碼。
必要的日誌記錄
線上執行之後,一定會出現一些問題,需要進行問題排查。問題排查的基本思路就是線索+邏輯推理。線索從哪裡來,從日誌記錄和資料庫裡來。
記錄關鍵變數、關鍵路徑。由於日誌量不宜過多(影響效能、磁碟儲存空間),因此往往會傾向於把業務ID及業務ID 關聯關係記錄下來,然後根據業務ID去資料庫裡查詢詳情。對於流程,最好能有一個業務ID或唯一鍵,能夠貫穿整個流程,這樣,根據業務ID或唯一鍵就能看到整個流程,以及看到流程在哪裡終止了。
對於併發問題,一定要留意時間線索。可閱:軟體除錯與問題排查的修煉之路與實戰經驗 。
必要的核心方法的單測
單測必不可少。程式設計師往往嫌單測麻煩而不願寫單測。實際上,單測是一個減少時間成本的保障措施。透過單測,確保核心邏輯沒有問題,就可以懷疑是流程中的引數不合理或者是其它地方有問題,而不至於一遍遍除錯,結果發現是核心方法的一個地方沒有覆蓋到或者沒有處理好。短鏈路除錯顯然比長鏈路除錯要省時。
關於單測編寫,可閱: 深入探究單元測試編寫,使用Groovy+Spock輕鬆寫出更簡潔的單測,使用Java函式介面及lambda表示式隔離和模擬外部依賴更容易滴單測,改善程式碼可測性的若干技巧 。
現在,程式碼和單測都可以透過 AI 生成了,進一步便利了程式設計師。所以,程式碼完成後,千萬不要拉下單測啦!
效能和穩定性
有一定業務規模的網際網路公司,往往對效能和穩定性也有要求。
處理大資料量或長流程時,介面往往會比較慢。解決效能的五件套:選擇適合的資料結構和演算法、索引、快取、併發、非同步非阻塞。此外還有精簡流程。做方案時,養成評估資料量的好習慣。無論資料量多少,都做個評估。資料量,可能是瞬時大資料量,可能是基於較大日增量的累積大資料量。多數情況下要考慮累積資料量。可閱:應用層效能最佳化思路及方法。
穩定性通常涉及到對系統全域性的理解和掌控。涉及大流量時,需要考慮對系統的衝擊,即系統在大流量時不至於直接崩潰導致服務不可用。此時要考慮限流、降級,評估對核心 API 呼叫和對中介軟體訪問的高頻程度,避免對核心API和中介軟體造成衝擊,尤其要避免雪崩。畢竟,核心API和中介軟體掛了,那就不只是一個服務出問題了。可閱:增強系統穩定性的基本方法 。
效能和穩定性考量,最考驗一個高階程式設計師的綜合技術素養。對網際網路常用服務端技術有一個整體概覽,對理解效能和解決問題也很有助益。正如修習內功也有助於修煉高階武學。可閱:網際網路應用服務端的常用技術思想與機制綱要 。
適度的複用性
複用性也是程式設計師非常看重的程式碼特性。寫一次程式碼,多次複用,省時省力,何樂而不為?
程式碼複用性,涉及多個層面:程式碼級(函式、介面、類、物件)、模式級、API級、模組級、框架級、中介軟體級、服務級。初級程式設計師通常能做到程式碼級複用,高階程式設計師應該能做到針對一類需求或模式級或API級的可複用設計,一流程式設計師能夠做到模組級、框架級、中間級、服務級。
泛型和函數語言程式設計,是達成程式碼可複用目的的兩大程式設計技巧。可閱:“寫出可複用程式碼的基本思想與實踐”, 函式式+泛型程式設計:編寫簡潔可複用的程式碼,再談函數語言程式設計:釋放程式設計創造力。
複用性最考驗程式設計師的抽象思維能力。抽象度越高,複用性越強。抽象能力強的人,通常是能寫出一個好用框架的。
為什麼說適度的複用性呢?因為程式碼的高複用性可能會導致程式碼清晰度下降或者有效能損耗,或者理解起來有一定成本,需要有所權衡。
適度的可擴充套件性
大多數公司對程式碼可擴充套件性並不作要求,更多是對系統架構設計有可擴充套件要求。不過,程式設計師為了省事省力,達到一定的程式碼可擴充套件性是有助益的。
達到程式碼可擴充套件性通常基於介面和外掛程式設計,要求對設計模式有一定掌握。比如策略模式、組合模式、裝飾模式、橋接模式、訪問器模式、觀察者模式等。可閱:實現可擴充套件程式碼的四步曲、由一次重構引發的對可擴充套件性的思考。
達成系統可擴充套件性,則需要具備更多技能和經驗:【整理】系統可擴充套件性的設計與實現 。
為什麼說適度的可擴充套件性呢?過度的可擴充套件性往往意味著過度設計,一開始考慮了很多,結果基本上用不上,反而導致系統實現複雜,維護成本增大,得不償失。
程式碼安全性
程式碼安全性放在最後,但並非不重要。高階程式設計師往往也會忽視程式碼安全性。
- 敏感資訊洩露:比如密碼、配置等列印在日誌裡(或者作為除錯語句列印上線卻忘了刪除)。
- 許可權控制不當:低許可權的人訪問到其未授權的資源。
- SQL隱碼攻擊:在資料庫相關操作中,可以讓使用者填入過於靈活的查詢條件卻不加檢查。
- 遠端程式碼執行:在可以讓使用者輸入語句進行的應用裡,沒有對惡意程式碼進行檢測或過濾。
- 反序列化問題:fastjson 的事情大家都知道了。即使大廠程式設計師都不能避免,其它人更不談了。
- 檔案路徑漏洞:一般體現在檔案上傳上,不過在任何可以讓使用者輸入路徑的地方,都可能存在檔案路徑漏洞。
程式碼重構
程式碼重構,是提升程式碼質量技能的最有效的手段。
經過程式碼重構,程式碼能達到一種更精練更優雅的境界,閱讀起來也是很有成就感的。可閱:精練程式碼:一次Java函數語言程式設計的重構之旅 ,一次重複程式碼重構的思考及探索。
程式碼重構涉及程式設計師的綜合技能素養:
- 簡單的程式碼刪減和挪動,涉及對分層結構的深入理解;
- 較複雜的程式碼結構改造,涉及對業務流程結構和設計模式的理解和應用;
- 更復雜的系統級重構,涉及對系統設計理論和新技術棧的學習和應用。
只有第一種是相對安全的,後兩種在完成之後,都要仔細檢查是否對現有邏輯有改動,或者需要回歸測試下。
進階磨鍊
迭代前進
不斷思考總結提煉,持續迭代最佳化前進。
在熟練駕馭程式碼質量,且能夠有效利用 AI 生成程式碼之後,就可以向更上層進階了。
如何作出良好的設計方案,是下一步的目標。因為程式碼已經可以由AI 生成,你所需要做的是定好整體方案和規劃,而不是沉迷於具體細節。可閱:如何做出一個好的設計方案 。
在方案之上,程式設計師還有更高的追求。那就是架構設計,全域性視野。可閱:從系統整體觀思考系統構建 。實際上,系統整體觀,不僅僅應用於技術性系統,也可以應用於社會系統,機構組織等。這時候,你的視野不再侷限於技術和程式碼,而是放眼於組織、社會、商業等。
思考力
程式設計師最核心的競爭力是什麼?強大的抽象和邏輯思維,高質量的思考力。
強大的抽象和邏輯思維,清晰透徹縝密的思考力,像鐳射一樣具有穿透力的思考力。這是從事軟體開發磨鍊出的特有的能力,這種能力可以輕易地理解現實社會的規則(儘管未必會屈從某些規則),理解很多比較複雜的關聯關係。
小結
本文小結了程式碼質量相關的知識和技能。可以看到,程式碼質量看上去只是程式碼層面,實際上反映的是程式設計師的綜合素養,包括程式設計師的邏輯和抽象思維、程式設計師的系統設計能力、程式設計師的編碼和細節處理能力等。要寫出高質量程式,可不那麼容易。
每一次總結提煉,都是對自身的一次昇華。