您现在的位置是:首页 >技术教程 >【代码规范】Google开源项目风格指南网站首页技术教程

【代码规范】Google开源项目风格指南

逆羽飘扬 2024-07-17 00:01:02
简介【代码规范】Google开源项目风格指南

系列综述:
?目的:本系列是个人整理为了秋招面试的,整理期间苛求每个知识点,平衡理解简易度与深入程度。
?来源:材料主要源于Google开源项目风格指南进行的,每个知识点的修正和深入主要参考各平台大佬的文章,其中也可能含有少量的个人实验自证。
?结语:如果有帮到你的地方,就点个赞关注一下呗,谢谢???!!!
?【C++】秋招&实习面经汇总篇



?点此到文末惊喜↩︎


0.扉页

译者前言

  1. 统一的编程风格的作用
    • 更容易的接收其他代码贡献者的提交,从而避免给代码阅读者和其他代码提交者造成的困扰
    • 更易于理解代码,方便人脑的模式匹配引擎推断各种标识符的含义
  2. 风格的解释
    • 可读性:风格是可读性的体现,是指导C++编程和文件组织方式的约定
    • 规则:风格是一种避免混乱的方式,规则一定要有权威性和说服力

1.头文件

  1. 头文件应该能够自给自足(self-contained)
    • 解释:已有的头文件可以为项目提供完整的支持,不用再包含额外的头文件
    • 例外:一个文件作为文本插入到代码某处。或者,文件内容实际上是其它头文件的特定平台(platform-specific)扩展部分。这些文件就要用 .inc 文件扩展名。
  2. 防止头文件的多重包含
    • 命名格式:<项目名>_<路径>_<文件名>_H_
    • 要求:所有的头文件
    // 为保证唯一性, 头文件的命名应为所在项目目录的全路径
    // 例如, 项目 foo 中的头文件 foo/src/bar/baz.h 可按如下方式保护:
    #ifndef FOO_BAR_BAZ_H_
    #define FOO_BAR_BAZ_H_
    ...
    #endif // FOO_BAR_BAZ_H_
    
  3. 避免使用前置声明
    • 正确方式:使用 #include 包含需要的头文件
    • 解释:前置声明是类、函数和模板的纯粹声明,没伴随着其定义
  4. 内联函数的使用
    • 函数体十行以内,性能关键的函数鼓励使用内联
    • 内联一个大型代码。可能减弱了指令缓存优化,导致执行更慢
    • 析构和构造函数有隐含的成员和操作,往往比看起来更长,不需要内联
    • 虚函数和递归函数不需要内联
  5. #include 的路径及顺序
    • #include的排序,不同类型间的头文件要插入空行类型内的可以使用字母排序
    // 本文件路径为google-awesome-project/src/foo/internal/Function.cpp
    // 
    // 1.本文件所要实现的头文件
    #include "foo/public/Function.h" // 优先位置
    // 2.C 系统文件
    #include <sys/types.h>
    #include <unistd.h>
    // 3.C++ 系统文件
    #include <hash_map>
    #include <vector>
    // 4.其他库的 .h 文件
    // 5.本项目内的 .h 文件
    #include "base/basictypes.h"
    #include "base/commandlineflags.h"
    #include "foo/public/comment.inc"// 纯文本文件
    

2.作用域

  1. 鼓励在文件内使用命名空间
    • 将全局作用域细分,防止全局作用域的命名冲突
    • 在命名空间的最后注释出命名空间的名字
    • 不要在命名空间 std 内声明任何东西
    • 禁止用内联命名空间
    // .h 文件
    namespace mynamespace {
    
    // 所有声明都置于命名空间中
    // 注意不要使用缩进
    class MyClass {
        public:
        ...
        void Foo();
    };
    } // namespace mynamespace
    
    // .cpp文件
    namespace mynamespace {
    // 函数定义都置于命名空间中
    void MyClass::Foo() {
        ...
    }
    // 鼓励在.cpp文件中使用匿名空间
    namespace {
    ...
    }  // namespace
    
    } // namespace mynamespace
    
    
  2. 定义在同一编译单元的函数, 被其他编译单元直接调用可能会引入不必要的耦合和链接时依赖; 静态成员函数对此尤其敏感. 可以考虑提取到新类中, 或者将函数置于独立库的命名空间内
    // 正确的
    namespace myproject {
    namespace foo_bar {
    void Function1();
    void Function2();
    }  // namespace foo_bar
    }  // namespace myproject
    
    // 错误的
    namespace myproject {
    class FooBar {
     public:
      static void Function1();
      static void Function2();
    };
    }  // namespace myproject
    
  3. 局部变量
    • 尽量声明的同时初始化:局部变量应该作用域尽可能小,距离第一次使用尽可能近
    • 基本变量类型可以作为局部变量:因为有寄存器优化,不会影响性能
    • 在循环前声明循环中要用的局部变量:特别是类的对象,可以避免频繁的构造和析构
    // 好——初始化时声明
    int j = g(); 
    // 坏——初始化和声明分离
    int i;
    i = f(); 
    // 好——声明时初始化
    vector<int> v = {1, 2}; 
    // 坏
    vector<int> v;
    v.push_back(1); 
    v.push_back(2);
    
    // 低效的实现
    for (int i = 0; i < 1000000; ++i) {
        Foo f;                  // 构造函数和析构函数分别调用 1000000 次!
        f.DoSomething(i);
    }
    // 高效的实现
    Foo f;                      // 构造函数和析构函数只调用 1 次
    for (int i = 0; i < 1000000; ++i) {
        f.DoSomething(i);
    }
    
  4. 静态和全局变量
    • 禁止使用类的静态变量:静态变量的构造函数、析构函数和初始化的顺序在 C++ 中是只有部分明确的,甚至随着构建变化而变化,导致难以发现的 bug
    • 要使用尽量在同一编译单元内(self-contained)
    • 多线程中的全局变量 (含静态成员变量) 不要使用 class 类型 (含 STL 容器), 避免不明确行为导致的 bug.
    • 作用域的使用, 除了考虑名称污染, 可读性之外, 主要是为降低耦合, 提高编译/执行效率
    • 尽量不用全局变量, 静态变量, 静态类成员变量, 以及函数静态变量,最好单独形成编译单元

3. 类

  1. 在构造函数中不能调用虚函数:调用是不会重定向到子类的虚函数实现
  2. 不要定义隐式类型转换
    • 单参数构造函数尽量使用explicit 关键字就行修饰,避免隐式转换

// TODO:实力没达到,目前看不懂,待更新


7.命名约定

  1. 通用命名规则
    • 少用缩写:尽可能使用通俗的描述性命名,别心疼空间。
    • 可以使用广为人知的缩写:如 i 表示迭代变量和用 T 表示模板参数常见缩写表
int price_count_reader;    // 无缩写
int num_errors;            // "num" 是一个常见的写法
int num_dns_connections;   // 人人都知道 "DNS" 是什么

int n;                     // 毫无意义.
int nerr;                  // 含糊不清的缩写.
int n_comp_conns;          // 含糊不清的缩写.
int wgc_connections;       // 只有贵团队知道是什么意思.
int pc_reader;             // "pc" 有太多可能的解释了.
int cstmr_id;              // 删减了若干字母.
  1. 文件命名
    • 全部小写
    • 使用_进行分割
    // 示例
    my_useful_class.cc
    http_server_logs.h// 比logs.h好
    
  2. 内联函数定义必须放在 .h 文件中。如果内联函数比较短,就直接将实现也放在 .h 中。
  3. 类、结构体和函数(大驼峰法)
    • 每个单词首字母都大写,但只有一个单词时不大写
    • 缩写的单词, 更倾向于将它们视作一个单词进行首字母大写
    // 类和结构体
    class UrlTable { ...
    class UrlTableTester { ...
    struct UrlTableProperties { ...
    
    // 类型定义
    typedef hash_map<UrlTableProperties *, string> PropertiesMap;
    
    // using 别名
    using PropertiesMap = hash_map<UrlTableProperties *, string>;
    
    // 枚举
    enum UrlTableErrors { ...
    // 函数
    int count()// 只有一个单词时,不大写
    void AddTableEntry()
    void DeleteUrl()
    void OpenFileOrDie()
    void StartRpc()// 不是使用StartRPC()
    
  4. 所有变量 (包括函数参数和成员变量) 和命名空间
    • 一律小写, 单词之间用下划线连接
    // 普通变量命名
    string table_name;  // 好 - 用下划线.
    string tablename;   // 好 - 全小写.
    string tableName;  // 差 - 混合大小写
    // 类数据成员
    class TableInfo {
      ...
     private:
      string table_name_;  // 好 - 后加下划线.
      string tablename_;   // 好.
      static Pool<TableInfo>* pool_;  // 好.
    };
    // 结构体内部的变量
    struct UrlTableProperties {
      string name;
      int num_entries;
      static Pool<UrlTableProperties>* pool;
    };
    // 命名空间
    namespace web_search{
    
    }
    web_search::index_util
    
  5. const声明的常量、枚举命名
    • 在程序运行期间其值始终保持不变的, 命名时以 “k” 开头, 大小写混合
    // const声明的常量
    const int kDaysInAWeek = 7;// 数学中的常量表示为k
    // 枚举命名,宏命名方式可能产生冲突
    enum UrlTableErrors {
        kOK = 0,
        kErrorOutOfMemory,
        kErrorMalformedInput,
    };
    
  6. 函数命名
    • 函数名的每个单词首字母大写
    • 缩写的单词, 更倾向于将它们视作一个单词进行首字母大写
    AddTableEntry()
    DeleteUrl()
    OpenFileOrDie()
    StartRpc()// 不是使用StartRPC()
    
    • 使用全大写,并使用下划线区分
    #define ROUND(x) ...
    #define PI_ROUNDED 3.14
    
  7. 类内的私有数据成员
    • 尾下划线的变量命名方式
class Test{
public:
	int FunTest();
private:
	int word_;// 下划线在开始位置是系统内部的变量
};

8.注释

  1. 注释一定要慷慨,因为需要给下一个阅读你代码的人看(下一个阅读的人大概率是你自己!6!)
  2. 要公布的项目文件头部应该加入版权公告
    /*
    ** This file is part of LibreBoot progect.(项目归属)
    ** file_name.py - The core part of LibreBoot Progect (文件名和作用概述)
    ** Copyright (c) 2023-06-01 WangSen 3045672234@qq.com(项目完成时间和个人信息)
    ** Copyright (c) 2024 WangSen 3045672234@qq.com(在2024年你改了一点)
    ** All rights reserved.
    **
    */
    
  3. 每个类的定义都要附带一份注释,描述类的功能和用法
    // Function:Iterates over the contents of a GargantuanTable.
    // Example:
    //    GargantuanTableIterator* iter = table->NewIterator();
    //    for (iter->Seek("foo"); !iter->done(); iter->Next()) {
    //      process(iter->key(), iter->value());
    //    }
    //    delete iter;
    class GargantuanTableIterator {
      ...
    };
    
  4. 函数注释
    • 声明处的注释描述函数功能
      • 函数的输入输出.
      • 对类成员函数而言: 函数调用期间对象是否需要保持引用参数, 是否会释放这些参数.
      • 函数是否分配了必须由调用者释放的空间.
      • 参数是否可以为空指针.
      • 是否存在函数使用上的性能隐患
      • 如果函数是可重入的, 其同步前提是什么?
    • 定义处的注释描述函数实现
      • 描述使用的特殊技巧和算法
    // Returns an iterator for this table.  It is the client's
    // responsibility to delete the iterator when it is done with it,
    // and it must not use the iterator once the GargantuanTable object
    // on which the iterator was created has been deleted.
    //
    // The iterator is initially positioned at the beginning of the table.
    //
    // This method is equivalent to:
    //    Iterator* iter = table->NewIterator();
    //    iter->Seek("");
    //    return iter;
    // If you are going to immediately seek to another place in the
    // returned iterator, it will be faster to use NewIterator()
    // and avoid the extra seek.
    Iterator* GetIterator() const;
    
  5. 变量注释
    • 通常变量名本身足以很好说明变量用途
    • 类数据成员全局变量通常要进行注释
    • 变量特殊值含义(nullptr、0、-1等)需要进行注释
    private:
     // Used to bounds-check table accesses. -1 means
     // that we don't yet know how many entries the table has.
     int num_total_entries_;
    
  6. 实现注释
    • 对于代码中巧妙的, 晦涩的, 有趣的, 重要的地方加以注释
    • 多行注释可以使用Tab进行对齐
    • 不要描述显而易见的现象,最好描述是代码自身的描述
    DoSomething();                  // Comment here so the comments line up.
    DoSomethingElseThatIsLonger();  // Two spaces between the code and the comment.
    { // One space before comment when opening a new scope is allowed,
      // thus the comment lines up with the following comments and code.
      DoSomethingElse();  // Two spaces before line comments normally.
    }
    std::vector<string> list{
                        // Comments in braced lists describe the next element...
                        "First item",
                        // .. and should be aligned appropriately.
    "Second item"};
    DoSomething(); /* For trailing block comments, one space is fine. */
    
  7. 不完美代码的注释
    • 使用 TODO 注释表示将来某一天做某事
    // TODO(kl@gmail.com): Use a "*" here for concatenation operator.
    // TODO(Zeke) change this to use relations.
    // TODO(bug 12345): remove the "Last visitors" feature
    
  8. 弃用注释
    • 写上包含全大写的// DEPRECATED的注释, 以标记某接口为弃用状态. 注释可以放在接口声明前, 或者同一行.

9.格式

  1. 每一行代码字符数不超过 80,因为80列宽是传统标准
  2. 尽量不使用非 ASCII 字符, 使用时必须使用 UTF-8 编码
  3. 制表符
    • 使用两个空格缩进代替Tab
    • 应该设置编辑器将制表符转为空格
  4. 函数声明与定义
    • 函数形参用, + 空格进行区分
    • ) {直接加空格
    • 形参放下,可以分行放置
    • 左圆括号总是和函数名在同一行
    • 函数名和左圆括号间永远没有空格
    • 必须添加形参名
    ReturnType LongClassName::ReallyReallyReallyLongFunctionName(// 没有空格
        Type par_name1,  // 4个空格
        Type par_name2,
        Type par_name3) {// 注意空格
      DoSomething();  // 函数体两个空格
      ...
    }
    
  5. 匿名函数
    int x = 0;
    auto add_to_x = [&x](int n) { x += n; };
    
  6. if表达式
    if (condition) {  // 圆括号里没有空格.
      ...  // 2 空格缩进.
    } else if (...) {  // else 与 if 的右括号同一行.
      ...
    } else {
      ...
    }
    // 只要其中一个分支用了大括号, 两个分支都要用上大括号.
    if (condition) {
      foo;
    } else {
      bar;
    }
    // 简短语句
    if (x == kFoo) return new Foo();
    
  7. switch语句
    switch (var) {
      case 0: {  // 2 空格缩进
        ...      // 4 空格缩进
        break;
      }
      case 1: {
        ...
        break;
      }
      default: {
        assert(false);
      }
    }
    
  8. 循环语句
    while (condition) {// 圆括号前后都有空格
      doing();
    }
    for (int i = 0; i < kSomeNumber; ++i) {// 圆括号前后都有空格
      doing();
    } 
    
  9. 指针和引用表达式
    • 声明时空格前置,使用时没有空格
    x = *p;
    p = &x;
    x = r.y;
    x = r->y;
    // 空格前置
    char *c, *a;
    const string &str;
    
  10. 普通的双目运算符
    • 加减乘除等两边都要加空格
  11. 初始化
    int x = 3;
    int x(3);
    int x{3};
    string name("Some Name");
    string name = "Some Name";
    string name{"Some Name"};
    
  12. 预处理指令位于缩进代码块中, 指令也应从行首开始
    // 好 - 指令从行首开始
      if (lopsided_score) {
    #if DISASTER_PENDING      // 正确 - 从行首开始
        DropEverything();
    # if NOTIFY               // 非必要 - # 后跟空格
        NotifyClient();
    # endif
    #endif
        BackToNormal();
      }
    
  13. 类格式
    // .h头文件
    class MyClass : public OtherClass {// 注意空格
     public:      // 注意有一个空格的缩进
      // 公有的构造函数和析构函数
      MyClass();  // 标准的两空格缩进
      explicit MyClass(int var);// 单参构造函数的explicit声明
      ~MyClass() {}
      // 其他函数
      void SomeFunction();
      void SomeFunctionThatDoesNothing() {
      	doing();
      }
      void set_some_var(int var) { some_var_ = var; }
      int some_var() const { return some_var_; }
      
     protected:
      // public 放在最前面, 然后是 protected, 最后是 private
      
     private:
      // 函数部分
      bool SomeInternalFunction();
      // 数据部分:尾下划线法
      int some_var_;
      int some_other_var_;
    };
    // .cpp实现文件
    
    // 如果不能放在同一行,
    // 必须置于冒号后, 并缩进 4 个空格
    MyClass::MyClass(int var)
        : some_var_(var), some_other_var_(var + 1) {
      DoSomething();
    }
    
    // 如果初始化列表需要置于多行, 将每一个成员放在单独的一行
    // 并逐行对齐
    MyClass::MyClass(int var)
        : some_var_(var),             // 4 space indent
          some_other_var_(var + 1) {  // lined up
      DoSomething();
    }
    
    // 右大括号 } 可以和左大括号 { 放在同一行
    // 如果这样做合适的话
    MyClass::MyClass(int var)
        : some_var_(var) {}
    
    
  14. 命名空间不要增加额外的缩进层次
    // 单命名空间
    namespace {
    
    void foo() {  // 正确. 命名空间内没有额外的缩进.
      ...
    }
    
    }  // namespace
    // 嵌套命名空间
    namespace foo {
    namespace bar {
    


少年,我观你骨骼清奇,颖悟绝伦,必成人中龙凤。
不如点赞·收藏·关注一波


?点此跳转到首行↩︎

参考博客

  1. 版权规范
  2. 待定引用
  3. 待定引用
  4. 待定引用
  5. 待定引用
  6. 待定引用
  7. 待定引用
  8. 待定引用
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。