<audio id="audio" title="02 | 编码阶段能做什么：秀出好的code style" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7e/d7/7edff57af7a2f188139c7783ba030fd7.mp3"></audio>

你好，我是Chrono。

上节课我介绍了C++程序的生命周期和编程范式，今天我就接着展开来讲，看看在编码阶段我们能做哪些事情。

在编码阶段，我们的核心任务就是写出在预处理、编译和运行等不同阶段里执行的代码，还记得那句编程格言吗：

“**任何人都能写出机器能看懂的代码，但只有优秀的程序员才能写出人能看懂的代码。**”

所以，我们在编码阶段的首要目标，不是实现功能，而是要写出清晰易读的代码，也就是要有好的code style。

怎么样才能写出human readable的好代码呢？

这就需要有一些明确的、经过实践验证的规则来指导，只要自觉遵守、合理运用这些规则，想把代码写“烂”都很难。

在此，我强烈推荐一份非常棒的[指南](http://openresty.org/cn/c-coding-style-guide.html)，它来自OpenResty的作者章亦春，代码风格参照的是顶级开源产品Nginx，内容非常详细完善。

不过有一点要注意，这份指南描述的是C语言，但对于C++仍然有很好的指导意义，所以接下来我就以它为基础，加上我的工作体会，从**代码格式**、**标识符命名**和**注释**三个方面，讲一下怎么“秀出好的code style”。

## 空格与空行

当我们拿到一份编码风格指南的时候，不论它是公司内部的还是外部的，通常第一感觉就是“头大”，几十个、上百个的条款罗列在一起，规则甚至细致到了标点符号，再配上干巴巴的说明和示例，不花个半天功夫是绝对看不完的。而且，最大的可能是半途而废，成了“从入门到放弃”。

我写了很多年代码，也看过不少这样的文档，我从中总结出了一条最基本、最关键的规则，只要掌握了这条规则，就可以把你code style的“颜值”起码提高80%。

这条“神奇”的规则是什么呢？

认真听，只有五个字：**留白的艺术**。

再多说一点，就是像“写诗”一样去写代码，**恰当地运用空格和空行**。不要为了“节省篇幅”和“紧凑”而把很多语句挤在一起，而是要多用空格分隔开变量与操作符，用空行分隔开代码块，保持适当的阅读节奏。

你可以看看下面的这个示例，这是我从某个实际的项目中摘出来的真实代码（当然，我隐去了一些敏感信息）：

```
if(!value.contains(&quot;xxx&quot;)){
  LOGIT(WARNING,&quot;value is incomplete.\n&quot;)
  return;
}
char suffix[16]=&quot;xxx&quot;;
int  data_len = 100;
if(!value.empty()&amp;&amp;value.contains(&quot;tom&quot;)){
  const char* name=value.c_str();
  for(int i=0;i&lt;MAX_LEN;i++){
    ...  // do something
  }
  int count=0;
  for(int i=0;i&lt;strlen(name);i++){
    ... // do something  
  }
}

```

这段代码真可谓是“高密度”，密密麻麻一大堆，看着“有如滔滔江水，连绵不绝”，读起来让人“窒息”，code style非常糟糕。

应用“留白的艺术”，代码就变成了下面的样子：

```
if (!value.contains(&quot;xxx&quot;)) {                    // if后{前有空格
  LOGIT(WARNING, &quot;value is incomplete.\n&quot;)        // 逗号后有空格
  return;                                      // 逻辑联系紧密就不用加空行
}
                                                  // 新增空行分隔段落
char suffix[16] = &quot;xxx&quot;;                          // 等号两边有空格
int data_len = 100;                            // 逻辑联系紧密就不用加空行
                                                  // 新增空行分隔段落
if (!value.empty() &amp;&amp; value.contains(&quot;tom&quot;)) {   // &amp;&amp;两边有空格
  const char* name = value.c_str();              // 等号两边有空格
                                                  // 新增空行分隔段落
  for(int i = 0; i &lt; MAX_LEN; i++){              // =;&lt;处有空格
    ...  // do something
  }
                                                  // 新增空行分隔段落
  int count = 0;                                  // 等号两边有空格
                                                  // 新增空行分隔段落
  for(int i = 0; i &lt; strlen(name); i++){          // =;&lt;处有空格
    ... // do something  
  }
}

```

加上了适当的空格和空行后，代码就显得错落有致，舒缓得当，看着就像是莎翁的十四行诗，读起来不那么累，也更容易理清楚代码的逻辑。

我还有个私人观点：**好程序里的空白行至少要占到总行数的20%以上**。虽然比较“极端”，但也不失为一个可量化的指标，你可以在今后的实际工作中尝试一下。

## 起个好名字

有了好的代码格式，接下来我们要操心的就是里面的内容了，而其中一个很重要的部分就是为变量、函数、类、项目等起一个好听易懂的名字。

这里有一个广泛流传的笑话：“**缓存失效与命名是计算机科学的两大难题**。”把起名字与缓存失效（也有说是并发）相提并论，足见它有多么重要了，值得引起你的重视。

但其实命名这件事并不难，主要就在于平时的词汇和经验积累，知道在什么情况下用哪个单词最合适，千万不要偷懒用“谜之缩写”和汉语拼音（更有甚者，是汉语拼音的缩写）。由于现在搜索引擎、电子词典都很发达，只要你有足够认真的态度，在网上一搜，就能够找到合适的名字。

另外，你还可以用一些已经在程序员之间形成了普遍共识的变量名，比如用于循环的i/j/k、用于计数的count、表示指针的p/ptr、表示缓冲区的buf/buffer、表示变化量的delta、表示总和的sum……

关于命名的风格，我所知道的应用比较广的有三种。

第一种风格叫“匈牙利命名法”，在早期的Windows上很流行，使用前缀i/n/sz等来表示变量的类型，比如iNum/szName。它把类型信息做了“硬编码”，不适合代码重构和泛型编程，所以目前基本上被淘汰了。

不过它里面有一种做法我还是比较欣赏的，就是给成员变量加“m_”前缀（member），给全局变量加“g_”前缀（global），比如m_count、g_total，这样一看就知道了变量的作用域，在大型程序里还是挺有用的。

第二种风格叫“CamelCase”，也就是“驼峰式命名法”，在Java语言里非常流行，主张单词首字母大写，比如MyJobClass、tryToLock，但这种风格在C++世界里的接受程度不是太高。

第三种风格叫“snake_case”，用的是全小写，单词之间用下划线连接。这是C和C++主要采用的命名方式，看一下标准库，里面的vector、unordered_set、shrink_to_fit都是这样。

那么，你该选用哪种命名风格呢？

我的建议是“取百家之长”，混用这几种中能够让名字辨识度最高的那些优点，就是四条规则：

1. 变量、函数名和名字空间用snake_case，全局变量加“g_”前缀；
1. 自定义类名用CamelCase，成员函数用snake_case，成员变量加“m_”前缀；
1. 宏和常量应当全大写，单词之间用下划线连接；
1. 尽量不要用下划线作为变量的前缀或者后缀（比如_local、name_），很难识别。

下面我举几个例子，你一看就能明白了：

```
#define  MAX_PATH_LEN  256             //常量，全大写

int g_sys_flag;                        // 全局变量，加g_前缀

namespace linux_sys {                // 名字空间，全小写
  void get_rlimit_core();               // 函数，全小写
}

class FilePath final                 // 类名，首字母大写
{
public:
  void set_path(const string&amp; str);    // 函数，全小写
private:  
  string m_path;                      // 成员变量，m_前缀 
  int    m_level;                     // 成员变量，m_前缀
};



```

命名另一个相关的问题是“名字的长度”，有人喜欢写得长，有人喜欢写得短，我觉得都可以，只要易读易写就行。

不过一个被普遍认可的原则是：**变量/函数的名字长度与它的作用域成正比**，也就是说，局部变量/函数名可以短一点，而全局变量/函数名应该长一点。

想一下，如果你辛辛苦苦起了一个包含四五个单词的长名字，却只能在短短十几行的循环体里使用，岂不是太浪费了？

## 用好注释

写出了有好名字的变量、函数和类还不够，要让其他人能“一眼”看懂代码，还需要加上注释。

“注释”在任何编程语言里都是一项非常重要的功能，甚至在编程语言之外，比如配置文件（ini、yml）、标记语言（html、xml），都有注释。一个突出的反例就是JSON，没有注释功能让许多人都很不适应。

注释表面上的功能很简单，就是给代码配上额外的文字，起到注解、补充说明的作用。但就像是写文章一样，写些什么、写多写少、写成什么样子，都是大有讲究的。

你可能也有不少写注释的经验了，一般来说，注释可以用来阐述目的、用途、工作原理、注意事项等代码本身无法“自说明”的那些东西。

但要小心，注释必须要正确、清晰、有效，尽量言简意赅、点到为止，不要画蛇添足，更不能写出含糊、错误的注释。

比如说，有这么一个模板函数get_value：

```
template&lt;typename T&gt;
int get_value(const T&amp; v);

```

代码很简单，但可用的信息太少了，你就可以给它加上作者、功能说明、调用注意事项、可能的返回值，等等，这样看起来就会舒服得多：

```
// author  : chrono
// date    : 2020-xx-xx
// purpose : get inner counter value of generic T
// notice  : T must have xxx member
// notice  : return value maybe -1, means xxx, you should xxx
template&lt;typename T&gt;
int get_value(const T&amp; v);

```

你可能注意到了，在注释里我都用的是英文，因为英文（ASCII，或者说是UTF-8）的“兼容性”最好，不会由于操作系统、编码的问题变成无法阅读的乱码，而且还能够锻炼自己的英语表达能力。

不过，用英文写注释的同时也对你提出了更高的要求，最基本的是**不要出现低级的语法、拼写错误**。别笑，我就经常见到有人英文水平不佳，或者是“敷衍了事”，写出的都是“Chinglish”，看了让人哭笑不得。

写注释最好也要有一些标准的格式，比如用统一的“标签”来标记作者、参数说明什么的。这方面我觉得你可以参考Javadoc，它算是一个不错的工程化实践。

对于C++来说，也有一个类似的工具叫Doxgen，用好它甚至可以直接从源码生成完整的API文档。不过我个人觉得，Doxgen的格式有些过于“死板”，很难严格执行，是否采用就在于你自己了。

除了给代码、函数、类写注释，我还建议**最好在文件的开头写上本文件的注释**，里面有文件的版权声明、更新历史、功能描述，等等。

下面这个就是我比较常用的一个文件头注释，简单明了，你可以参考一下。

```
// Copyright (c) 2020 by Chrono
//
// file  : xxx.cpp
// since : 2020-xx-xx
// desc  : ...

```

另外，注释还有一个很有用的功能就是todo，作为功能的占位符，提醒将来的代码维护者（也许就是你），比如：

```
// TODO: change it to unordered_map
// XXX: fixme later

```

总的来说，要写好注释，你要时刻“换位思考”，设身处地去想别人怎么看你的代码，这样的话，上面的那些细则也就不难实施了。

## 小结

在编码阶段，拥有一个良好的编程习惯和态度是非常重要的（我见过太多对此漫不经心的“老”程序员）。今天，我只介绍了三个最基本的部分，再来“敲黑板”画个重点：

1. 用好空格和空行，多留白，让写代码就像写诗一样；
1. 给变量、函数、类起个好名字，你的代码就成功了一半；
1. 给变量、函数、类再加上注释，让代码自带文档，就成了“人能够看懂的代码”。

有了这个基础，你还可以更进一步，使用其他高级规则写出更好的代码，比如函数体不能太长、入口参数不宜过多，避免使用else/switch导致层次太深（圈复杂度），等等，这些虽然也很有用但比较琐碎，暂时就不细说了。

对了，还有一招“必杀技”：善用code review，和你周围的同事互相审查代码，可以迅速改善自己的code style。

## 课下作业

最后是课下作业时间，给你留两个思考题：

1. 你用过哪些好的code style，你最喜欢今天介绍的哪几条？
1. 注释在代码里通常的作用是“说明文档”，但它还有另外一个重要的用法，你知道吗？

欢迎你在留言区写下你的思考和答案，如果觉得对你有所帮助，也欢迎分享给你的朋友，我们下节课见。

<img src="https://static001.geekbang.org/resource/image/3a/41/3a5325510a8c10a318f82f9ac2696941.jpg" alt="">
