概述
说到代码质量,这个是程序员职业生涯,至少是编码生涯的永久性话题;不同时期,见解也有不同。个人认为,不存在什么绝对错误和绝对正确,当然那种写出明显的空指针异常排除在外。有时候编码质量是一个见仁见智,个人的习惯问题。但是,编码也是群体活动,有个共同遵守的规范是必须的,也是必要的。
写在前面,本文所述的所有的工具,基本上都是考虑到Java语言层面,当然这些工具也可能适用其他语言。
代码质量检查工具
PMD
代码库一直在持续更新的代码质量检查工具,通过静态分析获知代码错误,即无需运行Java程序的情况下就报告错误。提供各大 IDE 插件,提供 maven-pmd-plugin
maven插件。最新版的 Java 语言质量检查规则。支持用户自定义规则。下面列出一些检查规则:
检查规则:
- If、While、IfElse、For表达式必须使用{},无论有多少语句;
- 如果方法返回boolean,那么注意避免不必要的if…then…else语句;
- 避免if语句嵌套过深(降低代码可读性);
解决办法:建议if嵌套不要超过2层。使用工具方法封装更多的if语句或者把嵌套的if表达式放到同一个层次中; - 忽略大小写进行字符串比较时,使用String.equalsIgnoreCase,不要使用String.toLowerCase。前者有更好的性能,而且还可以避免后者带来的本地化问题;
- 避免方法级的同步,块级别的同步可以确保内含真正需要同步的代码;
- 使用集合类的isEmpty方法;
java.util.Collection类的isEmpty方法提供判断一个集合类是否包含元素。不要是使用size()和0比较来重复类库已经提供的方法。原则:尽量复用,充分利用已有的资源,不要重复造轮子。 - 没有使用的就去掉,保持代码的干净、整洁,包括:没有使用的私有成员、本地变量、私有方法、方法参数(参数定义,但方法内没有使用此参数)
- 构建StringBuffer或StringBuilder时,如果知道长度,请指定,这样性能更好。不指定,则默认长度是16,这样当长度不够时,就会有扩容的动作。
- 如果本地变量只被赋值一次,或者方法参数从来不会被重新赋值,可以把它声明为Final;
- 如果想由数组构建List,请使用Arrays.asList;
- 数组复制,请使用System.arraycopy,别用循环;
"" + 123
的方式把数字转换为String,不够高效;- 避免代码中出现各种"空"的语句:空Catch,If,while,try,finally,switch,Synchronized,static块;
- 重复的代码:拷贝/粘贴代码意味着拷贝/粘贴bugs;
- 循环体创建新对象:尽量不要再for或while循环体内实例化一个新对象;
- 资源关闭:Connect,Result,Statement等使用之后确保关闭掉;
- 还有很多。。。
用户还可以自定义规则,检查Java代码是否符合某些特定的编码规范。例如可以编写一个规则,找出所有创建Thread和Socket对象的操作。
基本使用
命令行方式:java -jar pmd-6.8.0.jar /your_project/src text rulesets/unusedcode.xml
,更详细的命令行参数使用-help查看即可得知。
idea集成pmd,搜索插件安装即可。
工作原理
PMD的核心是JavaCC解析器生成器。PMD结合运用JavaCC和EBNF(扩展巴科斯-诺尔范式,ExtendedBackus-Naur Formal)语法,再加上JJTree,把Java源代码解析成抽象语法树(AST,Abstract SyntaxTree)。不过,为了让解析器承认这些普通的文本是合法的Java代码,它们必须符合某种特定的结构要求。这种结构可以用一种称为EBNF的句法元语言表示,通常称为“语法”(Grammar)。JavaCC根据语法要求生成解析器,这个解析器就可以用于解析用Java编程语言编写的程序。
实际运行中的PMD还要经过JJTree的一次转换。JJTree是一个JavaCC的插件,通过AST扩充JavaCC生成的解析器。AST是一个Java符号流之上的语义层。有了JJTree,语法分析的结果不再是System, ., out, ., .println
之类的符号序列,而是一个由对象构成的树型层次结构。
FindBugs
官网
最后一次更新是06 March, 2015,不算太过时。
简介
FindBugs是一个java byte code静态分析工具,检测出Java程序中上百种潜在的不同类型的错误。不注重style及format,注重检测真正的bug及潜在的性能问题,尤其注意尽可能抑制误检测(false positives)的发生。除了作为单独的组件安装,还可以集成IDE,以及maven。其检测输出结果可以是XML或文本形式的。支持自定义配置检查规则(做哪些检查,不做哪些检查),校验规则(用户自定义特定的bug模式需要继承它的接口,编写自己的校验类)。
规则
FindBugs检查类或者 JAR 文件,将字节码与一组缺陷模式进行对比以发现可能的问题。根据最新的页面bugDescriptions,自带88种Bad practice,149种Correntness,3种Experimental,2种Internationalization,18种Malicious code vulnerability,46种Multithreaded correntness,29种Performance,11种Security,80种Dodgy code。
这个页面能看到很多缩写,缩写的意义,以及不规范的和建议的规范的代码的解释。多看几遍,对于规范自己的coding style非常有帮助。
IDEA安装FindBugs非常容易。安装完之后,可能需要设置提高检查的级别,因为可能检查规则过于严格。
checkstyle
另起章节,参考Google checkstyle实战。
SonarLint & SonarQube
官网:https://www.sonarqube.org/
slogan: Continuous Inspection
简介
必须要指出 SonarQube 是开源的代码质量管理平台,以 web 页面形式(即Sonar Dashboard)可视化形式检查代码质量;SonarLint 是 SonarQube 提供的插件,可以安装在各个 IDE 工具。
后来者居上,SonarQube是一个集大成者,允许配置安装插件,比如前文提到的 Findbugs, PMD 和 CheckStyle,基于这些插件的代码质量检查规则多达上千条,已经可以覆盖代码质量的绝大多数方面,更新版本的功能更多。
Sonar 可以从七个维度检测代码质量:
- 代码规则——sonar可以通过PMD,CheckStyle,Findbugs等等代码规则检测工具规范代码编写;
- 潜在的缺陷——通过PMD,CheckStyle,Findbugs 等代码规则检测工具检测出潜在的缺陷;
- 复杂度——无论对于类还是方法,都不宜过长,长意味着复杂,问题丛生……。
- 重复——sonar会检查重复片段,此时需要重构并提取共有逻辑;
- 注释不足或者过多——关键的代码片段不能没有注释,注释得简洁有意义,且需要及时更新;
- 单元测试——sonar可以很方便地统计并展示单元测试覆盖率;
- 架构与设计——通过sonar可以找出循环,展示包与包、类与类之间相互依赖关系,可以检测自定义的架构规则 通过sonar可以管理第三方的jar包,可以利用LCOM4检测单个任务规则的应用情况, 检测耦合。
特性:
- 通过插件扩展
支持新的编程语言、添加规则引擎、计算更复杂的度量指标,这些都可以借助强大的插件扩展机制。通过插件机制,Sonar 可以集成不同的测试工具,代码分析工具,以及持续集成工具,比如pmd-cpd、check-style、find-bugs、Jenkins。通过不同的插件对这些结果进行再加工处理,通过量化的方式度量代码质量的变化,从而可以方便地对不同规模和种类的工程进行代码质量管理。 - 支持多种编程语言
借助插件,支持各种主流编程语言; - 集成CI
同时 Sonar 还对大量的持续集成工具提供接口支持,可以很方便地在持续集成中使用 Sonar - 代码质量周边
提供插件,规则、告警、例外……都可以在线配置。通过自己的数据库,SonarQube不仅仅是展示各项指标的综合结果,同时也结合历史质量数据。 - 国际化
对国际化以及报告文档化也有良好的支持。
IDEA 安装插件 Sonar Lint
安装 Sonar Lint 插件需要重启,然后打开other setting——SolarLint——General Settings,输入sonarqube的地址,一般公司会搭建内网平台,在 Server URL 里面添加这个地址,按需输入用户名/密码,测试连接,连接成功即可使用。
sonar平台搭建
非常类似于Jenkins,sonar提供war包,下载解压,配置一下数据库(默认MySQL,本地或者远程都行),然后启动脚本(bat或sh)。
Alibaba coding guiding
提供 IDEA 插件 Alibaba java coding guideline,方便集成在IDEA,实时发现并修改不规范代码。有一份详细的 pdf 文档《码出高效:阿里巴巴 Java 开发手册》解释编码规范;比较好的是:针对插件检查出来的不规范,有详细的解释。最近还有一本书《码出高效:Java开发手册》,纸质书定价99元,抢钱啊;土豪可忽视。
链接:阿里巴巴编码规范(JAVA)认证考后感
JArchitect
JArchitect,一款静态分析工具,使用一种基于Linq(CQLinq)的代码查询语言,像查询数据库那样来查询代码。最新版v2019.1下载地址,可以把许多其他静态分析工具的输出结果包含进来,然后使用CQLinq做查询,提供CI/CD工具集成功能,提供报表功能。付费工具,功能很强大。
简单使用:
解压缩,双击VisualJArchitect.exe,新建工程,选择一个想要被分析的项目,然后点击开始分析即可。
从分析结果面板可见其功能强大。
CQLinq:
类似SQL语法,有智能提示,并且给出近乎实时的语法检查和查询结果;
Simian
官网
下载
冗余代码检查工具,非免费,支持语言: C#,C,C++,,COBOL,Ruby,JSP,ASP,HTML,XML,Visual Basic等。
支持Windows。
命令行模式:
java -jar simian.jar [options] [files]
simian-2.3.33.exe -includes="D:\code\**\*.java" -threshold=3 -formatter=xml:e:\result.xml "*.rb"
集成
- 和 IDE,如 VS 集成
- 和 Jenkins 集成
- 和 Ant 集成
- 和 Checkstyle 集成
SourceMonitor
官网
代码度量工具,免费,针对不同的语言(包括但不限于 C、C++、C#、Java、VB、Delphi和HTML),输出不同的代码度量值。SourceMonitor从几个不同的视图层次,如项目视图、检查点(checkpoint)视图和函数视图,展示如下度量值:
- 总行数(Lines):包括空行在内的代码行数;
- 语句数(Statements):在C语言中,语句是以分号结尾的。分支语句if,循环语句for、while,跳转语句goto都被计算在内,预处理语句#include、#define和#undef也被计算在内,对其他的预处理语句则不作计算,在#else和#endif、#elif和#endif之间的语句将被忽略;
- 分支语句比例(Percent Branch Statements):该值表示分支语句占语句数目的比例,分支语句 指的是使程序不顺序执行的语句,包括if、else、for、while和switch;
- 注释比例(Percent Lines with Comments):该值指示注释行(包括
/* */
和//
形式)占总行数的比例; - 函数数目(Functions):指示函数的数量;
- 平均每个函数包含的语句数目(Average Statements per Function):总的函数语句数目除以函数数目得到该值;
- 函数圈复杂度(Function Complexity):指示一个函数可执行路径的数目,以下语句为圈复杂度的值贡献1:if/else/for/while语句,三元运算符语句,if/for/while判断条件中的"&&“或”||",switch语句,后接break/goto/return/throw/continue语句的case语句,catch/except语句;
- 函数深度(Block Depth):函数深度指示函数中分支嵌套的层数
- 类个数(Classes):包括class,struct和template在内的个数
- 平均每个类方法数(Methods per Class):包括内联和非内联的,template函数在内的类方法数除以所有类的个数
代码审查(code-review)工具
上面的工具仅仅只是编码规范的自动化检查工具;但是事实上,业务开发,仅仅只有这些规范检查工具是远远不够的,毕竟代码是写给人看的,写给同事,写给离职后的维护者看的。所以,还需要在此基础之上添加一道代码审查工具,帮助人工审查代码,检查可以优化的编码块。
- Review Board
- jArchitect
- RhodeCode
- Codebrag
- Crucible
- Malevich
- Gerrit
Review Board
Review Board起源于VMware的一些开发者。它基于Django,你可以把它安装在自己的服务器上,工作流和Rietveld极为类似。提供在diffs里进行语法彩色编码,使得代码阅读变得简便。此外,它还实现基于Lucene的搜索来帮助管理较大的diffs组。提供 IDEA 插件、邮件支持,有 web 端,使用体验良好。
Review Board在审查补丁(Patch)方面表现完美。一个叫做“提交审查”的工具被用来和SCM系统进行连接(支持各大VCS 如 SVN,DVCS 工具如 Git 等),可以允许你请求一个将被提交的修改的审查。用户基础页面将这个过程描述如下:
- 你在本地检出的代码上做些修改。
- 你通过公布diff、编写描述和选择一些审查者来创建一个审查请求。
- 你在审查请求中点击“发布”并等待你的审查者看到它。
- 审查者或者其他人看到你的审查请求,提出改进意见。
- 根据他们的评论更新你的代码。
- 公布更新后的diff,以及对他们评论的解答以指明你修改什么(或者你要说明为什么你不打算按照他们的建议修改代码)。
- 将修改提交到仓库中。
- 在审查请求中点击“设置为已提交”来从其他人的面板中移除你的审查请求。
Gerrit
Gerrit 实际上一个Git服务器,它为在其服务器上托管的Git仓库提供一系列权限控制,以及一个用来做Code Review (核心功能)的Web前台页面。实际上此处讲解的 Gerrit 的 code review 以及 GitLab Changes 的 code review 功能,都是基于 git hook 原理实现的。留坑:有时间,好好研究一下 Git hook。
登录Gerrit后在Projects–>List,选择工程,进入该工程的General界面。
clone 就是常规的 GitHub 页面的 git clone,此处推荐使用后者,即 clone with commit-msg hook;后面是协议,一般选择 SSH:
git clone ssh://awesome.me@code.awesome.com:2018/Qyhj/Service/PaymentService && scp -p -P 2018 awesome.me@code.awesome.com:hooks/commit-msg PaymentService /.git/hooks/
拷贝以上命令在自己本地 Git 命令行窗口执行即可拉取库代码。
todo
GitLab Changes
Changes 是一种类似 gerrit changes 的code review方式。使用方法如下:
- 需要在git clone时选择 with commit-msg,如图:
会将commit-msg hook文件一起 clone 到本地; - 随后通过
git add
和git commit
命令修改 & 提交代码到本地,push时记住需要执行git push origin HEAD:refs/for/master
; - 上面的
git push
会给出此次提交的git diff,即change页面地址,打开页面一目了然地查看 diff 。 - review代码,同意可以通过点Accept按钮将代码合并到远端仓库,不同意可以点击close按钮关闭掉change;
- 如果review代码需要多个人review后才能合并,可以在project settings页面配置;
- 如果review不通过,即close 掉之后,需要继续修改本地文件,然后执行同样的
git push
命令产生新的change,并打开链接,需要 approver 执行 approve 后才可以进行Accept操作; - 合并后如果发现不对劲,可以通过revert功能进行回退。
其他还有一些细节,可以自己去摸索。
总结:Gitlab 的 changes功能是仿照gerrit的code review功能引入的,其中 related_changes、patch_sets 的概念和gerrit基本一致,rebase、revert等功能用法也类似。注意:如果change进行rebase操作,本地更新代码时需添加–rebase参数,具体为:git pull --rebase
参考来源:内网类 blog 系统;
实施
上面讲了不少工具,可是规范毕竟是规范,不实施起来,强硬提交(--force
或者--hard
)也是无奈。所以有必要设置一个关卡,不满足则不让提交或者失败;可以从两个地方加以控制实施。
VCS 服务端
在 version control system 服务端(如git,svn服务端)实施,如果不达标则拒绝提交(允许提交到其他分支以便保留工作成果,但是不能 merge 到 master 或者 release branch)。提交代码前自动执行检查,想到Git commit hook,比如 pre-commit hook
#!/bin/sh
# From gist at https://gist.github.com/chadmaughan/5889802
# stash any unstaged changes
git stash -q --keep-index
# run the tests with the gradle wrapper
./gradlew clean build
# store the last exit code in a variable
RESULT=$?
# unstash the unstashed changes
git stash pop -q
# return the './gradlew build' exit code
exit $RESULT
将该脚本拷贝到项目.git/hooks/
下,在执行git commit
时自动触发检查,检查失败则提交失败。.git
隐藏文件夹并不能提交到远程代码仓库,除非人工分发和拷贝外,有没有更好的方式在团队中共享这个机制呢?
可以把pre-commit
纳入版本控制(如config/pre-commit
),再使用构建工具的扩展机制来自动完成拷贝工作,这样可以间接实现git hooks
的团队间共享。缺点:没有使用 gradle,使用 maven 怎么做?
# build.gradle
task installGitHooks(type: Copy) {
// 将pre-commit拷贝到指定位置
from new File(rootProject.rootDir, 'config/pre-commit')
into {
new File(rootProject.rootDir, '.git/hooks')
}
fileMode 0755
}
// 设置执行build任务时会自动触发installGitHooks任务
build.dependsOn installGitHooks
CI/CD 服务端
利用 CI/CD 服务端,在 Jenkins 构建(Jenkins 集成 checkstyle 或者 sonarqube)或者随后的部署阶段检查代码质量,不满足则触发失败。或者如果使用 maven-checkstyle-plugin
,则可以随时触发检查。