简介:面向Java开发者与团队规范落地场景,这套阿里巴巴代码规约资料整合了《阿里巴巴Java开发手册》华山版与详尽版两份PDF,并附带Eclipse、IDEA代码规约插件及格式化、代码模板相关配置,适合个人自查、团队代码评审和新项目规范初始化时参考。压缩包共5个文件,以2份PDF手册、2个XML配置文件和1个ZIP插件/格式化包为主,整体约20.14MB,体积轻便,便于随项目归档或分发给团队成员。已有489人学习下载,说明其在Java编码规范学习与工具落地方面具有一定参考价值。读者可据此对照命名、注释、异常、集合、并发等常见规约条目,结合XML模板统一IDE格式与检查规则,减少风格分歧和低级问题,提升代码可读性与评审效率。
1. 代码规约不是流程文档,是成本最低的一道拦截网
接手一个跑了三年的老项目,先翻的往往不是架构图,而是它的代码规约落地情况:命名乱不乱、常量有没有收口、并发工具是不是随手new Thread。代码规约听着像流程文档,实际是团队里成本最低的一道拦截网。这套在国内 Java 圈流传很广的开源规约,由三部分组成:一本按强制、推荐、参考三级标注的条款手册,一个 IDE 扫描插件,一套能挂进构建流程的规则集,覆盖命名、集合、并发、异常日志、数据库、工程结构。它不解决业务问题,解决的是「十个人写出十种风格」「Code Review 全靠老员工直觉」这类内耗。新手照着条款改习惯,熟手更该关心规则集怎么裁、阈值怎么设、存量代码怎么不被打死。
2. 拆开规约文档:三级条款怎么翻译成项目里的检查动作
2.1 强制、推荐、参考不是拍脑袋分的
拿到手册先别急着装插件,先搞懂分级逻辑,否则后面调规则时你会不知道哪条能砍。
强制条款的共同特征是:违反后大概率引发线上故障、安全漏洞或明显性能问题。比如在foreach里删集合元素、多个线程共享一个SimpleDateFormat实例、事务注解打在非 public 方法上。这类条款没有讨论空间,团队里有人觉得「我以前这么写也没事」,那是没赶上并发量上来。
推荐条款影响的是可维护性和协作成本,短期不炸,长期还债。方法行数、单个类的方法数量、注释完整度、命名长度都属这一类。它们适合在增量代码上慢慢收,不适合一上来就卡死构建。
参考条款跟技术栈和团队习惯强相关:是否允许用 Lombok、DTO 要不要继承基类、接口方法是否必须写 Javadoc。这类条款最大的坑是被无脑设成强制,结果团队为了绕过它写一堆@SuppressWarnings,规则形同虚设。
我一般的做法是把手册里所有「强制」条目过一遍,每条标三列:自动可查、只能人工查、本项目不适用。标完你会发现自己项目真正需要自动化的,通常只占全部条款的六成左右。
| 手册板块 | 典型强制条款 | 能否自动扫描 | 补充手段 |
|---|---|---|---|
| 命名风格 | 常量全大写下划线、抽象类加 Abstract 前缀 | 能 | 无 |
| 常量定义 | 不允许出现魔法值 | 部分(字面量检测) | Code Review |
| 集合处理 | Arrays.asList结果禁止 add/remove | 能 | 单测 |
| 并发处理 | 禁止用 Executors 创建线程池 | 能 | 线程池统一封装 |
| 异常日志 | 捕获异常后不得什么都不做 | 能 | 日志平台告警 |
| 单元测试 | 测试类以 Test 结尾、方法名语义化 | 部分 | 覆盖率门禁 |
| MySQL | 表名小写下划线、索引前缀 uk_/idx_ | 不能 | SQL 审核平台 |
| 安全 | 配置文件不得出现明文密码 | 部分 | 配置中心加密 |
这张表的意义在于:它告诉你哪些条款靠插件就能守住,哪些必须靠流程补。很多团队把数据库和安全规约也指望插件查,最后发现一条都扫不出来,就误判成「这套规约没用」。
2.2 把条款翻译成检查项:一条链路走通
规约文档是给人读的,插件规则是给机器跑的。中间那层翻译没做好,就会出现「条款都懂,代码照旧」的局面。
我习惯给每条要落地的条款补四个字段:条款原文、触发条件、修复动作、责任人。以集合那条为例,触发条件是「对Arrays.asList的返回值调用add/remove/clear」,修复动作是「用new ArrayList<>(...)包一层」,责任人是提交者本人,评审时不再讨论。这四个字段写清楚,规则报错时新人不用去翻手册原文也能改。
再往下才是插件能识别的形式。插件规则名通常和条款一一对应,比如命名类、集合类、并发类各有独立的规则集文件,构建期按文件名挂载。这意味着你可以按板块开关:后端项目挂命名、集合、并发、异常日志四个规则集,纯 SDK 项目把数据库和工程结构相关的规则集直接去掉。
有一点要注意:条款编号和规则名不是一对一。一条条款可能对应多条规则,也可能一条规则覆盖几条条款。做映射表时以「规则名」为主键,条款编号放备注,后面做豁免和债务统计时才不会乱。
2.3 命名、常量、集合、并发四类条款的读法
这四类占了日常报错量的绝大多数,值得单独说。
命名条款看着最无聊,实际最容易吵起来。手册要求类名大驼峰、方法和参数小驼峰、常量全大写下划线、抽象类以 Abstract 或 Base 开头、异常类以 Exception 结尾、测试类以 Test 结尾。真正容易被忽略的是 POJO 的布尔字段:不要让字段名以is开头,因为部分序列化框架会把isSuccess反序列化成success,前后端联调时字段对不上,排查半天。这条在评审里几乎没人提,但插件能查出来。
常量条款的核心是「不允许魔法值直接出现在代码里」。这里的魔法值不是所有字面量,而是有业务含义的字面量:状态码、阈值、超时时间。判断标准很朴素:这个值改一次,需要搜索几个文件。另外long或Long赋值必须用大写L,小写l和数字 1 在多数字体下几乎一样,线上改错一位数的事故不稀奇。
集合条款是重灾区,下面这段是典型翻车现场:
// 反例:Arrays.asList 返回的是固定长度视图,add 直接抛 UnsupportedOperationException List<String> list = Arrays.asList("alpha", "beta"); list.add("gamma"); // 运行时炸 // 正例:需要可变列表时显式包一层,容量按实际元素数给 List<String> mutable = new ArrayList<>(Arrays.asList("alpha", "beta")); mutable.add("gamma");Arrays.asList的入参是基本类型数组时还会出第二个坑:Arrays.asList(new int[]{1,2,3})得到的是List<int[]>,长度是 1 而不是 3,因为泛型不接受基本类型,整个数组被当成一个对象。同类问题还有subList返回的是原列表的视图,原列表结构一变,视图上的操作就抛ConcurrentModificationException。
并发条款里最值钱的一条是禁止用Executors工厂方法创建线程池:
// 反例:newFixedThreadPool 内部用的是无界队列,任务堆积时直接把堆吃满 ExecutorService bad = Executors.newFixedThreadPool(8); // 正例:核心数、最大数、空闲存活、有界队列、拒绝策略全部显式声明 ThreadPoolExecutor pool = new ThreadPoolExecutor( 8, // corePoolSize:常驻线程数,按 CPU 核数或 IO 密度定 16, // maximumPoolSize:峰值上限,别超过下游连接池承载 60L, TimeUnit.SECONDS, // keepAliveTime:超出核心数的线程空闲多久回收 new ArrayBlockingQueue<>(200), // 有界队列,容量即缓冲区上限 r -> { // 线程工厂:给线程起名,出问题时能从线程栈定位业务 Thread t = new Thread(r, "order-async-" + r.hashCode()); t.setDaemon(false); return t; }, new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略:满了由调用线程执行,形成背压参数里最容易设错的是maximumPoolSize和队列容量。队列给太大,等于把 OOM 风险往后推;给太小,稍微一波流量就打满触发拒绝。CallerRunsPolicy的好处是让上游慢下来,坏处是会阻塞调用线程,放在 Web 请求链路里要确认上游能接受这个延迟。这些判断插件不会替你做,它只负责把Executors.newXxx标红。
3. 代码规约插件跑起来:IDE、Maven、命令行三种姿势
3.1 IDE 插件:先让个人看得见
插件的作用是把问题前置到编码阶段,别等提交才发现。安装路径很常规:设置里打开插件市场,按「代码规约」「coding guidelines」这类关键词搜,找到对应的扫描插件装上重启。
装完后的使用方式有两种。第一种是右键项目或单个文件,选规约扫描,结果会按 Blocker、Critical、Major 三级列出,双击条目跳到具体行。第二种是开启实时检测,边写边标,适合新人期。第二种对老项目不友好,存量代码一打开满屏红线,人会本能地关掉它。
我建议的顺序是:先只对src/main/java下本次要改的包开实时检测,其余目录不动。插件一般支持按目录或按规则级别过滤,把 Major 关掉只留 Critical 和 Blocker,噪音会少一大截。
团队层面要做的是把插件配置导出来放进仓库。多数插件支持导出规则开关配置,把它和.editorconfig、代码格式化配置放一起,新人拉下代码导入一次,规则就和老成员一致,省掉「我这没报错啊」的扯皮。
3.2 构建期接入:用 Maven 把规则集挂上去
IDE 插件只能管住愿意装插件的人,构建期扫描才能管住所有人。常见的做法是把规约规则集作为 PMD 的 ruleset 挂进maven-pmd-plugin。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-pmd-plugin</artifactId> <version>${pmd-plugin.version}</version> <configuration> <!-- 规则集文件来自规约插件发布的 jar,按板块拆分,按需挂载 --> <rulesets> <ruleset>rulesets/java/ali-naming.xml</ruleset> <ruleset>rulesets/java/ali-constant.xml</ruleset> <ruleset>rulesets/java/ali-set.xml</ruleset> <ruleset>rulesets/java/ali-concurrent.xml</ruleset> <ruleset>rulesets/java/ali-exception.xml</ruleset> </rulesets> <sourceEncoding>UTF-8</sourceEncoding> <!-- 按项目实际 JDK 版本设置,版本不匹配会导致解析失败 --> <targetJdk>17</targetJdk> <!-- 违规时打印明细,CI 日志里能直接看到类名和行号 --> <printFailingErrors>true</printFailingErrors> <!-- 是否让构建失败,增量阶段建议先关,接入稳定后再打开 --> <failOnViolation>true</failOnViolation> <!-- 允许的违规总数,设为 0 表示零容忍,存量项目先给个缓冲值 --> <maxAllowedViolations>0</maxAllowedViolations> <excludes> <!-- 生成的代码不参与扫描,否则永远清不完 --> <exclude>**/target/generated-sources/**</exclude> <exclude>**/proto/**</exclude> </excludes> </configuration> <executions> <execution> <phase>verify</phase> <goals> <goal>check</goal> </goals> </execution> </executions> </plugin>几个参数值得单独解释。rulesets里挂的 xml 文件名对应规约的各个板块,命名、常量、集合、并发、异常日志、OOP、流程控制、ORM、注释、其他各有一个文件,可以按项目类型挑。纯服务端项目通常要挂 ORM,纯工具库挂上只会报一堆「未使用 MyBatis」的误报。
maxAllowedViolations是接入初期的救命参数。老项目直接设 0,第一次构建就是一屏红,谁都不会去修。合理做法是先跑一次拿到当前违规总数,把阈值设成这个数,之后每清理一批就把阈值往下调,形成只减不增的棘轮。
targetJdk必须和项目实际编译版本一致。设低了会漏报新语法相关规则,设高了 PMD 解析不了源码,直接报解析错误,看起来像插件坏了。
坐标里的groupId和版本号请以插件官方发布为准,不同时期的构件坐标有过调整,抄之前先去仓库确认一次,比在群里问半天快。
3.3 命令行批量扫描:给多模块项目用
Maven 插件在单模块项目里够用,多模块项目一次构建十几分钟,谁都不愿意为了扫描跑全量。这时用 PMD 命令行针对指定目录扫更快。
#!/usr/bin/env bash set -euo pipefail # 规则集 jar 解压后的目录,或直接从依赖里取出的 rulesets 路径 RULESET_DIR="./tools/pmd-rulesets" # 输出目录,CI 里作为产物上传 OUT_DIR="./build/pmd-report" mkdir -p "$OUT_DIR" # PMD 7 用 check 子命令;PMD 6 是 pmd -d ... 形式,按本机版本调整 pmd check \ --dir ./src/main/java \ --rulesets "${RULESET_DIR}/ali-naming.xml,${RULESET_DIR}/ali-set.xml,${RULESET_DIR}/ali-concurrent.xml" \ --format html \ --report-file "${OUT_DIR}/pmd.html" \ --minimum-priority 2 \ --no-cache \ --threads 4 # 再出一份纯文本,方便 grep 出 Blocker 级别的条目 pmd check \ --dir ./src/main/java \ --rulesets "${RULESET_DIR}/ali-naming.xml,${RULESET_DIR}/ali-set.xml" \ --format text \ --report-file "${OUT_DIR}/pmd.txt" \ --minimum-priority 1--minimum-priority用来按严重级别过滤,数值越小越严重,1 对应最严重的一档。日常开发跑 2,提交前跑 1,能兼顾速度和覆盖面。--threads在核多的机器上提升明显,但别开得比核数还大。--no-cache在 CI 里建议开,本地反复扫同一份代码时可以关掉换速度。
HTML 报告适合贴到评审里给人看,纯文本适合脚本 grep 后做门禁判断。两份都出,成本很低。
3.4 参数怎么调:三个容易设错的开关
第一是级别阈值。规约的 Blocker 对应的是「不改可能出事故」,Critical 是「大概率有问题」,Major 是「风格与可维护性」。门禁只卡前两级,Major 放进报告不阻断构建,这个组合在多数团队能跑通。
第二是排除路径。除了target/generated-sources,还要排掉测试资源目录、前端构建产物落进 Java 目录的那部分、以及历史遗留的legacy包。排除写少了报告没法看,写多了等于自欺欺人,建议每加一条排除都在提交信息里写清理由。
第三是failOnViolation的开启时机。接入第一周设为 false,只看报告不改流程;第二周开始卡新增代码;等存量清到阈值以下再全量卡。跳步直接全量卡,最常见的结果是有人在 CI 配置里加了个-Dpmd.skip=true,然后永远没人再去删。
4. 避坑记录:五条血泪排查
4.1 插件装上了却一条都扫不出来
现象:IDEA 里插件显示已启用,右键扫描转两秒,结果面板空的,或者弹一句规则集加载失败。
原因多半是插件版本和 IDE 大版本不匹配,或者 JDK 版本超出插件支持范围。较新的 IDE 对旧插件会做兼容层,但规则集解析走的是另一套逻辑,加载失败时提示往往只给一行「ruleset not found」。
解决办法是先看 IDE 日志里的完整堆栈,确认是插件类加载失败还是规则文件缺失;然后去插件市场换成与当前 IDE 版本对应的发布版本。公司内网环境还要确认插件市场能访问,离线安装的包版本号必须严格对应,差一个小版本都可能加载不出来。
4.2 第一次全量扫描四千条,团队当天集体关插件
现象:接入当天报告几千条违规,评审会上没人愿意看,第二天插件被大面积禁用。
原因是把存量代码和新增代码放在同一个标准下衡量。跑了三五年的项目,命名不规范、魔法值遍地是常态,这不是一天能还完的债。
解决办法是分两条线:新增代码零容忍,存量代码设基线。具体做法是先跑一次全量拿到总数 N,把maxAllowedViolations设成 N,之后每次合并请求只对改动文件做扫描,全量阈值只降不升。基线数字放在 CI 变量里,谁把阈值调高,评审直接打回。
4.3 ORM 规则在非数据库项目里疯狂误报
现象:一个纯工具库项目挂上全部规则集后,报出大量「未使用 MyBatis」「表名不符合规范」的条目,实际项目里连数据库依赖都没有。
原因是规则集按板块拆分,但很多人图省事把十个 xml 全挂上。ORM 规则会扫描注解和字符串常量,工具库里出现@Table字样的注释都能被误判。
解决办法是按项目类型裁剪规则集:纯工具库只挂命名、常量、集合、并发、异常日志;服务端项目再加 ORM 和工程结构;SDK 类项目连注释规则都可以放宽。规则集挂载清单写进父 POM 的 profile,子模块按需激活。
4.4 构建红了,但违规全在测试代码和生成代码里
现象:CI 卡在扫描阶段,点进去看违规行号,全在src/test或者 protobuf 生成的*OuterClass.java里。
原因是excludes没配全。生成代码的命名和常量风格由代码生成器决定,你改不了也不该改;测试代码里的魔法值和长方法在可读性上反而更自然。
解决办法是把**/target/generated-sources/**、**/generated/**、**/*OuterClass.java加进排除列表。测试代码单独处理:要么整体排除,要么只保留命名和异常两类规则,把魔法值和行数相关的关掉。排除规则本身也要评审,防止有人把业务包塞进去图省事。
4.5 扫描全绿,线上还是 NPE
现象:规约扫描零违规,发布后照样空指针、照样线程池打满。
原因是这套规约查的是语法层和常见 API 误用,它不知道你的user对象在某个分支里可能是 null,也不知道你的线程池下游连接池只有 20 个连接。把扫描通过当成质量达标,是最典型的误判。
解决办法是把规约扫描定位成第一道筛子,后面还要接单元测试覆盖率门禁、空指针静态分析、以及关键链路的压测。规约能拦住的是「写法层面确定会错」的问题,逻辑层面的问题得靠测试和监控。这一点在给团队做培训时必须讲清楚,否则大家会把工具当护身符。
5. 接进 CI:让规约从「扫一次」变成「卡得住」
5.1 增量扫描:只查本次改动
全量扫描在 CI 里跑一次要几分钟到十几分钟,合并请求频繁时没人受得了。增量扫描的思路是先拿到本次改动的 Java 文件列表,再只对这些文件跑规则。
#!/usr/bin/env bash set -euo pipefail # 合并请求场景取目标分支基线,本地提交场景退化为 HEAD~1 BASE="${CI_MERGE_REQUEST_DIFF_BASE_SHA:-HEAD~1}" # 只取新增/修改/重命名的 java 文件,删除的不参与扫描 git diff --name-only --diff-filter=ACMR "${BASE}" HEAD -- '*.java' > /tmp/changed.txt if [ ! -s /tmp/changed.txt ]; then echo "no java file changed, skip" exit 0 fi # 传给 PMD 的文件列表用逗号拼接,路径里有空格时要改用 --file-list FILES=$(paste -sd, /tmp/changed.txt) pmd check \ --dir "${FILES}" \ --rulesets "./tools/pmd-rulesets/ali-naming.xml,./tools/pmd-rulesets/ali-set.xml,./tools/pmd-rulesets/ali-concurrent.xml,./tools/pmd-rulesets/ali-exception.xml" \ --format text \ --minimum-priority 1 \ --report-file /tmp/pmd-incremental.txt # 有任何一条 Blocker 就退出非零,阻断合并 if grep -q "priority 1" /tmp/pmd-incremental.txt; then cat /tmp/pmd-incremental.txt exit 1 fi--diff-filter=ACMR里的四个字母分别表示新增、复制、修改、重命名,删掉的文件不需要扫描。基线变量在不同 CI 平台名字不一样,用${VAR:-默认值}的写法能兼容本地跑。
路径里有空格或者文件名带中文时,逗号拼接会失效,这时改成--file-list /tmp/changed.txt传文件。这个坑在包含前端资源的项目里踩过一次,排查了半小时才发现是文件名里有个空格。
5.2 阈值与门禁:什么级别能红
门禁设计要回答一个问题:什么样的违规值得让整个合并停住。我的经验是只卡两档。
Blocker 全部阻断,没有例外。这类问题包含并发集合误用、线程池无界队列、SimpleDateFormat共享、事务注解失效位置,任何一条漏到线上都可能是一次故障。
Critical 按新增数量卡。允许单次合并带进来少量,比如不超过三条,超过就阻断。这样既不会因为一条风格问题卡住紧急修复,也能防止有人一次性塞进一堆。
Major 只进报告不做门禁。报告在合并请求里以评论形式贴出来,人看不看随缘,但只要它一直存在,慢慢就会有人顺手改。
阈值写在 CI 变量里而不是写死在脚本里,方便临时调整并留下变更记录。有人临时放宽阈值去救火,事后忘了调回来,这在变量历史里一眼能看出来。
5.3 存量豁免与债务清单
豁免机制必须有,但要有成本。做法是允许在代码上标注忽略,但必须写清规则名和原因:
// 该字段命名遵循外部接口协议,重命名会导致反序列化失败 @SuppressWarnings("PMD.FieldNamingConventions") private String user_ID;标注格式统一,评审时只要看到@SuppressWarnings就重点看一眼原因。原因写「临时」或者「历史遗留」的,一律要求补充具体说明,否则不予通过。
债务清单则是另一个方向:把全量扫描结果导出成 CSV,按包路径聚合,算出每个包的违规密度,排个序。密度最高的那几个包,通常就是没人愿意碰的老模块。把这份清单放进季度技术债看板,每次迭代认领一两个包清理,比喊口号有用得多。
清单不要按月更新,频率太高没人看。一个季度一次,配合阈值下调一起做,效果比较实在。
6. 进阶:规则集裁剪与自定义规则
规则集不是挂得越多越好。跑顺之后你会发现有两类规则需要单独处理:一类是团队已经形成肌肉记忆、从来没报过错的,另一类是误报率高于 30% 的。前者可以摘掉换取扫描速度,后者必须改或者关。
改的方式是自定义规则。规约插件没覆盖到的团队约定,可以用 PMD 的 XPath 规则补,写一个 xml 文件挂进rulesets就能生效:
<?xml version="1.0"?> <ruleset name="team-custom" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"> <description>团队补充规则</description> <!-- 禁止无参 new Date(),统一走时间工具类,避免时区与可变对象问题 --> <rule name="NoDefaultDateConstruction" language="java" message="禁止直接 new Date(),请使用项目时间工具类" class="net.sourceforge.pmd.lang.rule.XPathRule"> <priority>2</priority> <properties> <property name="xpath"> <value><![CDATA[ //AllocationExpression /ClassOrInterfaceType[@Image = 'Date'] [not(Arguments)] ]]></value> </property> </properties> </rule> </ruleset>priority决定这条规则落在哪一档,2 对应 Critical,能进门禁。XPath 里[not(Arguments)]限定只匹配无参构造,带参数的new Date(timestamp)不拦,因为那种用法本身没问题。写完先在本地对一两个包跑一遍,确认误报可控再挂进 CI,直接上线的结果往往是第二天就被人注释掉。
还有一个更省事的技巧:只对改动行做门禁。多数静态分析工具支持输出行号,脚本里把改动行区间和违规行号做交集,只对交集内的违规阻断。这样存量文件的存量问题不会反复冒出来,增量问题一条跑不掉。
从那以后我每次新建工程,第一件事就是把规则集清单、排除路径、阈值这三样写进脚手架模板,再让 IDE 插件默认开启,避免每个项目都要重新讨论一遍「要不要卡 Major」。希望帮到你。
本文还有配套的精品资源,点击获取