方舟编译器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 7namespace 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 20constexpr 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 3constexpr 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 7void Func(char *str) { if (str == nullptr) { return; } // Business code }参数准备与业务实现之间
1 2 3 4 5 6 7void 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 8std::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 9void 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 19void 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