简介:面向Java开发者的阿里巴巴代码规约配套资源包,聚焦编码规范落地与工程实践,适合个人开发者、团队及技术管理者统一代码风格,降低评审成本。资源共5个文件,以PDF文档与XML配置为主,包含两版《阿里巴巴Java开发手册》——华山版与详尽版,以及Eclipse格式化、代码模板等规约插件配置,另附ZIP插件包,整体约20.14MB。手册系统梳理编程规约、命名风格、集合处理、异常日志与单元测试等要点;插件配置则可在IDE中实现自动检查、格式化与代码模板应用,将规约直接嵌入开发流程。相比单纯阅读文档,这套组合还能减少人工逐条对照规范的麻烦,帮助团队在编码阶段即时发现不合规写法,尤其适合新项目启动或旧项目规范治理。目前已有488人学习下载,对需要快速接受阿里规约的中高级开发者而言,是一份紧凑且可直接上手的参考资源。
1. 代码规约不是文档:是团队协作的边界
先说个反直觉的结论:代码规约文档下载以后,十有八九会在收藏夹里吃灰。真正让规约起作用的,不是人读文档,而是让规则跑进 IDE、跑进 CI,在代码提交前就把问题拦住。这份阿里巴巴代码规约资源,包含规约文档、IDE 扫描插件和规则配置参考,解决的是命名混乱、集合误用、并发隐患、可读性差这类每天都会踩的坑。适合正在带 Java 团队的开发者、接了存量项目想定规矩的人,也适合刚入行想写好代码的新人。全文按「规则重点 → 插件实操 → 整改实录 → 踩坑记录 → CI 落地」推进,跟着走一遍就能把规约用起来。
2. 核心规则划重点:命名、集合与并发三类高频违规
2.1 命名与常量:代码即文档的第一道门槛
规约里命名相关的内容占了不少篇幅,但它并不是在教你怎么起名字,而是在消灭「看代码猜意思」的成本。命名违规在扫描报告里永远是数量最大的类别,不是因为大家故意乱写,而是因为命名规则最容易执行、也最容易被人忽略。
规约里几条硬性要求需要注意:类名使用 UpperCamelCase,但有个特例——DO / BO / DTO / VO / AO 这类领域后缀,以及继承自既有框架的类名可以例外;方法名、参数名、成员变量、局部变量统一使用 lowerCamelCase;常量命名全部大写,单词间用下划线隔开。这些看起来简单,真正容易翻车的是 POJO 里的布尔变量:命名不允许带 is 前缀,因为部分框架在做属性解析时,会把isDeleted直接解析成deleted,导致序列化和反序列化的字段对不上。
常量相关的规则里,最常见的是「魔法值」问题。规约明确禁止在代码里出现魔法值,也就是未经定义的裸数字和裸字符串。
// 不推荐:看不懂 60 是什么含义 if (loginCount > 60) { lockAccount(); } // 推荐:语义自解释 private static final int MAX_LOGIN_FAIL_COUNT = 60; if (loginCount > MAX_LOGIN_FAIL_COUNT) { lockAccount(); }这段代码的逻辑差异一眼就能看出来。常量定义的核心不是「把数字提出来」,而是让后面的维护者不需要去业务文档里反查数字的含义。规约还顺带要求:如果多个地方用到同一个常量,必须定义在一个共享的常量类里,禁止在多个类里各自定义相同值的常量,否则改需求时很容易漏改。
2.2 集合与泛型:最常见的隐性 Bug 来源
集合是 Java 开发中使用频率最高的数据结构,也是规约里踩坑密度最高的章节。规约对集合的处理强调两个核心点:指定初始化容量、使用泛型。
先看泛型。不使用泛型的集合,等于把类型检查从编译期推迟到了运行期,而运行期的 ClassCastException 是所有异常里最难定位的一类。规约里对泛型的要求渗透在各处:集合声明必须带泛型,List不能裸用;Map的 key 用 String 时要考虑业务场景,如果用枚举做 key,必须使用 EnumMap,性能比 HashMap 好;数组转集合时要用Arrays.asList()还是Collections.emptyList(),取决于后续是否要增删元素。
Arrays.asList()是集合章节里被问得最多的一个点。它返回的是一个内部 ArrayList,不是 java.util.ArrayList,不支持 add 和 remove 操作。很多人在写测试代码时在这里翻车:
// 反例:运行期抛 UnsupportedOperationException List<String> list = Arrays.asList("a", "b", "c"); list.add("d"); // 正确:需要增删时用 new ArrayList 包一层 List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c")); list.add("d");规约对集合转数组还有一个具体要求:使用集合转数组时,必须使用带空数组的toArray(new T[0]),而不是toArray(new T[size])。new T[0]只是一个类型标记,运行时底层会重新分配数组;而new T[size]如果传入的空间刚好够用,会直接使用传入数组,反射创建空数组本身开销更小,写法也更干净。
2.3 并发与异常:扫描器真正值钱的地方
命名和集合的规则靠熟读文档也能记住,但并发和异常的处理没有实际踩过坑,很难理解规约为什么要那样要求。这一部分也是扫插件扫描报告里 Blocker 级别问题最集中的区域。
规约对并发最经典的一条要求是:SimpleDateFormat是线程不安全的,禁止定义为 static 变量使用。这条规则背后是真实的线上事故:在高并发场景下,多个线程同时调用一个 static SimpleDateFormat 的 format 或 parse 方法,因为 Calendar 内部状态被共享,会出现日期解析错乱,甚至直接抛 NumberFormatException。
// 反例:static SimpleDateFormat 线程不安全 private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); // 推荐:用 DateTimeFormatter(线程安全) private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");规约对异常处理同样有硬性要求:异常不要用 e.printStackTrace() 打印,因为 printStackTrace 会把堆栈输出到标准错误流,在分布式系统里这些日志会散落在各个节点上,根本无法统一收集。规约要求使用日志框架记录异常全貌,并且禁止在生产环境打印「系统错误」这类不带上下文信息的日志。
还有一条容易忽略的规则:catch 了异常之后不能只是简单地记录日志然后吞掉。如果 catch 块里没有任何处理逻辑,仅仅打一行日志,扫描器会标记为「异常被吞掉」。正确的做法是记录日志后抛出包装异常,或者返回业务上可理解的结果,而不是让调用方完全感知不到失败。
3. 插件安装与扫描实操:从 IDE 到命令行拿到第一份报告
3.1 离线安装:先解决内网环境
资源包里一般会带上 IDE 插件的离线安装包,通常是 zip 格式,这一点对内网开发环境特别友好。很多公司内部网络访问插件市场超时,如果资源本身带离线包,就能直接绕开网络问题。
常见做法是在 IDEA 或 Eclipse 里通过本地安装完成:打开 IDE 的设置界面,找到插件管理入口,选择从本地磁盘安装。以 IDEA 为例,路径通常是 Settings → Plugins → 齿轮图标 → Install Plugin from Disk,选择 zip 包后重启 IDE。Eclipse 则通过 Help → Install New Software → Add → Archive 来处理。
安装完成后需要确认插件是否被正确识别。IDEA 底部工具窗口会出现规约相关的面板入口,右键点击文件或目录时菜单里会出现规约扫描选项。如果重启后发现菜单里没有扫描入口,优先检查 IDE 版本和插件版本是否匹配,这是一个高频踩坑点。
安装本身不涉及代码,但有一个细节容易被忽略:插件安装后默认不会自动扫描历史代码,只会在打开文件时做实时提示。想要覆盖整个项目,必须手动发起一次完整扫描。
3.2 一键扫描与规则级别:分清 Blocker 和 Major
完整扫描的入口很直观:在项目目录或指定包上右键,选择规约扫描即可。扫描结果会按问题级别分组展示,规约插件把问题分为 Blocker、Critical、Major 和 Minor 四个级别,这个分级对后续整改优先级很重要。
| 级别 | 含义 | 典型场景 | 整改建议 |
|---|---|---|---|
| Blocker | 阻断性缺陷 | SimpleDateFormat 静态变量、集合转数组方式错误 | 必须立即修复 |
| Critical | 严重问题 | 魔法值、异常被吞掉、== 比较包装类型 | 需求迭代时优先修复 |
| Major | 主要问题 | 命名不规范、注释缺失、方法过长 | 排期修复或记录技术债 |
| Minor | 次要问题 | 部分未使用的变量、格式不统一 | 顺手修复即可 |
第一次扫存量项目时,报告数量会很吓人,这是正常的。不要指望一次性清零,合理的策略是先把 Blocker 和 Critical 清零,Major 级别的问题按模块逐个消化。扫描报告支持按级别过滤,也可以按文件分组,建议按包维度分批整改,每改一个包就重新扫一次,看到数字下降是保持动力的好办法。
3.3 用命令行把规约检查跑进 CI
插件只能在 IDE 里用,如果想在提交代码时强制检查,就需要命令行扫描。规约插件配套了对应的 PMD 实现,可以以 Maven 插件方式直接接入项目构建流程。
# 在项目根目录执行,生成规约扫描报告 mvn clean compile p3c-pmd:pmd -DskipTests # 报告输出在 target/pmd/ 目录下,可以用 CI 工具收集这里有个参数需要注意:p3c-pmd 插件执行 pmd 目标时,默认只扫描 main 代码,不会扫描 test 目录。如果想覆盖测试代码,需要额外调整 include 配置把src/test/java加进去。配置方式是在 pom.xml 里声明插件时增加 rulesets 和 includes 两个参数。
<plugin> <groupId>com.alibaba.p3c</groupId> <artifactId>p3c-pmd</artifactId> <version>2.1.1</version> <executions> <execution> <phase>verify</phase> <goals> <goal>pmd</goal> </goals> </execution> </executions> <configuration> <rulesets> <ruleset>rulesets/java/ali-comment.xml</ruleset> </rulesets> <includes> <include>src/main/java/**/*.java</include> </includes> </configuration> </plugin>配置里绑定了 verify 阶段,意味着执行mvn verify时规约检查会跟随构建一起运行。rulesets 参数决定启用哪几组规则文件,下面是规约插件的规则配置:
<rulesets> <ruleset>rulesets/java/ali-comment.xml</ruleset> <ruleset>rulesets/java/ali-concurrent.xml</ruleset> <ruleset>rulesets/java/ali-constant.xml</ruleset> <ruleset>rulesets/java/ali-exception.xml</ruleset> <ruleset>rulesets/java/ali-flowcontrol.xml</ruleset> <ruleset>rulesets/java/ali-naming.xml</ruleset> <ruleset>rulesets/java/ali-oop.xml</ruleset> <ruleset>rulesets/java/ali-other.xml</ruleset> <ruleset>rulesets/java/ali-set.xml</ruleset> </rulesets>各 ruleset 文件对应规约正文的不同章节。ali-set.xml 管集合,ali-concurrent.xml 管并发,ali-naming.xml 管命名。如果只想先启用其中的几组,直接在配置里删掉多余的 ruleset 行即可。注意 ruleset 设备在创建时默认包含全部规则,但不建议为了尽早通过检查就把所有规则都关掉,用规则逃过检查、在问题堆积到失控时反而要花更多时间清理。
4. 整改实录:三组典型违规的 Before 与 After
4.1 命名与常量整改
以一个真实业务模拟项目中的用户登录模块为例。原代码里方法名、变量名都存在明显的命名问题,扫描报告里 Major 级别的问题一抓一大把。
// Before:命名不规范 + 魔法值 public boolean check(String user_name, String userPassword, int count) { if (count > 3) { // 锁定逻辑 } return true; }这段代码有三个明显的规约违规点:参数user_name用了下划线命名,属于非 lowerCamelCase;变量count语义不明确,看不出统计的是登录失败次数;3是魔法值,含义不明。按照规约逐一修正:
// After:命名语义自解释 + 魔法值提取 private static final int MAX_LOGIN_FAIL_COUNT = 3; public boolean checkUserLogin(String userName, String userPassword, int loginFailCount) { if (loginFailCount > MAX_LOGIN_FAIL_COUNT) { // 锁定逻辑 } return true; }改完后代码的语义清晰很多。checkUserLogin直接点明方法职责,loginFailCount明确统计的是失败次数,3被提取为常量后,后续改锁定策略只需要调整常量值,不需要在业务代码里搜数字。这一类整改不需要任何额外的业务了解,纯机械操作,适合作为存量项目整改的起点。
4.2 集合转数组与泛型整改
集合相关的问题在存量代码里同样常见。许多老代码使用裸集合,数组与集合互转时也写得比较随意。
// Before:裸集合 + 数组转换不当 List list = new ArrayList(); list.add("user1"); list.add("user2"); String[] arr = new String[list.size()]; list.toArray(arr);这段代码里List没有指定泛型,后续从list中取元素时强制转 String 容易在运行期抛异常。数组转换虽然这里没有直接报错,但规约推荐使用new String[0]方式,因为new String[list.size()]需要提前计算长度,且如果 list 在并发环境下被修改,这里传的 size 和实际大小不一致,会有隐患。
// After:泛型 + 规约推荐的 toArray 写法 List<String> list = new ArrayList<>(); list.add("user1"); list.add("user2"); String[] arr = list.toArray(new String[0]);规约对集合转数组的推荐写法是toArray(new T[0]),原理是:传入的空数组只担任类型标记,底层会重新分配一个恰好大小的数组;而new T[size]如果传入的数组长度刚好等于集合大小,底层就会直接复用传入的数组,这会导致调用方持有的数组引用被改写成集合内容,容易引起误用。这种细节不读规约根本不会注意到。
4.3 并发与异常整改
并发相关的整改是存量项目里收益最明显的一项。它不像命名那样只是代码风格问题,而是真实的上线隐患。
// Before:static SimpleDateFormat + 吞异常 private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd"); public Date parseDate(String dateStr) { try { return SDF.parse(dateStr); } catch (ParseException e) { e.printStackTrace(); return null; } }问题有三层:第一层是 static SimpleDateFormat 在多线程下解析日期串会错乱;第二层是 e.printStackTrace() 不能用于日志采集;第三层是 catch 后返回 null,调用方无法区分是「解析失败」还是「数据本身就是 null」。
// After:DateTimeFormatter + 记录日志 + 不吞异常 private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); public Date parseDate(String dateStr) { try { return Date.from(LocalDate.parse(dateStr, DATE_TIME_FORMATTER) .atStartOfDay(ZoneId.systemDefault()).toInstant()); } catch (DateTimeParseException e) { log.error("parse date failed, dateStr={}", dateStr, e); throw new BusinessException("日期格式错误"); } }整改后:DateTimeFormatter 本身线程安全,能扛住多线程并发调用;日志记录了原始入参和完整堆栈,出了问题可以快速定位;抛出业务异常后,上层可以用统一异常处理机制捕获,不再出现静默失败。这一组整改需要业务方对异常语义达成共识,不能单方面决定抛异常,否则上线后可能出现新的故障。
5. 避坑指南:扫描器翻车的五个真实场景
5.1 现象:扫描出的问题数量比预期少很多,几乎等于没扫
有一次接手一个老项目,右键扫描后只报了十几个问题,当时觉得很奇怪。后来排查发现,项目里 Java 文件的编码是 GBK,插件在读取文件时遇到无法解码的中文注释直接跳过了解析,大量带中文注释的类没有进入扫描范围。
原因:规约插件对文件编码的识别依赖 IDE 的全局编码设置,当项目编码与 IDE 默认编码不一致时,插件解析会异常退出,不报错、只静默跳过。
解决:在 IDE 的 Settings → Editor → File Encodings 中把项目编码改为 UTF-8,同时把项目里的文件统一转码。从那以后我接手任何项目的第一件事就是确认编码,再跑扫描。
5.2 现象:插件装了,右键菜单里找不到规约扫描入口
插件安装成功后重启 IDE,右键项目目录时菜单里没有出现预期入口,误以为插件没装成功。我在内网环境遇到过两次,一次是 IDE 版本太旧,插件要求的最低版本没满足;另一次是装了社区版 IDE,而插件只支持旗舰版。
原因:插件有明显的最低版本限制,低于对应版本时插件加载失败但 IDE 不提示;部分 IDE 发行版不支持部分插件功能。
解决:先到插件管理器里确认插件状态是否为 enabled,再检查 IDE 版本是否在插件支持范围内。如果版本过低,要么升级 IDE,要么找对应旧版本的插件包。
5.3 现象:扫描报告里大量报「Equals 用 == 比较」的问题,但代码里 == 完全没问题
团队里有人给实体类重写了 equals 方法,但代码里仍然用 == 比较对象,扫描器判定为 Critical。开发觉得误报:重写后的 equals 逻辑比较的是 ID 字段,用 == 比较的是引用,两个对象引用肯定不相等,这就是正确逻辑。
原因:这不是误报。规约明确要求所有对象比较必须使用 equals,哪怕是同一个类的两个引用也不能用 ==,因为一旦类被代理(如 MyBatis 返回代理对象),== 比较就会失真,这是扫描器无法在静态分析阶段看出来的风险。
解决:统一改用 equals 比较;如果两个引用需要判断是否为同一个实例(比如做缓存标识),在代码里加注释说明为什么这里需要用 ==,并让扫描器通过抑制机制跳过。
5.4 现象:接入 Maven 插件后,构建失败,提示找不到 ruleset 文件
在 pom.xml 里配置好插件后执行mvn verify,报错提示找不到 rulesets/java/ali-comment.xml。查了本地仓库,插件的 jar 包存在,但里面没有对应的规则文件。
原因:p3c-pmd 插件在较新版本中把规则文件打包进了插件的 jar 里,但部分版本的 Maven 插件在解析 ruleset 路径时,相对路径解析基准不同,导致路径拼接后有误。
解决:将 ruleset 路径改为classpath:rulesets/java/ali-comment.xml格式,或者在 resources 里显式引入对应的规则文件。改完以后重新执行构建,规则就能被正确解析。
5.5 现象:整改完一个包后重新扫描,发现另一个包又冒出一堆新问题
团队按包分批整改,改完 A 包重新扫描,B 包的问题数量反而比之前多了。排查后发现,第一次扫描时 B 包的文件因为编码问题没被解析,编码修好之后,B 包里的旧问题全部暴露出来。
原因:分批整改的前提是一次完整扫描覆盖了所有文件,但编码、插件版本、扫描范围设置都可能影响文件覆盖率,如果第一次扫描本身不完整,后续对照就会失真。
解决:第一次扫描前,先确认全项目文件能被完整解析。一个简单的方法是随机抽查 3 到 5 个类文件,看扫描报告里是否都有它们的记录。确认基线正确后再开始整改,这样每次报告对比才有意义。
6. 把规约检查写进提交前脚本:一个管住手的小技巧
IDE 扫描是主动动作,靠自觉;CI 扫描是最后一道防线,靠机制。但 CI 的问题在于发现问题时代码已经提交了,整改成本变高。更实用的做法是在提交前加一道本地检查,用 git 的 pre-commit 钩子把规约扫描串进去。
#!/bin/bash # scripts/check-code-style.sh # 获取本次提交涉及的 Java 文件 STAGED_JAVA_FILES=$(git diff --cached --name-only --diff-filter=ACM -- '*.java') if [ -z "$STAGED_JAVA_FILES" ]; then exit 0 fi # 对暂存区文件执行规约检查 mvn p3c-pmd:pmd -Dpmd.includes=$(echo "$STAGED_JAVA_FILES" | tr '\n' ',') if [ $? -ne 0 ]; then echo "代码规约检查不通过,请先修复后提交" exit 1 fi脚本的逻辑分三段:先取出暂存区里新增、修改的 Java 文件列表;如果是空说明没有 Java 变更,直接放行;如果有变更,则把文件列表拼成逗号分隔串传给 pmd 插件的 includes 参数。检查不通过时提交被中断,开发必须先修复再重新提交。
参数方面有个细节值得说明:pmd.includes 传的是相对路径,必须与项目根目录的相对路径一致,否则匹配不到文件,钩子会静默通过,起不到拦截作用。初次配置后,建议故意在暂存区放一个有违规的代码文件,测试钩子是否真的能拦住。
把这个钩子放进项目根目录的 .git/hooks/pre-commit,并给脚本执行权限即可生效。团队场景下,更好的做法是把钩子脚本提交到代码仓库,让每个成员通过安装脚本把它链接到自己的本地 hooks 目录。
从那以后,我每接一个新项目,第一周就强制把这条链路走完:编码确认 → IDE 扫描建立基线 → 清零 Blocker/Critical → 接入本地钩子 → 推到 CI。规约文档可以放在书架上吃灰,但这些检查不能省。代码规约不是靠自觉来执行的,是靠工具链条把人管住。每一条规则背后都是一次真实的事故和修复成本,值不值得下载,跑一次扫描就有答案,希望帮到你。
本文还有配套的精品资源,点击获取