代码质量保障,从零到强制检查

random-pic-api

推行代码检查,最难的不是技术

代码质量保障是个经典话题。Checkstyle 做格式检查,PMD 做逻辑缺陷检查,SonarQube 做全方位的代码质量度量——这三件套怎么用,网上文档很全。

但真正的挑战从来不是”怎么配置这些工具”,而是**怎么让一个已经运行了两年的、代码量不小的、团队成员流动频繁的项目接受”强制代码检查”**。

想象一下这个场景:你在 CI 管道里加上了 Checkstyle,规则用的是 Google Java Style。第二天,所有 MR 都挂了——不是代码逻辑有问题,而是缩进不对、命名不规范、缺少 Javadoc。开发 leader 来找你:”能不能先关掉,这个需求很急。”你说不能。他说那就先降级为 warn。

然后这个 warn 就永远是 warn 了。没人看 warn。

这不是假设,这就是大部分项目型组织中推行代码质量工具的真实路径。所以我花了大量时间思考的不是”选哪个工具”,而是”怎么让别人愿意用”。

工具分工:各干各的,互相补充

先简单过一下三件套的定位。

Checkstyle 管格式。缩进是 4 个空格还是 tab?大括号是同行还是换行?类名是大驼峰还是什么?这些东西和逻辑无关,和审美有关。但审美不统一,代码仓库看起来就永远像多个人写的——虽然确实是多个人写的,但读代码的人不应该感知到这一点。

规则直接基于 Google Java Style,再做项目级调整。比如行长度限制设 140 字符而不是默认的 100——别问为什么,问就是团队共识。

PMD 管逻辑。它基于 AST(抽象语法树)分析代码,找的是真正的 bug 模式——循环里用 += 拼接字符串、用 == 比较字符串、SimpleDateFormat 当静态变量用、ThreadLocal 不调 remove。这些不是格式问题,是线上事故的种子。

PMD 的规则比 Checkstyle 更容易引起争议。因为格式问题可以靠”格式化一下就行”解决,但 PMD 报的问题有时需要改逻辑——比如把 String result = ""; for (...) result += item; 改成 StringBuilder。开发者会说”这里循环体量很小,不会有性能问题”。你说得对,但规则不讲感情,规则讲一致性。

SonarQube 管全局。它不是检查单行代码,而是从项目维度看代码质量——可靠性(Bugs)、安全性(Vulnerabilities)、可维护性(Code Smells)、测试覆盖率、重复率、复杂度。它有一个核心概念叫 **”Clean as You Code”**——不强求一次性修复所有历史债务,但要求新增代码达到质量标准。

这个理念在老旧项目中特别实用。你面对一个几十万行代码的项目,让团队花三个月把 Checkstyle 的几千个 warning 全修完?不现实,也没人愿意干。但”从今天开始,新增代码和修改代码必须通过检查”——这个要求是合理的,也是可以执行的。

怎么让规则不惹人烦

规则配置是推行成败的关键。配得太严,团队反弹;配得太松,等于没配。

我定的规则集中放在一个独立的 checkstyle-rules 模块中,通过 Maven 依赖的方式提供给所有业务项目。这样改一次规则,所有项目自动生效:

1
2
3
4
5
6
7
8
9
10
11
12
13
common-parent/
├── checkstyle-rules/
│ └── src/main/resources/
│ ├── checkstyle/
│ │ ├── checkstyle.xml
│ │ └── checkstyle-suppressions.xml
│ └── pmd/
│ ├── pmd-ruleset.xml
│ ├── concurrent.xml # 线程池、锁、ThreadLocal 规则
│ ├── exception.xml # 异常处理规则
│ ├── naming.xml # 命名规则
│ ├── oop.xml # 面向对象规则
│ └── set.xml # 集合操作规则

PMD 的规则我按类型拆了十几个文件,但真正启用的是这六类:

concurrent.xml:手动创建线程、Executors 工具类创建线程池、静态 SimpleDateFormat、ThreadLocal 未 remove。这些是”写对了是运气,写错了是事故”的问题。

exception.xml:finally 块中使用 return、事务没有 rollbackFor。都是静默破坏流程的写法。

naming.xml:类名、方法名、变量名、包名、抽象类以 Abstract 开头、异常类以 Exception 结尾、测试类以 Test 结尾。如果连名字都统一不了,就别谈规范了。

oop.xml== 比较字符串、== 比较包装类型、循环中 String 拼接、BigDecimal 用 double 构造。

set.xml:foreach 中修改集合、Arrays.asList 后修改、集合初始化不指定容量。

other.xml:方法中编译正则表达式、使用 Apache BeanUtils、浮点数直接比较。

Checkstyle 的检查先于 PMD 执行。为什么?因为格式问题占了 80% 的违规,如果格式都没过,就别浪费 PMD 的时间了。

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
<!-- 父 POM 中的配置 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<configLocation>checkstyle/checkstyle.xml</configLocation>
<failOnViolation>true</failOnViolation>
<includeTestSourceDirectory>false</includeTestSourceDirectory>
</configuration>
<executions>
<execution>
<id>validate</id>
<phase>validate</phase>
<goals><goal>check</goal></goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.xxx</groupId>
<artifactId>checkstyle-rules</artifactId>
<version>${framework.version}</version>
</dependency>
</dependencies>
</plugin>

编译时不通过就 fail fast。不搞 warn,只搞 error。warn 没人看,error 才有人修。

IDE 集成

CI 检查是最后的防线,但最好的检查是在代码还没提交的时候就发现问题。

Checkstyle 在 IntelliJ IDEA 中有官方插件,安装后指向项目中的 checkstyle.xml,写代码的时候就能看到波浪线提示。PMD 也有插件,但我没强制推广——PMD 的问题量少,编译前统一处理即可。插件太多也会拖慢 IDE。

另一个关键配置是 IDEA 的代码风格文件。把团队统一的风格配置导出为 codestyle.xml,放在仓库里,新成员加入时导入一下就行。缩进、空格、换行这些格式问题,80% 可以通过 IDE 自动格式化解决。

但这有个问题:IDE 的配置文件会过时。你在仓库里放了一份 codestyle.xml,三个月后 IDE 升级了,或者团队调整了规则,文件没更新。更理想的方式是通过 IDEA 插件来管理代码风格——类似 checkstyle-rules 模块的思路,插件按需从远程拉取最新的规则。不过这是长期规划的事了。

给特殊情况留后路

规则不可能覆盖所有场景。有些代码生成器生成的代码,风格和手动写的完全不同。有些测试方法名确实需要很长。这些情况需要通过抑制机制来处理。

被动的抑制:在 checkstyle-suppressions.xml 中配置全局排除规则:

1
2
3
4
5
6
<suppressions>
<!-- 排除生成的代码 -->
<suppress files=".*[/\\]target[/\\].*" checks=".*"/>
<!-- 测试类不检查 Javadoc -->
<suppress files=".*Test\.java$" checks="JavadocMethod"/>
</suppressions>

主动的抑制:代码中注解标记:

1
2
3
4
5
@SuppressWarnings("checkstyle:LineLength")
private static final String LONG_CONSTANT = "...";

@SuppressWarnings("PMD.UnusedLocalVariable")
public void specialMethod() { ... }

关键是抑制必须有明确原因。code review 时看到 @SuppressWarnings 就要问:为什么要抑制?有没有更好的写法?抑制不是偷懒许可证。

紧急情况下的逃生舱

生产环境出问题,需要紧急修复,代码检查可能成为阻碍。这是合理的担忧。解决方式是在 Maven 命令中允许跳过:

1
2
# 紧急部署时跳过所有代码检查
mvn clean deploy -Dcheckstyle.skip=true -Dpmd.skip=true

但跳过是例外,不是常态。每次跳过都应该有记录。你可以在 CI 管道里加一个简单的审计——检测到跳过参数时,自动发一条消息到团队群:”注意:XX 服务本次部署跳过了代码检查。”

推行的节奏:先小后大

回到开头的话题——怎么让别人接受。

我大概花了三个月做渐进式推行。第一个月,只在框架本身的模块中启用 Checkstyle,不做 PMD。让团队先习惯”构建可能因为格式问题失败”这件事。

第二个月,在修复了框架自身的问题后,向一个业务模块试点。同时提供 IDEA 配置文件和一键格式化指南——“如果你想在提交之前就解决大部分问题,导入这个文件然后 Ctrl+Alt+L”。

第三个月,全面推广到所有模块,同时启用 PMD。PMD 的初始规则只保留了并发、异常处理、集合操作这三类——选最能防 bug 的,不选最全的。规则集在后续版本中逐步增加。

这个节奏的核心原则是:让团队先感受到工具带来的好处,再增加工具的约束力。如果一上来就扔三百条规则,团队只会觉得你在给他们添麻烦。但如果他们先发现”咦,PMD 帮我发了一个潜在的 NPE”,接受度就完全不同了。


写到这里,整个系列已经覆盖了架构全景、工程规范、Response 设计、缓存重构、配置管理、代码质量——这些都是研发底座的”软”层面。下一篇也是最后一篇,聊点”硬”的:微服务网关的性能调优,从 context-path 设计到 Nginx 内核参数,都是线上真实踩过的坑。