
简介面向Java开发者与项目代码质量管理场景的PMD规则配置包聚焦静态检查规则的定制与落地。包内共10个文件以9个XML规则配置和1份TXT说明为主XML承担规则集、规则、类别、参数及排除项等定义TXT便于快速了解用法压缩包仅35KB轻量便于导入项目或团队规范库。已有1059人学习下载。读者可从中获得按设计、代码规模、空代码块、导入、未使用代码等维度组织的规则配置理解规则ID、实现类与阈值参数的对应关系并据此裁剪出适配自身项目的检查集也可作为Eclipse PMD插件及持续集成环境的规则输入用于统一编码规范、发现潜在缺陷和冗余代码。内容按类别拆分便于按需组合与增量启用降低团队统一规范时的配置成本。整体适合希望提升Java代码质量、需要参考现成PMD规则写法的初中级开发者与团队规范维护者。1. 从四十几处 SuppressWarnings 说起接手一个跑了五六年的老服务时我先在代码库里搜了一遍SuppressWarnings光带 PMD 前缀的就有四十几处散落在十几个包里注释清一色写着“先关掉再说”。翻到后面才看明白问题不在业务代码而在一份没人维护的规则文件——它本质上是 PMD 的规则集配置决定哪些检查开着、哪些被摘掉、命中了算几级严重度、在什么目录下压根不扫。这份 PMD 的规则文件资源就是围绕 ruleset XML 展开的最小可用规则集怎么起、XPath 与 Java 自定义规则怎么写、规则集怎么分层、怎么在 Maven 和 Gradle 里挂上去、出了问题从哪查。适合刚接手静态检查、想把告警从“天天喊狼来了”调成“喊一次改一次”的 Java 工程同学也适合要给多个仓库统一口径的技术负责人。2. 规则集 XML 的骨架从最小可用到分类引用很多人第一次接触 PMD 规则文件是从复制一份别人写好的 XML 开始的。复制本身没问题问题是复制完不知道里面每一段在干什么后面想摘一条规则、改一个阈值只能靠猜。这一章把一个规则集文件拆开先保证它能跑起来再讲清楚引用、覆写、过滤这三件事各自解决什么问题。2.1 一个能跑起来的最小规则集规则集文件的本质是一个 XML 文档根节点是ruleset里面用若干个rule声明“我要哪条规则、带什么参数”。最小可用形态长这样直接放到仓库根目录的rulesets/下面就能被命令行和构建工具读到?xml version1.0 encodingUTF-8? ruleset nameteam-base xmlnshttp://pmd.sourceforge.net/ruleset/2.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.net/ruleset_2_0_0.xsd description团队基础规则集只保留会导致线上问题的检查/description !-- 1. 单条引用直接点名某条内置规则 -- rule refcategory/java/errorprone.xml/EmptyCatchBlock properties !-- 允许 catch 块里只有注释避免把“故意吞掉”的写法也报出来 -- property nameallowCommentedBlocks valuetrue/ /properties /rule !-- 2. 整类引用 排除一次挂上一个分类再摘掉不想要的 -- rule refcategory/java/codestyle.xml exclude nameShortVariable/ exclude nameLongVariable/ /rule !-- 3. 覆写严重度1 最高5 最低只调优先级不动规则本身 -- rule refcategory/java/design.xml/TooManyMethods priority3/priority /rule /ruleset这段写法里有三处最常被忽略。xmlns和schemaLocation不是装饰PMD 解析规则集时会按这个命名空间校验结构写错或者漏掉轻则 IDE 里一片红线重则直接抛解析异常。description建议写清楚这份规则集的定位因为半年后接手的人只看得懂这一句话。properties里的name必须是规则本身声明过的属性名大小写敏感写错一个字母PMD 就会报“未知属性”直接中断而不是帮你忽略掉。关于严重度PMD 用 1 到 5 表示1 最严重。这个数字同时影响两件事报告里的排序以及构建工具里failurePriority的拦截阈值。很多人只改了priority却发现 CI 行为没变原因就在阈值那一侧没动第五章会单独说这个坑。2.2 三种引用路径整类、单条、引用别的规则集ref属性的取值决定了你引的是什么实际项目里常用的有三种差别不小写法示例适用场景注意点整类引用category/java/errorprone.xml新项目起步先把一个大类全开规则多、噪声大必须配合 exclude单条引用category/java/errorprone.xml/EmptyCatchBlock精挑细选逐条治理规则改名后这里最先失效引用自建规则集rulesets/team-base.xml多仓库共享同一套口径路径相对当前规则集文件解析第三种最容易被低估。当团队有五六个仓库规则集迟早要收敛成一个公共文件各仓库再写自己的增量。写法是!-- 引用另一个规则集文件的全部内容 -- rule refteam-base.xml/ !-- 只引用里面的一条并在本项目里重新调参 -- rule refteam-base.xml/TooManyMethods properties property namemaxmethods value40/ /properties /rule !-- 引入整个规则集但摘掉其中一条 -- rule refteam-base.xml exclude nameCommentRequired/ /ruleexclude只对同一次ref引入的内容生效也就是说你不能在前面引了整类、后面另起一个rule ref去 exclude 里面的一条——那样写不会报错但也不会生效属于比较典型的“看着改了其实没改”。另外同一条规则如果在文件里被引用两次PMD 会合并后出现的属性覆盖前面的。这个行为本身不算错但排查时很难看出来所以我的习惯是一个规则文件里一条规则只出现一次需要在不同场景用不同参数就拆成两个规则集文件。2.3 在规则集里过滤文件include-pattern 与 exclude-pattern规则集不只管“查什么”还管“查谁”。根节点下可以直接写文件过滤规则值是匹配文件路径的正则ruleset nameteam-base ... description排除生成代码与迁移脚本/description !-- 只分析 java 文件避免误扫模板和资源 -- include-pattern.*\.java/include-pattern !-- 生成代码、老版本兼容层不参与检查 -- exclude-pattern.*/generated/.*/exclude-pattern exclude-pattern.*/legacy/v1/.*/exclude-pattern /ruleset这三条写上去构建工具和命令行都会遵守比自己写脚本拼文件列表省事得多。要注意的是路径分隔符在 Windows 和 Linux 上不一致正则里用/一般没问题因为 PMD 内部会做归一化但如果你的规则写死了\\在 CI 上就会静默失配——匹配不上不会报错只是那些文件被安静地跳过了你会以为是规则没生效。还有一种误伤把exclude-pattern写得太宽比如.*test.*结果把src/main/java/.../ContestService.java也排掉了。我一般会在规则集旁边留一行注释写清楚这个正则想排的到底是哪几个目录避免后人放大它。3. 自定义规则XPath、Java 实现与规则集打包内置规则覆盖了大部分通用场景但每个团队都有自己的硬性约定不许直接printStackTrace、DAO 层不许出现字符串拼接 SQL、对外暴露的 DTO 字段名必须带后缀。这类约定用内置规则拼不出来只能自己写。PMD 给了两条路——XPath 和 Java 类选哪条取决于规则的复杂度和是否需要跨文件判断。3.1 XPath 规则先看 AST 再写表达式XPath 规则的好处是不用编译、不用打包、改完立即生效适合“单文件内能判断完”的约束。写之前必须先看 AST因为 PMD 的 XPath 是对语法树求值节点名写错一个就是一个永远不命中的空规则。PMD 自带一个 designer 工具可以直接粘一段代码、看它的树结构也能实时调试 XPath。这个过程建议不要跳过凭记忆写节点名命中率很低。确认节点后规则长这样rule nameAvoidPrintStackTrace languagejava message不要直接调用 printStackTrace请用日志框架并带上上下文 classnet.sourceforge.pmd.lang.rule.xpath.XPathRule descriptionprintStackTrace 只输出到标准错误生产环境会丢失链路信息/description priority3/priority properties property namexpath value![CDATA[ //PrimaryExpression /PrimaryPrefix /Name[Image printStackTrace] ]]/value /property /properties example ![CDATA[ // 违规 catch (Exception e) { e.printStackTrace(); } // 合规 catch (Exception e) { log.error(处理订单失败, orderId{}, id, e); } ]]/example /ruleclass固定写 XPathRulelanguage决定这条规则只在 Java 文件上跑message是报告里输出的文案——这条别写“违反规范”这种废话写清楚“为什么不行”开发者才愿意改。example里的正反例会被规则集文档工具抓取写好了等于顺手把团队文档也维护了。XPath 里几个高频套路用Image匹配标识符文本用[Image xxx]精确取值用and组合条件避免误伤。要特别注意版本差异PMD 6 和 PMD 7 的 Java AST 节点名并不完全一致同一段 XPath 在 6 上命中、在 7 上可能一条都报不出来。跨大版本迁移时务必拿 designer 重新核一遍节点路径。3.2 Java 规则需要状态或跨节点判断时再上XPath 表达不了的东西比如“统计一个方法体里连续的 if 层数”“判断某个字段在整份文件里有没有被读写过”就得写 Java 类。简单场景继承抽象规则类就够了import net.sourceforge.pmd.lang.java.ast.ASTName; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; public class AvoidSystemOutRule extends AbstractJavaRule { Override public Object visit(ASTName node, Object data) { // ASTName 覆盖了字段访问、方法调用前缀等场景这里只盯 System.out / System.err String image node.getImage(); if (System.out.equals(image) || System.err.equals(image)) { // 第三个参数是报告文案会覆盖规则集里的 message addViolationWithMessage(data, node, 禁止直接使用 image 请注入日志组件); } return super.visit(node, data); } }这段代码的逻辑是遍历每个标识符节点命中System.out或System.err就报一次。visit返回super.visit保证继续往下走否则会漏掉子节点。data是规则上下文多文件级规则里用它传状态。有一点必须提前知道PMD 大版本升级时自定义规则的基类和访问者签名有调整旧的AbstractJavaRule写法在新版本里可能被标记为过时需要用新的规则链式 API 重写。所以自建规则不要写太多能 XPath 解决的优先 XPath改造成本低。3.3 打包成 jar让 PMD 认得你的规则Java 规则要进 classpath最稳的形态是打成 jar。目录结构按包名来编译后打进去即可# 目录结构 # custom-pmd-rules/ # src/main/java/com/example/pmd/AvoidSystemOutRule.java # src/main/resources/category/java/team.xml # 编译并打成 jar mvn -q clean package # jar 里必须能找到规则定义文件路径要和 category 引用对得上 jar tf target/custom-pmd-rules.jar | grep team.xml # 期望输出category/java/team.xml关键在于META-INF之外的资源路径。PMD 在 classpath 上找规则定义文件时是按category/java/xxx.xml这样的相对路径查的所以资源目录要建一样的层级规则集里才能用category/java/team.xml/AvoidSystemOut引到自定义规则。jar 放进 classpath 后命令行用-auxclasspath或直接放到 lib 目录规则集文件里就可以像引用内置规则一样引用它。3.4 挂到 Maven、Gradle 与命令行规则集写完只是半成品真正的验收标准是构建里能拦住人。Maven 侧的配置plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-pmd-plugin/artifactId configuration !-- 用绝对路径拼避免相对路径解析到 target/classes 下 -- rulesets ruleset${project.basedir}/rulesets/team-base.xml/ruleset /rulesets failOnViolationtrue/failOnViolation !-- 严重度小于等于 3 的违规才让构建失败 -- failurePriority3/failurePriority printFailingErrorstrue/printFailingErrors includeTestsfalse/includeTests /configuration /plugin${project.basedir}/这个前缀不是可有可无相对路径在插件里解析的基准目录和你想的不一样第五章会详细说这个坑。failurePriority决定了“几级及以上算失败”和规则集里的priority是两套数必须对齐。Gradle 侧有个额外的坑插件自带一套默认规则集ruleSetFiles是往默认集合上追加的不是替换。所以要先把默认清空pmd { // 清空内置默认集否则会和自定义规则叠加出现意料之外的告警 ruleSets [] ruleSetFiles files($rootDir/rulesets/team-base.xml) consoleOutput true ignoreFailures false }命令行验证时可以直接跑PMD 7 的命令形态是pmd check6 系是直接pmd -d参数名 also 有差异# PMD 7 pmd check -d src/main/java -R rulesets/team-base.xml -f text -r build/pmd.txt --cache .pmd-cache # PMD 6-cache 是单横线 pmd -d src/main/java -R rulesets/team-base.xml -f text -r build/pmd.txt-d是待分析目录-R是规则集路径-f是报告格式text / xml / sarif 都支持-r是输出到文件不加就直接打屏。缓存能显著提速但改完规则集建议先清一次缓存再跑避免拿着旧结果下结论。4. 分层与抑制让规则集在团队里活过三个月新规则集上线第一周通常很受欢迎第二周开始有人抱怨第三周就有人往代码里加SuppressWarnings。这不是开发者的错多半是规则集没分层、没给合法的豁免通道。这一章讲怎么把规则集拆成可维护的结构以及四条不同粒度的抑制手段分别该在什么时候用。4.1 三层结构基础层、团队层、项目层单文件规则集在第三个仓库接入时就会失控。我一般拆成三层每层职责明确第一层是基础层只放“任何 Java 项目都该遵守”的规则比如空的 catch 块、资源未关闭、循环里拼字符串。这一层的规则数量要克制控制在二三十条宁可少而准也不要一口气开两百条然后被集体无视。第二层是团队层放本团队的语言约定比如命名后缀、注释要求、禁止使用的 API。这一层通过引用基础层再追加ruleset nameteam-standard ... description团队标准 基础层 团队约定/description !-- 全量引入基础层 -- rule refbase.xml/ !-- 团队约定DTO 必须以 Dto 结尾 -- rule refcategory/java/codestyle.xml/ClassNamingConventions properties property nameclassPattern value[A-Z][a-zA-Z0-9]*/ /properties /rule !-- 团队约定单个方法不超过 80 行 -- rule refcategory/java/design.xml/ExcessiveMethodLength properties property nameminimum value80/ /properties /rule /ruleset第三层是项目层只放“这个项目特殊”的差异比如老模块暂时放宽圈复杂度、新模块启用更严的检查。项目层的内容应该越少越好超过十条就说明该回收到团队层了。分层的实际收益在升级时最明显基础层改一条所有仓库跟着变不用挨个改文件。代价是公共规则集必须以某种方式分发出去——可以是独立的 jar 包走私服也可以是 git submodule看团队现有基础设施别为此新造一套轮子。4.2 四条抑制通道粒度从细到粗规则集不可能百分之百准确一定要留合法的豁免通道否则大家会自己发明一套更糟的——比如把整段代码注释掉。按粒度从小到大排有四条路最细的是行级//NOPMD注释。它必须写在违规的那一行末尾写在上一行不起作用// 错误写法单独一行抑制不到下一行 // NOPMD String sql select * from t where id id; // 正确写法跟在违规行尾部 String sql select * from t where id id; // NOPMD 主键由上层校验为 long第二级是注解级SuppressWarnings可以挂在方法、字段、类上适合“这个方法确实要这么写”的场景。规则名要写全限定形式比如SuppressWarnings(PMD.AvoidPrintStackTrace)只写PMD会把整个类的检查都关掉属于过度抑制SuppressWarnings(PMD.AvoidPrintStackTrace) // 只关这一条 private void dumpForLocalDebug(Throwable t) { t.printStackTrace(); }第三级在规则集里做统一豁免用violationSuppressXPath指定“哪些节点不报”。这招特别适合生成代码——你没法往生成的文件里加注解但可以在规则集里统一放过rule refcategory/java/errorprone.xml/AvoidLiteralsInIfCondition properties !-- 类名以 Generated 结尾的整类跳过这条规则 -- property nameviolationSuppressXPath value//ClassOrInterfaceDeclaration[ends-with(SimpleName, Generated)]/ /properties /rule第四级是文件级过滤也就是 2.3 节讲的exclude-pattern用于整个目录不参与分析。这招最重用之前想清楚被排除的目录会彻底脱离质量视野三个月后没人记得它为什么被排除。4.3 从“全量崩溃”到增量收紧最容易失败的上线方式是把规则集一次性调严然后全量跑一遍出来几千条违规。这时候只有两个结局要么构建一直红着没人管要么把阈值调到形同虚设。我的做法是先跑一遍拿到存量数字把当前违规数记成基线然后只对新代码生效# 第一次只统计不拦截拿到存量基线 pmd check -d src/main/java -R rulesets/team-standard.xml -f text -r build/pmd-baseline.txt # 统计各规则的违规条数按数量倒序排先治大头 grep -oE ^\S\.java:[0-9]:\s[^:] build/pmd-baseline.txt \ | awk -F: {print $2} | sort | uniq -c | sort -rn | head -20这段命令的作用是把报告里每条违规的规则名抽出来计数一眼看出哪几条规则贡献了八成噪声。数量特别大的那几条通常说明参数设置和团队现状不匹配先调参数而不是逼着大家改代码。然后按“先修影响面大的、后修边角”的顺序推进每清理完一批就把对应的规则从“仅记录”提升到“拦截”。拦截面用failurePriority控制慢慢从 1 收到 2 再收到 3。这个过程通常要持续几个迭代急不得——一次性把红线画满最后一定是红线被整体无视。5. 排错规则文件最容易翻车的五件事规则文件本身不复杂出问题往往出在“看起来生效了其实没生效”这类静默失败上。下面五条是实际排查里重复率最高的每条都按现象、原因、解决三步写遇到对得上的直接照着改。5.1 本地规则全生效CI 上一条都不报现象命令行跑本地规则集能出一堆告警推到 CI 后报告是空的构建一路绿灯。原因Maven 插件里的rulesets相对路径不是相对项目根目录解析的而是走 classpath。规则文件放在仓库根目录的rulesets/下本地手工跑当然找得到插件里就是找不到而部分版本对找不到的规则集不会硬失败于是安静地用了默认集或者空集。解决一律用${project.basedir}拼绝对路径或者把规则文件放到src/main/resources下让它随资源一起进 classpath。改完在 CI 里故意提交一条必违规的代码验证一次别靠看日志猜。5.2 XML 能解析但中文描述变乱码、schema 校验告警现象规则集在本地编辑器里正常换台机器或进 CI 后报 XML 解析异常或者告警里全是问号。原因两个常见点。一是文件里声明的encoding和实际保存编码不一致Windows 上默认存的 GBK 文件被当成 UTF-8 读二是schemaLocation写成了 http 地址但环境不允许出网校验超时或失败。解决统一用 UTF-8 保存文件头声明保持一致schemaLocation里的地址用于 IDE 校验PMD 运行时不强依赖它内网环境可以把它去掉只保留xmlns。规则集里的中文只出现在description和message确认这两处编码正常就够了。5.3 调了 priority 但拦截行为完全没变现象把某条规则从 3 改成 1期望它拦住构建结果 CI 还是绿的。原因priority只是给违规打严重度标签真正决定构建失败的是构建工具侧的failurePriority阈值。两个数字分属不同文件改一个不动另一个行为当然不变。解决把阈值当成一等公民管理。规则集的 priority 和插件的failurePriority放在一起评审改一个就检查另一个。另外注意failOnViolation如果是 false阈值调多严都不会拦这个开关经常在项目初期被关掉然后忘了打开。5.4 exclude 写了但规则还在报现象整类引入后加了exclude nameXXX/报告里这条规则照样出现。原因exclude里的名字必须和规则的实际名称完全一致大小写、拼写差一个字符就不生效而且不会有任何提示。更隐蔽的一种是把它写在了单独的一个rule ref里——那个ref引入的是整类exclude却挂在了另一个不相干的ref节点下。解决exclude必须和它所排除的ref是同一个rule节点。名字直接去 PMD 的规则文档里复制别手打。改完跑一次全量报告用规则名计数确认目标规则真的归零了而不是看文件里“写了 exclude”就放心。5.5 升级 PMD 大版本后一批规则报“找不到”现象换了个 PMD 版本构建直接失败报Cannot resolve rule/reference指向规则集里某几行。原因大版本升级时规则会重命名、合并或者从某个分类挪到另一个分类老的category/java/xxx.xml/RuleName路径自然失效。同时自定义 Java 规则的基类签名也可能变。解决升级前先跑一次pmd check的规则列表命令或直接看新版本规则文档把失效的规则名逐个比对替换不要只改分类路径。自建 XPath 规则要拿 designer 重新核对节点名AST 在大版本间有调整是常态。升级这件事单独排一个迭代不要和业务需求混在一起做否则回滚成本很高。6. 验证一条规则是真生效还是假生效规则文件最大的风险不是写错是写完不知道对不对。新加一条规则最后悔的场面是上线两个月后才发现它从来没命中过——要么节点名写错要么被某个更宽的 exclude 顺带排掉了。所以我现在写完规则集一定要走一遍“正例反例对拍”不做这一步不算完成。6.1 建一对专门用来违规的文件在仓库里留一个pmd-selftest目录放两个文件一个故意写满违规一个严格合规。目录本身不参与主构建只在验证规则集时被扫。// pmd-selftest/BadCase.java —— 每一处都能触发一条规则 public class BadCase { public void run() { try { Thread.sleep(100); } catch (Exception e) { e.printStackTrace(); // 应命中 AvoidPrintStackTrace } } }// pmd-selftest/GoodCase.java —— 同样的业务逻辑写法合规 public class GoodCase { private static final Logger LOG LoggerFactory.getLogger(GoodCase.class); public void run() { try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断标志 LOG.warn(休眠被中断, e); } } }这两个文件的价值在于可重复。每次改规则集扫一遍期望结果是BadCase报 1 条、GoodCase报 0 条。多了说明规则误伤少了说明规则没生效。6.2 看报告内容别看退出码pmd check -d pmd-selftest -R rulesets/team-standard.xml -f text -r build/selftest.txt echo exit$? # 断言违规文件必须报出指定规则 grep -q AvoidPrintStackTrace build/selftest.txt echo 规则命中 OK || echo 规则失效 # 断言合规文件必须零告警 grep -c GoodCase.java build/selftest.txt退出码只能告诉你“有没有违规”不能告诉你“是哪条规则在报”。把grep断言放进脚本规则名对不上就报错比人眼扫报告可靠得多。第二段grep -c期望输出是 0如果不是 0说明合规写法被误伤了得回去看规则的属性参数是不是太严。6.3 一次典型的踩坑复盘有一次给几个仓库加一条“禁止在循环里拼字符串”的规则规则集改完跑本地报告里干干净净我以为是代码质量好直接推了。两周后有人问我这条规则到底有没有生效回头一查——规则集里那条ref引用的分类路径在新版本里已经变了PMD 报了个警告就跳过了而构建配置里刚好没把这类警告升级成失败。从那以后我每次动规则集都强制走一遍“正例必须报、反例必须不报”的对拍BadCase报不出来就当规则没写。多花的这两分钟比事后翻两个月前的构建日志便宜太多。希望帮到你。本文还有配套的精品资源点击获取