方舟编译器C++语言编程规范增强:空行
Contents
文章通过段落将内容变得更具节奏,代码通过换行来达到相同的效果。换行对于代码阅读来说非常重要,过多的空行会减少屏幕显示的有效代码;过少的空行又易使代码上下黏连,造成“文不加点”的阅读困惑;不恰当的换行有可能使语义割裂,从而形成阅读障碍,甚至具有误导性。
格式类规范总体要求:凸显相关内容的关联性,隔离不同分组,使代码简洁而又层次分明。
如果任何的编码规范违背了上述要求,均应避免削足适履,当跳出规范外进行设计,将好的设计作为特例场景固化。
基础规则
完全禁止使用连续空行
若存在希望通过两个连续空行来带代码进行分块的场景,使用单空行+注释的方式进行分割。
文件域布局
文件层级经常会包含以下元素:文件头注释(包含版权声明)、#include
、namespace
、常量、enum
/union
/struct
/class
、全局函数。其他还有:全局变量、宏、extern
声明、using namespace
、using
/typedef
。约定整体布局如下:
|
|
文件头尾
注释
上置注释与其注释内容间不应有空行。
更有严者,除右置注释外,其他注释均不应与下方代码之间有空行,即换种说法,不应存在无对应代码的注释。
特殊场景解释:文件头注释并非注释头文件保护#define
,或头文件引用#include
,但无空行对可读性无负面影响,且使代码更加紧凑,所以约定头文件注释与以上两者之间无空行。若源文件中头文件注释紧跟的为namespace
或其他代码,则文件头注释需要与下文保持一个空行,为未来可能添加#include
预留空间。
头文件引用代码域
头文件引用#include
代码域与其上内容不留空行,但与其下的namespace
或其他代码片段之间有空行。
通常来说#include
之间不应有空行,但若基于以下几个原因之一,或其他充分的解释,可以适当添加空行:
包含大量头文件(半屏乃至一屏),难以管理
头文件分组更易做扩展(如
phase_manager
中对phase
的头文件按类别分组)《方舟编译器C++语言编程规范》中例外的场景
例外: 平台特定代码需要条件编译,这些代码可以放到其它 includes 之后。
1 2 3 4 5 6 7
#include "foo/public/FooServer.h" #include "base/Port.h" // For LANG_CXX11. #ifdef LANG_CXX11 #include <initializer_list> #endif // LANG_CXX11
下面以一个C++
的分类方式进行分组示例(Test.cpp):
|
|
命名空间代码域
命名空间代码域与其上内容之间有空行(在文件起始例外),但与其下的代码片段之间有空行。
《方舟编译器C++语言编程规范》
- 大括号内的代码块行首之前和行尾之后不要加空行。
*”与其下的代码片段之间有空行”*违背了开源规范中的此原则,但此开源规范应当有一条先决条件,即当大括号内代码块缩进级别多于大括号的代码块时。所以如下代码,依然需要空行,避免干扰下方代码块的阅读。
1 2 3 4 5 6 7
namespace maple { template <typename T> void Func(T &) { } } // maple
命名空间代码域包含以下几类内容:
- 声明命名空间
namespace
- 引用命名空间
using namespace
- 引用类型
using Type
这几类内容中间均无空行,如下所示。
|
|
命名空间的右括号由于需要添加空间结束的右置注释,所以其与上方代码块之间的空行多数场景可以考虑省略。即
|
|
或
|
|
类型代码域
类型代码域包含了enum
/enum class
/union
/struct
/class
/GlobalFunction
(全局函数可看作类型,乃是从仿函数的角度来看,包括其使用大驼峰命名)。其各自形成块,并以}
或};
作为终结符。
基础规则:类型代码域中每个类型之间必须留空行,与其他代码域之间必须留空行。由于每个类型均包含相当多的信息,一般不会在一个行块内定义完全,这些类型自身已形成一个分组,所以他们之间必须留空行。
例外场景如下:
类型与命名空间的结束符(
}
)之间的空行,在不影响阅读的情况下,往往可以移除。constexpr
/const
常量、using
/typedef
与类型有强关联,可以形成大的分组时,可看作一个整体,其内部的空行多数可以省略。示例如下:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
constexpr int kScopeLocal = 2; // the default scope level for function variables constexpr int kScopeGlobal = 1; // the scope level for global variables constexpr int kSymKindCount = 7; enum MIRSymKind { kStInvalid, kStVar, ... }; using HolderType = std::vector<int32_t>; void Func(const HolderType &data) { for (int32_t elem : data) {} } constexpr int kNumberLimit = 32; // Document for `Num` class Number { public: };
常量代码域
常量代码域包含了constexpr
/const
定义的常量,以及enum
/enum class
定义的枚举常量,枚举常量随类型代码域中的规约。constexpr
/const
定义的常量从是否需要进行显示分组的角度,可以分为以下两种场景进行参考(分模块、分组也是设计中重要的一份内容,尤其是在本文中,会经常提及)。
无显示分组
1 2 3
constexpr int kScopeLocal = 2; // the default scope level for function variables constexpr int kScopeGlobal = 1; // the scope level for global variables constexpr int kSymKindCount = 7;
按业务显示分组
1 2 3 4 5 6
// Consts of Scope. constexpr int kScopeLocal = 2; // the default scope level for function variables constexpr int kScopeGlobal = 1; // the scope level for global variables // Consts of SymolKind constexpr int kSymKindCount = 7;
常量代码域与其他代码域之间需要保留一个空行。
其他
函数宏的规则同函数,常量宏的规则同常量
集中的
extern
声明往往命名空间代码域下,规则同常量using
/typedef
通常不会有集中声明,若存在,规则同常量全局变量上下必须有空行,并有详细的注释来解释必须使用全局变量的原因
类域布局
enum
/enum class
/union
/struct
在C++
中,这几种关键字创建的为数据对象,而class
创建的通常是算法对象或业务对象。这也是为什么当class
定义的类型中出现大量Get
/Set
成员变量时,会看起来比较新手,C++
有自己的数据对象,而且对象设计应尽量遵循**Tell, Don’t ask"原则,题外话了。
这里以struct
作为示例,struct
包含成员函数的场景同class
。
|
|
- 成员变量与结构体定义的
{
、}
之间均无空行 - 成员变量之间无空行。若要对成员变量进行分组,可以使用注释。
class
|
|
在struct
规范的基础上,plublic
/protected
/private
可见性关键字上方需要空格,而下方紧接代码,无空格。除成员函数例外,其他如类内的常量、成员变量、using/typedef
同文件域布局::常量代码域,类内静态成员变量同全局变量。
成员函数间必须要空行,但也有例外:
- 构造函数与析构函数间的空行可以省去,构造函数上方的空行以及析构函数下方的空行不能省。
- 对于纯函数声明,若多个函数存在相关性,即可以分组,则他们之间的空行可以省去。
函数域布局
函数域内的空行难以用精确的规则来约束,此处只能尽量描述其思想以及常见插入空行的示例。函数内空行的基本原则是:
- 避免为每行代码都追加一个空行,会导致代码过于松散
- 避免超过5行或10行且缩进层级少的代码中,一个空行都没有,或导致代码
- 避免随性切割代码,用以达到适当换行这样的描述
代码块之间好的空行应有类似阅读时抑扬顿挫之感。以下是几个常见可以考虑插入换行的场景:
入参校验与业务实现之间
1 2 3 4 5 6 7
void Func(char *str) { if (str == nullptr) { return; } // Business code }
参数准备与业务实现之间
1 2 3 4 5 6 7
void Func(const std::string &filePath) { std::string fileName = filePath.substr(); std::string fileType = fileName.substr(); std::string dirName = filePath.substr(); // Business code }
业务实现与构建返回信息
1 2 3 4 5 6 7 8
std::vector<int32_t> Func(const std::string &filePath) { // Business code std::vector<int32_t> rst; rst.push_back(0); rst.push_back(1); return rst; }
业务实现与区域资源回收之间
1 2 3 4 5 6 7 8 9
void Func(int32_t dataCode1, int32_t dataCode2) { LockResource(dataCode1); LockResource(dataCode2); // Business code UnlockResource(dataCode1); UnlockResource(dataCode2); }
平行的业务实现与业务实现之间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
void Func(char *str) { // Check input if (str == nullptr) { return; } // Check input std::string fileName = filePath.substr(); std::string fileType = fileName.substr(); if (fileType != "csv") { return; } std::string dirName = filePath.substr(); if (dirName.find("..") != std::string::npos) { return; } // Business code }
函数域内空行添加空行的原则最终的原则:无论从结构设计抑或是业务概念上,可以进行分组,在不会显得松散的情况下,可以添加空行。换句话说想好怎么回复质疑的理由,那便可以换行。
Author 朦呆农码
LastMod 2020-02-07