花費 3 天時間整理的程式碼規範示例程式碼,你確定不進來看看嗎?

小北師兄發表於2021-06-26

小夥伴們,大家好,小北師兄又來餵飯啦,從上次寫完《關於 Windows 下 Qt 開發,這個問題必須要搞清楚!》後好久沒有更新啦(前段時間出差了好久/(ㄒoㄒ)/~~),最近接手了他人寫的專案,然後按照實際需求小幅度改動程式碼,因此 Qt 學習進度就會減慢了很多,不過師兄一定會在之後學習 Qt 過程中總結一些知識分享給大家。

不知道小夥伴們有沒有接手過他人的專案,反正師兄這次接手這個專案後確實犯難了,沒有一切關於專案的文件說明,只有一份專案原始碼留給我。不過師兄沒有放棄,因為專案要求不是大幅度改動程式碼,師兄還可以閱讀少量原始碼來滿足需求。

師兄拿到專案的原始碼後就開始粗略看了起來,本來心裡想著程式碼裡應該會有一些說明,閱讀起來也能慢慢理解專案,沒想到反手就是一個大大的驚喜:程式碼寫的很隨意,大量的函式可以有幾百行,想看看註釋,結果註釋也是寫的很不規範,看著就像是在學校急忙忙做的程式碼實驗,只有自己能看的懂註釋。

事情的經過就是這樣,面對一個亂糟糟的程式碼,確實提不起來想看的慾望,而且閱讀效率會非常低,也會導致專案的時間線拉長。師兄說了這些,主要是想強調一點:程式碼規範是非常重要的!

一個好的專案,程式碼基本都有統一的規範,否則程式碼就會隨著版本迭代,變得越來越臃腫。程式碼規範應在專案早期建立,這些規範對於保持整個專案的一致性非常有必要,採用一致的規範可以提高效率並簡化專案維護。

一個好的編碼規範,對我們來說能夠起到很多意向不到的效果。至少會有以下好處:

  1. 提高編碼效率:整齊劃一的程式碼方便進行復制貼上,複用別人寫好的程式碼很方便,風格也很適配自己的專案
  2. 提高程式碼的可讀性:看到別人寫的程式碼就和自己寫的沒有什麼差距,這樣就可以使程式設計師很快上手,很好的理解軟體,進而降低軟體的維護成本
  3. 方便團隊協同工作:大家使用同一的規範,這樣就消除了五花八分的書寫方式,統一協調。

說了這麼多關於程式碼規範的重要性,師兄建議大家去看看 Google 的關於 C++ 的程式碼規範(當然 Google 還有其他程式語言的規範,文章底部會有連結),這份規範指南的價值不僅僅侷限於它羅列出的規範, 更具參考意義的是它為了列出規範而做的謹慎權衡過程,大家可以從中學到很多知識。

這份規範指南不僅列出你要怎麼做, 還告訴你為什麼要這麼做,,哪些情況下可以不這麼做,以及如何權衡其利弊我們在自己平時開發程式時,可以參照該指南及從中汲取靈感, 建立適合自身實際情況的規範。

通過閱讀這份規範指南,師兄做了一份筆記,其中包括文字版記錄和程式碼版記錄,文字版主要是師兄看這份指南時寫的筆記,程式碼版記錄是師兄根據這份指南,用程式碼的形式重新描繪了這份指南

如果小夥伴們覺得文字版不好記憶,可以在寫程式碼時開啟我這份示例程式碼找到相關的例子來參考,下面附錄部分貼上了示例 .h 檔案的程式碼,大家可以看看。最後我在百度網盤中也儲存了這份程式碼示例和文字版筆記,可以在後臺回覆 “code_style” 關鍵字獲取網盤連結。當然也可以來我的 Github 上進行下載。

/*
 * CopyRight (c) 2021 saber
 * File: example.cc
 * Project: code_style
 * Author: gcj
 * Date: 2021/6/19
 * Description: google code style
 * License: see the LICENSE.txt file
 * github: https://github.com/saber/code_style
 */

// 說明當前檔案函式功能和使用,個人覺得太麻煩的說明可以省略!只需要簡答介紹和提示。或者也可以不寫!

// 行的長度最好不要超過 80!

// 標頭檔案包含按照 <PROJECT>_<PATH>_<FILE>_H_ 進行命名,專案 code_style 路徑為 code_style/example.h 可寫成:
#ifndef CODE_STYLE_EXAMPLE_H_
#define CODE_STYLE_EXAMPLE_H_
**留白**
// 標頭檔案包含順序:相關標頭檔案(專案中的)、C 庫、C++庫、其他庫.h,比如第三方 opencv、Eigen 等等庫
#include <sys/types.h>           // C 庫標頭檔案

#include <vector>                // C++ 標頭檔案

#include <Eigen/Core>
#include <opencv2/core/core.hpp> // 其他第三方庫

#include "code_style/xxxx.h"     // 利用專案內其他標頭檔案的某個型別或函式,不要使用 .. 類似的相對路徑,要使用相對於專案根目錄路徑


// 通用命名規則:函式、變數、檔案、應具有描述性,不要過度縮寫,型別和變數名應該是名詞,函式名可以用「命令性」動詞

// 常量和靜態儲存期變數命名:名稱前面加k,例如 kDaysInAWeek,
// 所有具有靜態儲存型別的變數 (例如靜態變數或全域性變數) 都應當以此方式命名。
// 綜合來說:就是所有 const /constexpr 變數(區域性或全域性,類內),
// 以及 靜態儲存期變數(包括 static 標識的變數或者未命名的名稱空間)的都要用 k 開頭!!!!
extern int kGlobalVar;           // 需要在標頭檔案中進行外部宣告
extern constexpr int kGGG1;
extern const int kGlobalVar2;
extern int kGoogleStatic1 = 4;   // 這裡去掉了 static 

// 這裡可以放置外部前置類全域性函式的宣告,然後在 .cc 檔案中進行包含,如果本標頭檔案一些函式和成員函式定義中使用
// 了這個類物件,那麼也需要在標頭檔案中新增 #include "ABC" #include "DEF"
class ABC;
template <typename T>
class DEF;


// 可以在 .cc檔案、 .h檔案的函式、方法或類中,可以使用 using 宣告即 using std::string
namespace xxxx專案名稱 {          // 小寫字母,最高階名稱空間取決於專案名稱,不要使用縮寫,但是大家都知道的縮寫可以用。
namespace code_style {           // 對應於 專案/code_style 資料夾

// 可以在這裡加上 別名
using Uint8Color = std::array<uint8, 3>;


// TODO 對臨時的,短期的解決方案,但不完美的程式碼使用這個註釋,或者寫一些 "將來某一天要做的事情"即 未完成事。或明確的事項
// DEPRECATED 棄用註釋,不是強制棄用,格式如下:

// DEPRECATED(寫說明,幫助其他人修復其呼叫點) ,或者一般做法,可以將一個棄用函式改造成為一個行內函數,這一函式將呼叫新的介面。
void FuncTodo() {
    // pass
}
// 改造後的行內函數(行內函數放在標頭檔案!)
inline FuncTodo() {
    // pass
    // 呼叫新的介面
    FuncOne();
}

// 函式呼叫時,儘可能易讀性:比如: my_widget.Transform(x1, x2, x3, y1, y2, y3, y4, z1, z2, z3);可以改為:
// my_widget.Transform(x1, x2, x3,
//                     y1, y2, y3,
//                     z1, z2, z3);

// 屬性和展開為屬性的巨集,寫在函式宣告或定義的最前面,即返回型別之前
// 巨集的名字,注意全大寫,然後下劃線連結。
#define MUST_USE_RESULT // 起到提示作用,可以想著用


// 函式宣告:註釋函式功能,標註: 對於比較明顯的就不需要說明了。
// 輸入輸出,
// 函式是否分配了必須由呼叫者釋放的空間。
// 引數是否可以為空指標,或者在函式內部進行空指標檢測,提示錯誤!用 glog CHECK()巨集。
void GoogleTest(); // 儘量使用這種在名稱空間中的函式,不要在名稱空間外部定義全域性函式,而 static 函式要用在類中


// 型別名稱的每個單詞首字母均大寫, 不包含下劃線
using VectorInt = std::vector<int>;
typedef std::vector<int> VectorInt;

// 內部變數命名規則與普通變數命名規則一致,小寫加下劃線連結,但是不需要結尾加下劃線,恰好區分 class
struct GoogleSection {
    int data_member;        // 變數:不需要下劃線結尾,當做普通變數處理,小寫字母加下劃線
    int data_2;
    static int kStructTest; // 用 k 開頭
    const int kData = 3;
};
// 列表初始化
GoogleSection cc{2,3};

// 函式程式碼比較少 10 行的函式宣告為行內函數,且宣告和定義都放在標頭檔案中,否則編譯失敗。
// 對於包含 for()等迴圈 switch 的情況,最好不要用內聯,除非,for()等迴圈 switch 用的少,或基本很少執行。
// 當然對於有些雖然宣告為內聯,但是編譯器有時不會看做內聯。比如遞迴函式、虛擬函式
inline void TestInline(int test_num) {
    int test_num_buf = test_num + 2;
    return; // 建議 void 返回格式
}


// 類註釋:在類前面,描述類的功能
// 和用法,比如 Example:...寫下簡單用法
// 除非它的功能相當明顯。一般情況下可以不說明,用分行 // 註釋法
// 是否可多執行緒訪問例項,需要說明,以及可重入性!
// TODO: 進行一些還需要做的事情!
class GoogleNorm {  // 對應檔名字 google_norm.h google_norm.cc
public: // 公有型別宣告
    typedef int   int32;
    typedef long long int64;
    enum GoogleChoice { GLOG_CHOICE, GFLAGS_CHOICE }; // 列舉值,採用常量的命名方式。
    /*
    enum GoogleChoice { 
        GLOG_CHOICE, 
        GFLAGS_CHOICE
    }; // 列舉值,採用常量的命名方式。
    */

    // 內部變數命名規則與普通變數命名規則一致,小寫加下劃線連結,但是不需要結尾加下劃線,恰好區分 class
    struct GoogleSection {
        int data_member;        // 變數:不需要下劃線結尾,當做普通變數處理,小寫字母加下劃線
        int data_2;
        static int kStructTest; // 用 k 開頭
        const int kData = 3;
    };
private: // 私有型別宣告
    // 巢狀類前置宣告(之後在 .cc 檔案中進行定義即可)
    // 如果這個類不對外開放,那麼就在在對應的 .cc 檔案中進行定義,
    // 否則就在該標頭檔案中進行定義且改為 public 訪問許可權
    class NestClass;

public: // 外部呼叫的函式及變數
    // 屬於 static const constexpr 的資料成員要放在 public 後面,剩下其他變數全部是 private
    constexpr static const char* kConfigurationFileActionName = // k 開頭 // 因為是 static 常量
                                "intensity_to_color";
    static const kMemberStaticConst = 3;

    GoogleNorm() = default;
    // 建構函式一般不註釋,切記,讀程式碼的人知道構造/解構函式的功能,需要註明函式對引數做了什麼,是否取得指標所有權,析構清理了什麼
    GoogleNorm(int i ) {}
    GoogleNorm(const GoogleNorm &other) : section_cout_(other.section_cout_) {
        int * temp = new (nothrow) int[other.ClassStatic]();
        // 拷貝元素
        temp
    }
    ~GoogleNorm() = default;
    //or
    // 解構函式,注意釋放記憶體,以及變數對齊問題,顯示整齊
    ~GoogleNorm() {
        if (nullptr != queue_) {
            delete[] queue_;
            queue_    = nullptr;
            capacity_ = 0;
            size_     = 0;
            head_     = 0;
            tail_     = 0;
        }
    }

    // 屬性巨集或展開為屬性的巨集,寫在函式宣告或定義的最前面。 表示提醒呼叫者
    MUST_USE_RESULT bool IsOk();

    void get_const(){ return ClassStatic_; }            // 單函式內部要留白左右一個空格

    void get_section() const { return  section_cout_; } // 取值函式和設值函式要全小寫加下劃線。最好與實際成員變數對應

    // 宣告:註釋函式功能:對於明顯的可以不用說明。比如上面取值和設值函式就不用加註釋
    // 輸入輸出,
    // 函式是否分配了必須由呼叫者釋放的空間。
    // 引數是否可以為空指標,或者在函式內部進行空指標檢測,提示錯誤!用 glog CHECK()巨集。
    void MemberFunc(); // 普通函式 單詞首字母大寫!

protected:

private: // 類成員變數要加下劃線,一律小寫字母加下劃線,結尾加下劃線
    static int kClassStatic = 3;
    const int kDaysInWeek_ = 8;
    // 空格前置 風格一致
    char *c; // char &c;

    // 資料成員註釋,一般不用,但是對於變數可以接受 NULL or -1 這種值時,需要註釋代表的含義
    int section_cout_; // 類的成員 變數(靜態、非靜態),下劃線結尾
    int *p_; // 一段記憶體
};

}   // namespace code_style
}   // namespace xxxx專案名稱
**留白一行**
#endif  // CODE_STYLE_EXAMPLE_H_

參考資料

C++ 風格指南

Google 開源專案風格指南 (中文版)

歡迎大家關注我的微信公眾號「小北師兄」,好的文章會優先在裡面不定期分享!
比如:
反覆研究好幾遍,我才發現關於 CMake 變數還可以這樣理解!

開啟微信客戶端,掃描下方二維碼即可關注!

相關文章