程式碼質量在「內卷時代」的重要性

語言: CN / TW / HK

這裡是Z哥的個人公眾號

每週五11:45 按時送達

當然了,也會時不時加個餐~

我的第「173」篇原創敬上

大家好,我是Z哥。

提到程式碼質量,不知道你的腦海中浮現出的第一個詞是什麼?規範?可讀性?優雅?

在我的心中,好的程式碼質量 = 舒服。看著舒服,接手這樣的專案感覺舒服,在其中找問題和改程式碼舒服。

軟體開發這個行業是一個年輕的行業,如果在十幾年前談程式碼質量,可能還算是個比較高階的問題。但是在大家都認為越來越內卷的當下,注重和提升程式碼質量我認為是每個程式設計師的必修課。說的嚴重一些,它影響了你在團隊中的價值,說的表面一些,它是你在團隊中的“面子”。

假象一下,你接手了兩個專案,一個專案程式碼乾乾淨淨、非常整潔,另一個隨處可見的相似程式碼以及雜亂無章的分類擺放。你對這兩個專案的前負責人是什麼想法?未來你更願意和誰合作?我想答案是顯而易見的。

在我看來要寫出舒服的程式碼並不需要對那些程式碼規範背的滾瓜爛熟,其實只要掌握一個六字核心原則:高內聚低耦合。如此寫出的程式碼至少能在60分以上。

可以回想一下,當你在做一個簡單專案的時候,使用那些成熟的框架和工具可以輕鬆地完成大部分工作。甚至會感覺有點無聊,因為感覺都是在CRUD。這就是應用了經過精心設計的高內聚低耦合的框架和工具所具有的效果,讓事情變簡單。

講句題外話,「高內聚低耦合」在軟體開發領域真是一個黃金原則,在哪都適用,大到一個分散式系統的設計,小到一個class的設計。如果我的腦子只能記住一條原則的話,毫不猶豫會選擇它。

那麼如何讓自己也能寫出高內聚低耦合的程式碼呢?我們要對「高內聚低耦合」有更深入地理解,而不是僅僅停留在這6個字上。

葡萄牙馬德拉大學精確科學與工程中心的教授,被認為是計算機領域先驅者之一的賴瑞·康斯坦丁帶隊對內聚性和耦合性做了深入的研究和分析,對內聚性和耦合性的強弱關係進行了梳理,得到了以下結論。(摘自於維基百科)

內聚性的分類如下,強度由低到高排列:

偶然內聚性:是指模組中的機能只是剛好放在一起,模組中各機能之間唯一的關係是其位在同一個模組中(例如:“工具”模組)。

邏輯內聚性:是隻要機能只要在邏輯上分為同一類,不論各機能的本質是否有很大差異,就將這些機能放在同一模組中(例如將所有的滑鼠和鍵盤都放在輸入處理副程式中)。模組內執行幾個邏輯上相似的功能,通過引數確定該模組完成哪一個功能。

時間性內聚性:是指將相近時間點執行的程式,放在同一個模組中(例如在捕捉到一個異常後呼叫一函式,在函式中關閉已開啟的檔案、產生錯誤日誌、並告知使用者)。

程式內聚性:是指依一組會依照固定順序執行的程式放在同一個模組中(例如一個函式檢查檔案的許可權,之後開啟檔案)。

聯絡內聚性/資訊內聚/通訊內聚:是指模組中的機能因為處理相同的資料或者指各處理使用相同的輸入資料或者產生相同的輸出資料,因此放在同一個模組中(例如一個模組中的許多機能都訪問同一個記錄)。

依序內聚性/順序內聚:是指模組中的各機能彼此的輸入及輸出資料相關,一模組的輸出資料是另一個模組的輸入,類似工廠的生產線(例如一個模組先讀取檔案中的資料,之後再處理資料)。

功能內聚性:是指模組中的各機能是因為它們都對模組中單一明確定義的任務有貢獻(例如XML字串的詞法分析)。

耦合性的分類如下,強度由高到低排列:

內容耦合:也稱為病態耦合當一個模組直接使用另一個模組的內部資料,或通過非正常入口而轉入另一個模組內部。

共享耦合/公共耦合:也稱為全域性耦合指通過一個公共資料環境相互作用的那些模組間的耦合。公共耦合的複雜程度隨耦合模組的個數增加而增加。

外部耦合:發生在二個模組共享一個外加的資料格式、通訊協議或是裝置介面,基本上和模組和外部工具及裝置的溝通有關。

控制耦合:指一個模組呼叫另一個模組時,傳遞的是控制變數(如開關、標誌等),被調模組通過該控制變數的值有選擇地執行塊內某一功能;

特徵耦合/標記耦合:也稱為資料結構耦合,是指幾個模組共享一個複雜的資料結構,如高階語言中的陣列名、記錄名、檔名等這些名字即標記,其實傳遞的是這個資料結構的地址;

資料耦合/資料耦合:是指模組藉由傳入值共享資料,每一個資料都是最基本的資料,而且只分享這些資料(例如傳遞一個整數給計算平方根的函式)。

訊息耦合:可以藉由以下二個方式達成:狀態的去中心化(例如在物件中),元件間利用傳入值或訊息傳遞 (電腦科學)來通訊。

無耦合:模組完全不和其他模組交換資訊。

如果你程式碼寫的還不夠多,上面有些差異還無法很好的感知。但是你不需要把這些概念一字一句背下來,只要平時在寫程式碼的時候多思考一下:“當前的程式碼設計是屬於哪種型別?”。如果不能確定的話回頭來看這篇文章:D。慢慢地,通過寫更多的程式碼,你會對耦合和內聚的強弱,有更敏感的感知力。

根據上面的這些概念,寫出高質量程式碼的思路就很清晰了。method的歸類、class的歸類能根據功能內聚性歸類的絕不用順序內聚,能根據順序內聚性歸類的絕不用更弱的。耦合也是同樣的,能不耦合的就不耦合,能用訊息耦合的絕不用資料耦合。

但是想要保證程式碼按照這個設想去發展,還是需要通過做一些具體的事情作為抓手。這些事不需要全部做,但每一項都有助於提高程式碼質量。

/01  執行程式碼規範+Code Review/

在Z哥看來,執行程式碼規範,最重要的價值並不是非得讓100%的程式碼符合這個規範,而是讓所有人一起養成一種意識,意識到我的程式碼會被別人看到,被評價。這樣才能在寫程式碼的時候,不僅僅是為了實現功能。

所以具體程式碼規範是什麼樣,並沒那麼重要,可以是自己定義的,也可以是參考大廠的。當然我更推薦前者,大廠的規範雖好,但是你要全部照搬,這個執行成本可不小。

如果你想提高程式碼質量,但又不想做很多事。那麼執行程式碼規範+Code Review可以是你的唯一選擇。如果你是一位個人開發者,那麼可以讓身邊你認為程式碼寫的最好的人幫你做CodeReview,以他的規範作為程式碼規範即可。

很多人覺得程式碼規範是一種約束,會降低開發效率。其實不會,最多在初期因為自己並不習慣一些規範,所以花了很多時間在修正程式碼。一旦走上正軌後,程式碼規範反而會提高開發效率,因為節省了很多閱讀程式碼的時間以及同事之間溝通的時間。

就算它真的降低了開發效率,但你要提升效率也不應該降低程式碼質量,而是通過其它方式去提效。

/02  寫單元測試/

寫單元測試之所以能提高程式碼質量,是因為如果不是高內聚低耦合的程式碼,你會發現單元測試非常難寫。

比如,你只想測一下方法A,但是發現裡面的依賴錯綜複雜,好吧,都stub掉。最後發現測一個方法寫了幾十個stub,這種操作我親眼看到過……。這就是前面提到的「內容耦合」過多了。

所以,能輕鬆地寫出單元測試,並且將其養成一種習慣,你的程式碼質量必然不會差。

/03  設計先行/

雖然設計不出完美的程式碼,但是優先考慮設計可以讓你多思考“我應該怎麼寫這段程式碼”,而不是直接抄起傢伙就寫,寫到哪算到哪。

畢竟大多數功能都不可能一步到位,需要多次迭代。這種情況下最初的設計就顯得尤為重要,畢竟大部分人遇到不舒服的程式碼不會推翻重寫,最多就是修修補補,甚至是直接在這之上疊加新的程式碼。

/04  專案與團隊”微服務化”/

保證一個幾萬行程式碼的專案質量和幾百萬行程式碼的專案必然難度不同。所以,如果合適的話可以將專案拆小,並且由專門的團隊負責。這樣可以提高團隊把控程式碼質量意願,並降低其難度。

/05  利用相關的工具/

主流的程式語言或多或少都有一些靜態程式碼分析工具、單元測試覆蓋率統計工具,這些要充分利用起來。它們可以快速的幫助避免一些低階的程式碼壞味道,節約大量時間。

/06  幾個程式碼層面的小建議/  

01  勿過度使用鏈式程式設計

很多人會追求極致少的程式碼行數,恰好鏈式程式設計能投其所好。的確在很多時候鏈式程式設計可以提高程式碼的可讀性,但是它帶來的弊端也是顯而易見的,

  1. 除錯的時候觀察變數變得很不方便。

  2. 容易在當前方法裡處理不應該在這裡處理的業務邏輯。畢竟很多class的方法和屬性是public的,相比單獨做一層封裝再呼叫,“點”出來直接用多香啊~所以在使用鏈式程式設計的時候也得遵守「得墨忒耳定律」。

得墨忒耳定律:

每個單元對於其他的單元只能擁有有限的知識:只是與當前單元緊密聯絡的單元;

每個單元只能和它的朋友交談:不能和陌生單元交談;

只和自己直接的朋友交談。

維基百科

02  避免隨處可見的try-catch

Try-catch雖好,切勿貪杯。很多人喜歡寫try-catch然後通過一個單獨封裝的通用返回模型告知呼叫方出現了什麼異常。

這種方法的目的最初是為了避免上層呼叫者沒有做異常捕獲導致程式崩潰,但是弊端也是顯而易見的,如果呼叫方沒有正確的判斷返回模型裡的異常相關屬性,會導致程式在錯誤的狀態下繼續執行,這個後果就不可預知了。

所以我認為通過try-catch封裝異常應該出現在更上層的程式碼裡,越底層的程式碼越不應該封裝異常。

03  認真編寫訪問修飾符

很多程式語言都有多個訪問修飾符,我們在編寫的程式碼的時候應該儘可能的選擇最嚴格的修飾符,而不是什麼都是public。

因為public會導致很多變數在不知道什麼情況下就被外部修改了,導致bug層出不窮、排查困難,專案質量堪憂。

訪問修飾符的過於寬鬆也是前面提到的鏈式程式設計被過度使用的推手之一。

訪問修飾符的目的是為了防止程式設計師在無意間誤用不應該使用的方法和屬性,畢竟程式碼往往不只有一個人寫。

04  慎用繼承

繼承的確挺香的,可以少寫很多程式碼。但是使用不當會破壞封裝的效果,造成訪問修飾符的失效。

繼承的正確使用姿勢應該傳達的是“子父”的關係,而不是“相似”的關係。比如“汽車”可以繼承於“交通工具”,但是不應該繼承於“腳踏車”,雖然它們都有輪子。

像汽車和腳踏車的這種情況要複用的話,可以抽象提煉出相同的部分,然後通過「組合」的方式進行。

最後,如果你對程式碼質量有更高的追求,想修煉和強化“內功”,那必須不能錯過這本經典書籍。(之前的黃皮版本更新成這本灰皮了)

好了,總結一下。

這篇呢,Z哥和你分享了我對程式碼質量這件事的看法。在行業越來越內卷的趨勢下,注重“質”總是沒錯的。

Z哥認為想要提高程式碼質量最核心的原則就是:高內聚低耦合。文中給你羅列了賴瑞·康斯坦丁教授提煉了不同的內聚性和耦合性原則來表達關係的強弱。

基於對內聚性和耦合性原則的理解,再通過以下抓手進行程式碼質量的提升工作:

  1. 執行程式碼規範+Code Review

  2. 寫單元測試

  3. 設計先行

  4. 專案與團隊”微服務化”

  5. 利用相關的工具

最後還分享了幾個程式碼層面的建議:

  1. 勿過度使用鏈式程式設計

  2. 避免隨處可見的try-catch

  3. 認真編寫訪問修飾符

  4. 慎用繼承

希望對你有所啟發。

推薦閱讀:

原創不易,如果你覺得這篇文章還不錯,就「在看」或者「分享」一下吧。鼓勵我的創作 :)

如果你有關於軟體架構、分散式系統、產品、運營的困惑

可以試試點選「閱讀原文

分享到: