“敲击少”格式化风格

需求

  1. 稣希望保持手指的健康,尽量少敲击。

  2. 面对“这样可以,那样也可以”的规范,稣希望能贯彻某种原则,以减少内耗。

思考

以上两条在多数情况下是不矛盾的,所以就可以把第一条作为原则:尽量少敲击!

但要注意的是,本文讨论的是编码风格里最低级的议题——格式。所以它不涉及如何命名,比如说变量、函数的命名,应该贯彻可读性好的原则,别乱缩写。

“敲击少”的原则仅用于信息量完全一样的场景!

具体规则

1. 缩进 2 个空格即可

在视觉上 2 个空格已经足够分辨。

著名风格中,使用 2 个空格缩进的有:LLVM、Google、Chromium、GNU、Mozilla,用 4 个的只有:Microsoft 和 WebKit。可见 2 个才是主流。

ClangFormat 配置为:

1
IndentWidth: 2

2. 左对齐节省空格

2.1 指针左对齐(PointerAlignment)

C++ 不像 Go、Rust 那样有“官方的”格式化风格,所以指针符号怎么对齐就有四种样式:

  • 左:int* p;
  • 右:int *p;
  • 中:int * p;
  • 哦不:int*p;

著名风格主要分为两派:Google、Chromium、Mozilla、WebKit 使用左对齐,而 LLVM、GNU、Microsoft 使用右对齐。

作为规范制定者,淘汰掉最后一种是极其容易的——因为 ClangFormat 的 PointerAlignment 没有这个选项!而 Middle,则是因为没有著名风格使用,也应该被淘汰!

接下来,稣便利用“敲击少”的原则在“左”和“右”中二选一!注意到:在类型转换中,两者是不同的:

1
2
void*p{};
auto i = static_cast<int*>(p);

在左样式时,被格式化为:

1
2
void* p{};
auto i = static_cast<int*>(p);

而使用右样式,则被格式化为:

1
2
void *p{};
auto i = static_cast<int *>(p);

可见右样式在类型转换场景下会比左样式多出一个空格,这违反了“敲击少”的原则,所以稣愉快地选择了左样式

同理,ReferenceAlignment 应该和 PointerAlignment 一样,所以可以把其值设为 Pointer 或 Left。

em,本条还没完……还有一个 DerivePointerAlignment 应该设为 false。如果是 true,ClangFormat 会分析格式化文件,以确定最常见的 & 和 * 对齐方式。指针和引用对齐样式将根据文件中找到的首选项进行更新。然后,PointerAlignment 仅用作后备。

一份 .clang-format 的相关部分参考:

1
2
3
DerivePointerAlignment: false
PointerAlignment: Left
ReferenceAlignment: Pointer

2.2 AlignEscapedNewlines 左对齐

在转义的换行符中对齐反斜杠的选项。可能的值:

  • DontAlign:不要对齐转义的换行符。(难看)
  • Left:将转义的换行符尽可能左对齐。
  • Right:将转义的换行符尽可能右对齐。(最好看,但空格更多)

AlignEscapedNewlines 使用 Left 的著名风格有:Google、Chromium,使用 Right 的有:LLVM、GNU、Microsoft、Mozilla、WebKit。

3. 在不影响可读性时,尽量少换行

通常来说,在语句块之间插入换行或注释,可以提高可读性。比如说:

1
2
3
4
5
6
7
8
9
10
render_log_init();

struct render_context_args ctx_args;
bool ok = render_server_main(argc, argv, &ctx_args);

/* this is a subprocess */
if (ok && ctx_args.valid)
ok = render_context_main(&ctx_args);

return ok ? 0 : -1;

如果把空行都去掉,就比较难一眼看清这些代码做了几件事。当然,如果每个语句块的开头都有注释,其实也可以不要空行,只不过注释可不是人人都爱写,并且,一些通过命名自注释的语句块,再写注释确实显得重复啰嗦。

但是,反转来了——有些地方换不换行不影响可读性。比如说 { 之前的换行、函数返回值类型后的换行……

3.1 BraceWrapping

事实上,缩进已经能保证可读性,{ 有没有和 } 垂直对齐,已经不重要。(每行不要太长更重要点!)

所以,在 .clang-format 的 BraceWrapping 节下面,应该越多 false/Never 越好,比如 Google 和 Chromium 都是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterExternBlock: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true

在著名风格里,论 { 之前省换行,Google 和 Chromium 是并列第一名。

3.2 BreakAfterReturnType

这个选项有 5 个可选样式值:

  • None:自动
  • All:总是在返回类型之后换行,包括定义和声明。
  • TopLevel:总是在顶级函数的返回类型之后换行,包括定义和声明。
  • AllDefinitions:总是在函数定义的返回类型之后换行。
  • TopLevelDefinitions:总是在顶级定义的返回类型之后换行。

主流的著名风格使用 None,比如 LLVM、Google、Chromium、Microsoft、WebKit,只有两个例外:

  • Mozilla 采用 TopLevel
  • GNU 采用 AllDefinitions

结论

在“敲击少”原则的筛选下,著名风格里只有 Google、Chromium 生存下来。

扩展

  1. 指针判空》时,应该省略和 nullptr 比较。

  2. 从机器的角度看“少”,应该把换行符指定为 LF,毕竟在 UTF-8 编码时,每行能省一字节。参考:《Git 换行符设置终极指南

如果您使用微信,也可以关注公众号 UMU618,在公众号文章里评论。