简介PMD 是一款开源的 Java 静态代码分析工具能在编码阶段帮助开发者发现潜在 bug、冗余代码和不良习惯并配合 Eclipse 插件在编辑器中实时反馈。这组规则文件打包为 zip 压缩包共 10 个文件其中 9 个为 XML 规则配置、1 个为 TXT 使用说明整体仅 35KB轻量且便于导入。规则文件按类别拆分组织覆盖 design、imports、empty、unusedcode、codesize、finalizers、unnecessary、basic 等常见检查维度并提供 all 总集合配置既支持整体套用也可按需单独引用。导入 Eclipse 的 PMD 插件后即可运行代码检查还能通过参数调整阈值、排除特定文件实现更贴合团队的代码质量管控。目前已有 1056 人学习适合希望快速上手规则定制、提升 Java 代码质量的开发者。1. PMD 规则文件静态检查的“裁判规则”为什么比代码本身更重要PMD 跑在 CI 上报出一堆 Warning团队看多了就麻木了问题到底出在哪出在规则文件上。PMD 的规则文件就是那本裁判手册——哪条算违规、违规算多严重、要不要让构建失败全由 XML 里那几行决定。我拆过不少项目第一件事永远是翻规则文件而不是看检查报告。因为报告只是结果规则文件才是源头。这篇笔记适合三类人想给团队定制检查规则的 Java 工程师、被 PMD 误报惹烦了想收紧规则的人、以及打算从默认规则集迁移到自维护规则文件的项目负责人。2. 规则文件的结构从 ruleset 根节点到 rule 子节点的参数地图2.1 根节点和骨架参数命名空间、description、rule 三件套打开一份 PMD 规则文件第一眼就是 XML。认准根节点ruleset它有两个必填属性xmlns和xmlns:xsi。这里有个普遍存在的翻车点PMD 6 和 PMD 7 的命名空间 URL 不一样老文件直接拿到新版 PMD 上加载解析器会报“无法识别的规则集”。所以开局第一件事确认你项目里 PMD 的版本再决定用哪套命名空间。一份最小可用的规则文件长这样?xml version1.0 encodingUTF-8? ruleset nameMyCompanyRules 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团队自维护的 PMD 规则集/description rule nameAvoidSystemOutRule languagejava message不要直接使用 System.out 打印请使用日志框架 classcom.example.pmd.AvoidSystemOutRule priority2/priority /rule /ruleset逻辑说明name给规则起唯一标识language指定目标语言message是违规时输出给开发者的提示文案class指向处理该规则的类或 PMD 内置规则处理器。priority是严重级别从 1 到 51 最严重。参数说明message写得越具体越好我见过只写 “bad code” 的规则开发者在 PR 评论里追着问“到底哪里 bad”。所以 message 里我一般会带上“期望的替代方案”比如例子里的“请使用日志框架”减少沟通成本。priority建议 2 或 3 起步一上来就全设 1CI 会红得让你怀疑人生。2.2 priority 优先级语义1 到 5 到底怎么分优先级不是摆设它直接决定告警在报告里的归类以及后续能不能接进质量门禁。我的习惯是优先级语义典型场景落地策略1Blocker阻断资源未关闭、空指针高危路径修复前不允许合并2Critical严重System.out 直出、违反团队 API 约束进 CI 失败条件3Major一般空的 catch 块、魔法值过多告警统计趋势4Minor轻微命名不规范、局部变量可复用告警不阻断5Info信息代码风格偏好只进 IDE 提示实际项目里我建议优先级 1 和 2 的规则控制在十条以内。规则文件不是越严越好而是越符合团队当下承受力越好。上来就压 50 条 Blocker结果就是团队偷偷在 CI 配置里把 PMD 插件禁掉——这招我在不止一个项目里见过。2.3 引用内置规则集用 exclude 和 include 裁剪而不是全量引入PMD 自带一批规则集按类别分布在category/java下面比如errorprone.xml、performance.xml、bestpractices.xml。直接在规则文件里引入整个类别是很多项目的默认操作rule refcategory/java/bestpractices.xml/但全量引入的问题很现实一个类别几十条规则里面有三分之一不符合团队口味误报率一高规则文件的可信度就崩了。更稳的做法是引用单条内置规则rule refcategory/java/bestpractices.xml/UnusedPrivateMethod priority3/priority /rule如果想保留某个类别大部分规则只去掉几条刺头用excluderule refcategory/java/errorprone.xml exclude nameCloseResource/ /rule这里说明一下参数ref的路径格式是规则集文件路径/规则名路径对大小写敏感规则名不能写中文别名。我早期吃过这个亏errorprone.xml/CloseResource写成了closeResourcePMD 静默跳过构建还是绿的——最怕的不是报错而是这种“看似成功”的假象。3. 自定义规则的两种写法XPath 规则和 Java 规则怎么选3.1 什么时候必须写自定义规则默认规则覆盖不到的“人肉规范”内置规则集再全也管不住团队自己的约定。常见的例子公司要求禁止使用java.util.Date、禁止往catch块里塞超过三行业务逻辑、禁止在Controller里直接调Mapper。这些约束写在架构文档里就是靠 Code Review 人肉执行效率低还不稳定。把它们写进 PMD 规则文件等于把文档变成自动执行的检查器。写自定义规则有两条路XPath 规则和 Java 规则。选哪条看你要匹配的结构复杂程度。XPath 规则适合“节点形态匹配”比如“空 catch 块”“方法名不能以test开头”Java 规则适合需要跨节点分析、需要看上下文、需要做数据流判断的场景比如“检测资源是否关闭”。3.2 XPath 规则实操空 catch 块检测空 catch 块是典型的“一眼就能看出来但内置规则不一定管”的场景。用 XPath 写法如下rule nameAvoidEmptyCatch languagejava messagecatch 块不能为空至少记录一下异常日志 classnet.sourceforge.pmd.lang.rule.XPathRule description禁止空 catch 块/description priority3/priority properties property namexpath value ![CDATA[ //CatchStatement/Block[count(*)0] ]] /value /property property nameversion value2.0/ /properties /rule逻辑说明//CatchStatement/Block先定位到所有 catch 块的代码块count(*)0过滤出没有任何子节点的空块。CDATA包住 XPath 表达式避免 XML 解析器把表达式里的特殊字符吃掉。参数说明version这个属性很容易被忽略它决定 XPath 的方言版本。PMD 6 默认走 XPath 1.0 兼容模式但 1.0 不支持很多高级函数写 2.0 能用的函数更多但要求解析器支持。另一个坑在class路径PMD 7 里 XPath 规则的类路径变成了net.sourceforge.pmd.lang.rule.xpath.XPathRule老路径也能跑但日志里会打 deprecated 警告。建议直接用新路径反正两个版本都识别。3.3 Java 规则实操禁止 System.out.printlnXPath 写不动的场景就轮到 Java 规则上场。比如禁止System.out.println用 XPath 也能写但要想排除字符串里的System.out.println这种误报就得上 Java。package com.example.pmd; 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) { String image node.getImage(); if (image ! null image.startsWith(System.out.print)) { asCtx(data).addViolation(node, 不要直接使用 System.out 打印请使用日志框架); } return super.visit(node, data); } }逻辑说明visit(ASTName ...)是 PMD 访问者模式的入口AST 上的每个名称节点都会经过这里。getImage()取节点文本内容startsWith(System.out.print)同时覆盖println和print。asCtx(data).addViolation(...)是 PMD 7 里新增的违例上报写法如果是 PMD 6写成addViolation(data, node, ...)两个版本 API 差异不小这个点单独记忆。参数说明规则本身不需要额外参数但我建议把 message 文本作为常量放 Java 类里XML 里只留priority和class引用减少两处维护的成本。规则注册回 XML 时message属性在 XML 里可以省略类里上报的文本会覆盖它——这也是一个值得知道的优先级关系。4. 把规则文件接进构建Maven 与 Gradle 的配置与参数边界4.1 Maven 插件配置rulesets 路径和 failOnViolation 的关系Maven 项目用maven-pmd-plugin核心是告诉插件“用哪份规则文件”。常见配置plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-pmd-plugin/artifactId version3.21.0/version configuration rulesets rulesetsrc/main/resources/ruleset.xml/ruleset /rulesets failOnViolationtrue/failOnViolation maxAllowedViolations50/maxAllowedViolations printFailingErrorstrue/printFailingErrors /configuration executions execution goals goalcheck/goal /goals /execution /executions /plugin逻辑说明rulesets里写的是相对于项目根目录的路径可以写多个。failOnViolationtrue意味着只要发现违例就挂掉构建但有一个例外——maxAllowedViolations允许你设一个“容忍额度”比如 50只要总数不超过 50构建依然能过。printFailingErrors会在构建日志里打出具体的违规代码和位置。参数说明这里有个容易误读的组合failOnViolation控制的是“有没有违例”maxAllowedViolations控制的是“违例数量上限”。我见过团队把failOnViolation设成false结果 PMD 变成了只出报告、不干活的状态规则白写。正确姿势failOnViolation必须为truemaxAllowedViolations设为 0然后根据实际跑出来的存量违规数规划每周降 10 条。4.2 Gradle 插件配置ruleSetFiles 与 ruleSets 的覆盖关系Gradle 用pmd插件配置块长这样pmd { toolVersion 6.55.0 ruleSetConfig rootProject.files(config/pmd/ruleset.xml) ruleSets [] ignoreFailures true maxFailures 30 }逻辑说明ruleSetConfig指向规则文件ruleSets []是一个极其关键的清空动作。Gradle PMD 插件默认会加载一批内置规则集不写空数组你的自定义规则文件会和默认规则集一起生效等于两套裁判同时执法。ignoreFailures true是“只报告不阻断”配合maxFailures 30才能在失败和容忍之间找到平衡。参数说明toolVersion建议显式固定别用默认版本否则团队机器上装的 Gradle 版本不同拉到的 PMD 版本也不同规则加载行为会漂移。还有个隐藏属性pmdMain.pmd和pmdTest.pmd是分开的默认只跑pmdMain源码质检和测试代码质检要分开配置。4.3 多模块项目规则文件别复制用构建脚本统一分发多模块 Maven 项目里如果每个子模块的 pom 里都维护一份规则的相对路径很容易出现“A 模块改了规则B 模块还是旧规则”的漂移。我的习惯是建一个独立的config模块或者把规则文件放在父 pom 的src/main/resources下子模块通过${project.parent.basedir}引用rulesets ruleset${project.parent.basedir}/src/main/resources/ruleset.xml/ruleset /rulesets这样规则文件只有一份就是单点维护。对于 Gradle 多项目把pmd.ruleSetConfig写进subprojects块即可。5. 避坑与排查规则文件实战的 5 条血泪记录5.1 规则文件加载了但没有任何效果现象CI 照常跑报告里一条违规都没有代码里明显有违规却“幸存”。原因最常见的是路径写错Maven 里src/main/resources/rule.xml写成src/main/rule.xmlPMD 不会报错只会在日志里打一行 WARN 表示规则集为空。另一个原因是 Gradle 的ruleSets没清空自定义规则被默认规则集顶掉。解决先跑一次mvn pmd:check -X在调试日志里搜“Ruleset loaded”或者“ruleset”看实际加载了哪份文件、文件里解析出几条规则。Gradle 就gradle pmdMain --info直接看PMD任务日志里最后一行加载的规则集路径。路径问题一把梭就能查出来。5.2 XPath 在 PMD Designer 里能匹配命令行走就翻车现象在 PMD Designer 可视化界面里测试 XPath 表达式能命中目标代码部署到 CI 命令行跑同样的规则一条都不报。原因版本不一致。Designer 是独立下载的 GUI 工具可能内置 PMD 7 的内核而项目里 Maven 插件用的是 PMD 6两个版本的 AST 节点结构有变化XPath 的表达式写法不通用。另外version属性设成 1.0 还是 2.0 也会导致能力差异。解决把 Designer 的版本和项目toolVersion/Maven 插件版本对齐在 Designer 里加载的就是项目同款 PMD 内核。另外在pom.xml里显式固定插件版本别再让 Maven 拉最新版。这件事属于典型的“本地对了、线上没对”排查成本极高。5.3 priority1 的规则让整个模块构建下线现象团队加了五条 priority1 规则第二天 CI 变成红色瀑布所有人都在等规则修复才能合并。原因存量代码里违规数量巨大没有人统计过基线。规则文件上线前没跑过一次“只报告不阻断”的模式直接开了 failOnViolationtrue等于把历史债务一次性全额催收。解决新规则先以maxAllowedViolations大额度运行两个迭代把存量违规清得差不多再逐渐收紧额度。也就是先让子弹飞一会。我一般用maxAllowedViolations等于当前存量违规数加 10% 的缓冲每周减 5%。5.4 排除生成的代码target 目录里的代码也被扫了现象规则文件里明确排除了一部分包但构建报告里依然出现target/generated-sources下的代码告警。原因PMD 的忽略逻辑是“基于路径前缀”exclude写的是包名路径不是文件系统路径。比如你写exclude namecom.example.generated/但如果生成的类在com.example.generated.model且源码在target/generated-sources/...下路径前缀需要按源目录的物理结构写。解决在规则的exclude-pattern里写物理路径exclude-pattern.*/target/generated-sources/.*/exclude-pattern这是正则匹配注意转义点号。生成的代码本来就不该被人工规则约束一眼排除最干净。5.5 中文注释引发“UTF-8 编码错误”翻车现象规则文件里写了中文 description 和 messageCI 上加载时报org.xml.sax.SAXParseException ... Invalid byte 1 of 1-byte UTF-8 sequence。原因Windows 上有时候 IDE 默认用 GBK 存盘文件头还是xml encodingUTF-8实际字节流不是 UTF-8。PMD 按 XML 声明格式解析读到 GBK 字节流就爆。解决统一用 UTF-8 无 BOM 格式保存规则文件并在 Maven 插件里加inputEncodingUTF-8/inputEncoding。这种问题在本地 IDE 很难发现Windows 本地能跑Linux CI 上必炸。6. 用 PMD Designer 和最小样例验证规则防翻车的那道保险规则文件写完别直接接 CI。我每次写新规则都强制先做一轮“最小样例验证”成本和收益比极高。先准备一个只有几行代码的测试类TestRule.java里面故意写一个违规案例和一个合规案例。比如测空 catch 块规则就写public class TestRule { public void bad() { try { Thread.sleep(100); } catch (InterruptedException e) { // 空块应该被 PMD 抓到 } } public void good() { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }然后在 PMD Designer 里加载规则文件和这个测试类看违规是不是只命中bad()。Designer 左侧是 AST 树选中违规节点能直接看到 PMD 是顺着哪条路径找到它的——这种可视化的排查比在 CI 日志里猜要快得多。确认 Designer 里结果正确后再用命令行扫一遍确保真实运行时的行为一致pmd -d TestRule.java -R ruleset.xml -f text输出会列出命中的行号和违规描述。此时看两件事第一违规行号是不是对应着bad()方法第二good()是不是真的没被误报。误报比漏报更危险漏报只是规则没生效误报是规则在“误伤好人”团队对规则的信任会被一次误报消磨大半。规则文件这东西看着是静态的 XML实际上每次变更都活在“方便”和“误伤”的博弈里。我后来自己也算想明白了规则上线前走一遍 Designer 加载、命令行复核、样例代码双面验证其实就是给规则上了道保险花不了半小时却能拦住后面成串的“规则崩了”的问答。从那以后我每次提交规则文件都强制先跑这套验证再发 PR——希望帮到你别让规则文件成为团队里那个没人敢碰的黑匣子。本文还有配套的精品资源点击获取