從開源專案學習 C 語言基本的編碼規則

清蒸竹馬發表於2014-07-30

每個專案都有自己的風格指南:一組有關怎樣為那個專案編碼約定。一些經理選擇基本的編碼規則,另一些經理則更偏好非常高階的規則,對許多專案而言則沒有特定的編碼規則,專案中的每個開發者使用他自己的風格。

所有程式碼都保持一致風格的大型庫,更容易讓人理解。

有許多資源是關於能讓人採取的更好的編碼規則的,我們可以通過以下方式學到好的編碼規則:

  • 閱讀書或雜誌
  • 瀏覽網站
  • 與同事交流
  • 參加培訓

另一個更有趣的方法是通過研究一個成熟的知名開源專案來得知其開發者是怎樣編寫程式碼的。對於C語言來說,Linux核心是個不錯的選擇。

對初級甚至中級的C開發者而言,可能不太容易深入到Linux核心的程式碼中,但是我們的目標不一定得是對它的原始碼做出貢獻,而是探索它是怎樣實現的。

讓我們以來自Linux原始碼中的一個函式實現為例:

這段程式碼看起來非常乾淨,實際上這個函式

  • 僅有幾行程式碼
  • 簽名定義得好
  • 註釋寫得好
  • 縮排安排得好
  • 變數名非常清晰

同樣的功能可以被另一個開發者以下面這種方式實現

編碼風格對原始碼的可讀性影響甚巨,投入一些時間培訓開發者以及週期性評審程式碼總有利於輕鬆地維護和升級程式碼。

讓我們使用CppDepend深入到Linux核心的原始碼中,發現一些它的開發者們採用的編碼規則。

模組化

模組化是一種提升使用不同獨立部分構建軟體的程度的設計技巧,你可以輕鬆地管理維護模組化的程式碼。

對於像C這種沒有名字空間(namespace)、元件(component)或者類(class)等邏輯構件的過程語言,我們可以使用目錄和檔案實現模組化。

下面是一些可能的情形:

  • 把所有的程式碼放在一個目錄中
  •  分離出與一個模組或子模組有關的檔案,再放入一個特定的目錄

Linux核心使用目錄和子目錄來模組化核心原始碼:

封裝

封裝是指隱藏一份實現內部的功能與資料。在C中,封裝是通過使用關鍵字static實現的。這些實體稱為檔案域的函式和變數。

讓我們通過執行下面的CQLinq查詢語句查詢一下所有的靜態函式:

使用Metric檢視,我們可以清楚地看到有多少個函式是靜態的。在Metric檢視中,程式碼庫由樹形圖(Treemap)描述。樹形圖(Treemap)是一種使用巢狀矩形來展示樹結構資料的方法。CppDepend中樹形圖使用的樹結構就是通常的程式碼層次結構:

  • 專案包含目錄
  • 目錄包含檔案
  • 檔案包含結構體,函式和變數

樹形圖檢視為描述一條CQLinq請求的結果提供了一種有用的方式,所以我們能夠視覺化地看到與該請求有關的型別。

正如我們所見,許多函式都宣告為靜態的

現在我們來查詢靜態域:

和函式的標記一樣,許多變數都宣告為靜態屬性。

在Linux核心原始碼中,只要函式和變數對於檔案域而言是私有的,就使用封裝的手法。

使用結構體儲存你的資料模型

在C程式設計中,函式使用變數達到不同的處理要求,這些變數可以是:

  • 靜態變數
  •  全域性變數
  • 區域性變數
  • 結構體變數

每個專案都有一些可以被很多原始檔使用的資料模型,可以使用全域性變數,但這不是好的解決方案,更推薦使用結構體對資料分組。

讓我們搜尋一下屬於基本型別的全域性變數:

僅有幾個變數與該查詢匹配,也許我們可以把這些變數中的一些分組到結構體中,例如(elfcorehdr_addr and elfcorehdr_size),或者是(pm_freezing and pm_nosig_freezing)這樣。

讓函式短小而幹練

下面是來自linux編碼風格網頁的一份關於函式長度的建議:

函式應該短小而幹練,並且一個函式只做一件事。它們應該恰好佔據一屏到兩屏(我們都知道ISO/ANSI標準螢幕的大小為80×24),只做一件事並且做得很好。

一個函式的最大長度與它的複雜程度和縮排層級成反比。所以,如果你有一個概念上簡單的函式,這個函式包含著冗長的(但是簡單)case語句,而每個case分支都對應著為不同情況而不得不進行簡單處理,那麼這個函式長點也沒多大關係。

讓我們找找多於30行的函式:

多於30行的方法僅有幾個。

函式引數的個數

擁有多於8個引數的函式呼叫起來可能很傷神,還可能降低函式的效能。一個替代方案是提供一個專職傳參的結構體。

僅有2個方法的引數多於8個。

區域性變數的個數

方法的區域性變數超過8個,會讓方法難以理解,也難以維護。超過15個就非常複雜,這時應該拆分成兩個更小的方法(使用工具自動生成的除外)。

僅僅5個函式的區域性變數超過15個。

避免定義複雜的函式

有許多度量複雜函式的指標,上面提到的程式碼行數、引數個數和區域性變數個數是基本的。

還有些其他有趣的指標:

  • 環路複雜度在過程軟體中是一個廣為使用的指標,等於一個過程可以接納的決議的個數。
  • 巢狀深度是一個定義在方法上的指標,該指標與方法體中最深巢狀作用域成正比。
  • 最大巢狀迴圈等於巢狀在一個函式中最深迴圈層級。

這些指標的可接受最大值更多依賴於團隊的選擇,並沒有標準參考值。

讓我們找找可能需要重構的函式:

只有很少幾個可以被認為是複雜的函式。

命名約定

命名約定沒有標準,每個專案經理都可以選擇他們認為更好的規範,重要的是遵守選擇的約定,讓專案擁有一致的命名。

例如在Linux中,結構體必須以小寫字母開頭,我們可以驗證一下這條規則在整個核心程式碼中是否成立,執行下面的查詢:

僅有4個結構體以“_”代替小寫字母開頭。

縮排

縮排非常有利於讓程式碼容易閱讀,下面這段摘自linux編碼風格網頁的文字,說明了隱藏在使用縮排背後的動機。

 

原理闡釋:縮排背後整體思想是為了清楚地定義一個控制塊的開始與結束。特別是當你已經持續對著螢幕長達20個小時的時候,你更容易發現縮排是怎樣發揮功效的(如果你有大量的縮排)

當前,有的人會聲稱,八字元的縮排會讓程式碼行右端伸太遠,這會是程式碼在80字元寬度的終端螢幕上難以閱讀。對此的答案是:如果你需要多於3級縮排的話,不管怎麼說,你已經陷入囹圄,你應該修改你的程式。

結論

探究一些知名開源專案總是有利於提高你的程式設計技巧,沒必要下載生成該專案,你從——比如說——GitHub上就可以發現這些程式碼。

相關文章