c++编码规范检查-cpplint

上一篇文章我们已经制定了编码规范,但规范条例这么多,是不可能人工 review 的,必须得用工具自动化检查才行。
因为我们使用的是 google 的编码规范,所以我们直接使用 google 提供的编码规范检查工具 cpplint 就行了。下面详细介绍一下 cpplint 的用法。

1. 下载安装

cpplint 其实就是 google styleguide 仓库中的一个 python 脚本而已,直接下载下来就可以使用了。这里我选择 git clone 整个 styleguide 工程,然后把 cpplint 目录加入到 PATH:

1
2
3
4
5
qiushao@qiushao-pc:~$ cd projects/opensources/
qiushao@qiushao-pc:~/projects/opensources$ git clone https://github.com/google/styleguide.git
qiushao@qiushao-pc:~/projects/opensources$ cd
qiushao@qiushao-pc:~$ echo 'export PATH=$PATH:/home/qiushao/projects/opensources/styleguide/cpplint' >> .bashrc
qiushao@qiushao-pc:~$ source ~/.bashrc

这样 cpplint.py 就安装完了,可以使用了。

2. 基本使用方法

我们先看看使用帮助:

1
2
3
4
5
6
7
8
qiushao@qiushao-pc:~$ cpplint.py --help

Syntax: cpplint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...]
[--counting=total|toplevel|detailed] [--root=subdir]
[--linelength=digits] [--headers=x,y,...]
[--quiet]
<file> [file] ...
......

各参数解析如下:

2.1 --verbose

cpplint 把检查结果分成 0 ~ 5 六个等级。等级 0 为输出全部结果。
换句话来说就是当错误等级 >= verbose 时,就输出。

2.2 --output

指定输出格式,默认格式是 emacs, 还可能指定为 vs7(Visual Studio) 的输出格式。

2.3 --filter

分类过滤器,决定是否要输出某些类型的错误。默认输出所有的错误类型。
- :不输出这个类型的错误。
+ :输出这个类型的错误。
比如:

1
2
3
--filter=-whitespace,+whitespace/braces
--filter=whitespace,runtime/printf,+runtime/printf_format
--filter=-,+build/include_what_you_use

可以这样来查找所有的错误类型:

1
2
3
4
5
6
7
qiushao@qiushao-pc:~$ cpplint.py --filter=
build/class
build/c++11
build/c++14
build/c++tr1
build/deprecated
...

2.4 --counting

输出结果统计,有三个选项, 默认选项是 total:

  • total :只输出错误总数
  • toplevel :输出各顶层分类的错误统计,比如说 ‘build’, ‘whitespace’
  • detailed :输出各子分类的错误统计,比如说 ‘build/class’, ‘whitespace/braces’

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
qiushao@qiushao-pc:~/source/android-10/system/core/adb$ cpplint.py --verbose=5 --counting=toplevel adb.cpp 
adb.cpp:33: <chrono> is an unapproved C++11 header. [build/c++11] [5]
adb.cpp:34: <condition_variable> is an unapproved C++11 header. [build/c++11] [5]
adb.cpp:35: <mutex> is an unapproved C++11 header. [build/c++11] [5]
adb.cpp:37: <thread> is an unapproved C++11 header. [build/c++11] [5]
adb.cpp:62: Do not use namespace using-directives. Use using-declarations instead. [build/namespaces] [5]
adb.cpp:135: Missing space before ( in switch( [whitespace/parens] [5]
adb.cpp:135: Missing space before { [whitespace/braces] [5]
adb.cpp:322: Missing space before ( in switch( [whitespace/parens] [5]
adb.cpp:322: Missing space before { [whitespace/braces] [5]
adb.cpp:390: Missing space before ( in if( [whitespace/parens] [5]
Done processing adb.cpp
Category 'build' errors found: 5
Category 'whitespace' errors found: 5
Total errors found: 10

2.5 --root

指定代码的根目录。这个参数会影响头文件保护的宏命名检查结果,比如, 有个头文件的路径为 chrome/browser/ui/browser.h, 则头文件保护的宏命名为:

1
2
3
未设置--root => CHROME_BROWSER_UI_BROWSER_H_
--root=chrome => BROWSER_UI_BROWSER_H_
--root=chrome/browser => UI_BROWSER_H_

2.6 --linelength

一行代码的长度限制。默认值为 80 个字符,超 80 个字符后,会提示错误。对于现代的大屏幕来说,我感觉 80 个字符太小了,设置为 120 比较合理。

2.7 --headers

设置头文件的扩展名,默认只接受 .h 后缀。例子:

1
2
--headers=hpp,hxx
--headers=hpp

2.8 --extensions

设置 cpplint 要检查的文件扩展名,比如:

1
--extensions=hpp,cpp

2.9 --quiet

如果没有错误,则不输出任何信息

2.10 配置文件

我们可以在代码目录里面放一个 CPPLINT.cfg 文件,把参数配置都写到里面来,比如:

1
2
3
4
5
6
set noparent
filter=+filter1,-filter2
exclude_files=regex
linelength=80
root=subdir
headers=x,y

set noparent :让 cpplint 不要搜索上级目录查找其他的 .cfg 文件。
exclude_files :排除某些文件,文件名可以使用正则表达式。

3. 添加自定义规则

cpplint 定义了很多规则,但也不可能满足所有人的需求。下面就来说说怎么扩展规则。
我们先看看 cpplint 的基本框架:
cpplint代码框架

从上面的图可以看到几个关键的函数:

  • ProcessFile : 对单个文件的整体处理,包括编码,换行符等
  • ProcessFileData : 对单个文件的内容进行处理,包括 copyright 声明等
  • ProcessLine : 对每一行进行处理,大部分规则都在这里处理了

每一项规则都定义成一个函数,以 CheckXXX 方法命名。
检查到错误之后调用 Error(filename, linenum, category, confidence, message) 函数来输出错误。

  • filename : 文件名
  • linenum : 出错行数
  • category : 错误类型
  • confidence : 错误等级, 1 ~ 5
  • message 错误提示

我们只需要参考现有的规则添加即可。基本上都是对正则表达式的使用而已。也因为 cpplint 只是使用了模式匹配的方法来做检查,并没有进行语法分析,所以有很多规则没法检查。比如,命名规则等。这也是一个非常大的缺陷。不过目前也没有其他更优秀的开源工具了,就将就着用先了。

4. git 提交前自动检查

我们的目标是在开发者 git commit 之前自动进行代码风格检查,如果检查有任何错误,则提交失败。所以这个检查需要是在本地进行的。让错误尽早地消灭在源头。幸好,git 提供了 pre-commit hooks 可以满足我们的要求。
git pre-commit 默认是关闭的,打开的方式如下:把 .git/hooks/ 目录下的 pre-commit.sample 文件重命名为 pre-commit 即可。然后稍作修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".

if git rev-parse --verify HEAD >/dev/null 2>&1
then
against=HEAD
else
# Initial commit: diff against an empty tree object
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi

# Redirect output to stderr.
exec 1>&2

cpplint=cpplint.py
sum=0
filters='-whitespace/line_length,-build/include'

# for cpp
for file in $(git diff-index --name-status $against -- | grep -E '\.[ch](pp)?$' | awk '{print $2}'); do
$cpplint --filter=$filters $file
sum=$(expr ${sum} + $?)
done

if [ ${sum} -eq 0 ]; then
exit 0
else
exit 1
fi

我们来尝试提交一个有错误的代码:

1
2
3
4
5
6
7
qiushao@qiushao-pc:~/projects/clion/test$ git commit -m 'test'      
main.cpp:0: No copyright message found. You should have a line: "Copyright [year] <Copyright Owner>" [legal/copyright] [5]
main.cpp:2: Line contains invalid UTF-8 (or Unicode replacement character). [readability/utf8] [5]
main.cpp:7: Could not find a newline character at the end of the file. [whitespace/ending_newline] [5]
Done processing main.cpp
Total errors found: 3
qiushao@qiushao-pc:~/projects/clion/test$

pre-commit 脚本的返回值为非 0 值,则认为有错误,就会提交失败。
上面这个例子把 filters 参数直接写在这个脚本里面了,实际应用时最好写到 CPPLINT.cfg 配置文件里面。