Google C++ Style Guide并不是一个百科全书,也不是一个C++使用指南,但它描述适用于Google及其开源项目的编码指南,并不追求全面和绝对正确,也有许多人置疑它的一些规则。但作为一个最具影响力的编码规范,它里面有许多内容值得我们研究学习。
以下主要摘自GSG负责人Titus Winters在CppCon 2014上的演讲。
制订Google C++ Style Guide的目的
引导开发去做对的事,同时不易犯错。
哲学总结
关注于读者,而非作者
(Optimize for the reader, not the writer.)
从维护的代码规模以及团队协作的角度考虑,Google C++ Style Guide的制订更加偏向于读者的体验,而非代码作者的感受。原因是代码被用于阅读和理解的开销要远大于编码。
适度的规则
(Rules should pull their weight.)
法无具细则无法,所以不会列出所有不能做的事项。对于未提到内容,则遵循一个大原则:”低调 (Don’t be clever.)”。(另一种相对的做法就是,不要犯傻(Don’t be stupid)。)
标准很重要,但不能迷信
(Value the standard, but don’t Idolize.)
标准对于工作非常重要,但不代表它是万能的。可以通过cppreference.com或stackoverflow去跟进标准的变化,以及相关的讨论,这样更有助于理解标准、使用标准。
保持一致性
(Be consistent.)
这是最为关键的一项。保持一致性有助于分解工作,并且更好的协作,包括自动化,减少对一些不必要的争论。在GSG中有助于一致性的规则包括:
* Include guard的命名
* 参数顺序 (先输入,再输出)
* 命名空间
* 声明顺序 (类里先public, 后private, 先方法,再成员变量)
* 命名规则
* 格式化 (这种事还是交给工具吧)
通过清晰、明确的方式避免歧义和出错的可能性
(If something unusual is happening, leave explicit evidence for the reader)
核心是以明确的方式编写代码。比如:
* 常量引用代表输出参数,指针表示会被函数修改。
* override和final对成员函数的修饰。
* Interface类、Client类等的名称使用相应的后缀(Interface,Client)。再
* 函数的重载就没有使用不同的函数名来得明确。
* 可变参数、以及缺省函数参数都可能导致使用者理解上的问题,而有误用的可能。
* 使用异常处理(Exception),不如错误处理(Error handling)明确。
避免风险难控的实现,亦或是奇巧淫技,因为太难维护。
(Avoid constructs that are dangerous or suprising, Avoid tricky and hard-to-maintain constructs)
这里包括了两条,一是有风险,或者冷不丁会给个惊喜的实现方式:
* 复杂的static和全局对象,会在退出时出现问题。
* 使用override和final可以避免错误使用。
* 使用异常常常会转移错误,是非常危险的。
另一条是一些隐晦的技术会带来维护上的压力。比如:
* 不要使用宏 (复杂,不清晰)
* 模板类元编程 (复杂)
* 非公有化的继承 (可能会带来意外惊吓)
* 多重继承 (很难维护)
不要污染全局命名空间
(Avoid polluting the global namespace)
因为代码太大,如果没有控制,全局命名空间里的冲突可能性非常高。所以一个重要的规则就是限制使用全局命空间。也是分而治之的思想。重点是:
* 将代码放到命名空间里。
* 不要在头文件里将using放到全局命名空间里。它会随着引用依赖而扩散。
* 在.cc文件就没那么重要,但仍然建议将static变量放到匿名命名空间里,不要使用using namespace来引用其它命名空间。
必要时向调优及实用性让步
(Concede to optimization and practicalities when necessary)
有一些规则主要推荐某种更优的实践。比如:
* 前置声明 (优化编译时间)
* Inline函数 (限定于较小的函数)
* 推荐使用前置递增 (++i, 特别是迭代器)
关于争议性的规则
Titus通过对两个最具争议的规则说明了如何运用上面的规则达成目前的结论的。两个最具争议的规则是:
* 不能有非常量引用的输入参数 (No Non-const reference as function arguments)
* 不使用异常 (No use of exceptions)
我这里只说明第一条所依据的规则:
* 一致性 (和众多代码实现保持一致)
* 清晰、明确,便于排查 (有没有不合预期的修改,甚至编译期就可以预防问题)
* 有风险、可能带来意外惊喜
引用的生命周期问题。原因是开发者对指针的生命周期比较敏感,但对引用的生命周期管理较容易忽视。
其中的要点就是在定义规则时要知道为什么要制订它,什么时候应用它, 以及未来又将如何修改它。