简介PMD是开源的静态代码分析工具用于检测Java源代码中的潜在问题与编码规范偏差。该压缩包提供一套PMD规则文件的XML配置合集主要面向使用Eclipse PMD插件或对接持续集成流程的Java开发者可帮助团队建立自定义代码检查策略。压缩包共10个文件其中9个XML规则文件覆盖基础、空代码、设计、导入、finalizers、未使用代码等常见类别另附1个说明文本整体仅35KB轻量易用。已有1057人浏览学习该资料。通过这些规则文件读者可快速导入并运行PMD检查掌握ruleset、rule、category、parameter、exclude等核心配置的作用理解如何定制阈值、排除特定文件或方法为日常代码质量管理提供可参考的样板。1. PMD 的规则文件先搞清楚它到底管什么PMD 是 Java 静态代码分析工具里用得比较多的一款而它的规则文件就是让 PMD 真正干活的指挥中心。这个文件本质上是 XML里面描述了你希望 PMD 在扫描代码时检查哪些问题方法太长、空 catch 块、未使用的变量、嵌套过深的 if、System.out.println 调用等等。没有规则文件PMD 只是一台空转的扫描引擎有了它PMD 才能变成符合团队口味的代码警察。这篇笔记适合两类人一类是用了默认规则集但被误报和漏报困扰的开发者另一类是准备定制团队规范、打算动手写规则的新手。我会从结构拆到落地再把我踩过的坑一次讲完。2. 规则文件的结构拆解从 ruleset 到 rule 再到 property2.1 ruleset 根节点命名空间与 schema 决定你的格式合法性所有 PMD 规则文件的根节点都是ruleset。这个节点上有两个东西决定文件能不能被正确解析xmlns命名空间和xsi:schemaLocation。PMD 从 6.x 开始统一使用http://pmd.sourceforge.net/ruleset/2.0.0这个命名空间旧版的1.0.0虽然还能被兼容但新规则特性不会生效。我一般直接复制自带规则集头部作为模板而不是手打因为 schema 的 URL 有大小写和斜杠的坑。一个最小骨架如下?xml version1.0 encodingUTF-8? ruleset nameMyCustomRules 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 /ruleset这个骨架里name属性会出现在 PMD 的报告中建议用有业务含义的名字比如MyServiceRules。description是给人看的可以写清规则集的适用场景。如果name或者description空着不写PMD 不会报错但后续在 IDE 插件或 CI 报告里很难定位规则来源属于给自己埋雷。需要注意schemaLocation里的 URL 在离线环境可能无法访问但 PMD 在大多数情况下会优先读取本地 schema 兜底。如果出现无法解析 schema 的异常常见做法是去掉xsi:schemaLocation属性只保留xmlns。这个细节看起来小却是我见过的最容易卡住新人的地方。2.2 rule 节点的核心属性language、class、message、priorityrule是规则文件里唯一真正干活的节点。每个 rule 节点定义一条规则它有四个最核心的属性缺一个或者写错一个都会导致规则异常跑不出结果。name规则的唯一标识在同一个 ruleset 内不能重复最好全局也不重复否则多 ruleset 合并时可能冲突。language指定规则作用于哪种语言Java 规则写java其他语言同理。PMD 会用它来匹配源代码的语言类型。如果写错规则会被加载但永远不触发。class规则实现类的全限定名。PMD 分为 Java 写死的规则和基于 XPath 的规则后者 class 统一是net.sourceforge.pmd.lang.rule.XPathRule。message规则触发时报告里输出的提示文本。这个要写成“可执行的建议”而不是一句评语比如“检测到空 catch 块建议至少记录一条异常日志”。priority不是属性而是一个子元素。它表示规则严重级别取值 1 到 51 是最严重。很多新手把 priority 当成属性写进rule priority3在 2.0.0 命名空间下这样写会直接解析失败。正确写法是独立的子标签rule nameAvoidEmptyCatch languagejava classnet.sourceforge.pmd.lang.rule.XPathRule message空catch块建议至少加一条日志 priority3/priority /rule如果你想引用 PMD 自带的 Java 规则class 属性会指向那个规则的具体实现类。常见做法是打开 PMD 自带的规则集 XML复制一条现有 rule 节点再改 name 和 message。这样做最稳因为你不用记类的全限定名PMD 自带规则集就是最好的模板库。2.3 property 与 properties参数怎么喂给规则实现很多规则不是写死的它们通过property子元素接收运行参数。例如一条限制方法行数的规则会有一个max或threshold属性。属性定义有两种一种写在properties容器里另一种直接作为rule的子元素。我推荐统一用properties包裹这样结构更清晰也方便在多个规则间复制。rule nameExcessiveMethodLength languagejava classnet.sourceforge.pmd.lang.java.rule.design.ExcessiveMethodLengthRule message方法过长请拆分子方法 priority3/priority properties property namemax value150/ property nameignoreComments valuetrue/ /properties /rule逻辑说明这里max150表示方法行数上限ignoreCommentstrue表示不计注释行。修改参数时先看规则文档再对照 XML 里 property 的类型。如果规则类是自定义的 Java 类property 会被反射方式灌入同名 setter 方法如果没找到对应 setterPMD 会抛配置异常这属于比较隐蔽的配置错误。常见的 property 类型有 int、boolean、String、甚至正则表达式。写布尔属性时只认true/false不要写1/0写数字属性时 value 必须是纯数字不能带单位。如果规则文档没有写清类型最快的方式是在 PMD Designer 里打开这条规则它会显示属性表单和默认值。2.4 规则集引用include、exclude、ref 组合出你的专属规则包大型项目里你不会只有一个规则文件。常见做法是编写一个“主入口 ruleset”通过include和exclude把其他规则文件或规则组合进来。例如主规则集只引用你需要的几个分类并排除掉不适合的某条规则ruleset nameMainRules xmlnshttp://pmd.sourceforge.net/ruleset/2.0.0 include pathcategory/java/design.xml/ exclude nameExcessiveMethodLength/ rule refcategory/java/errorprone.xml/AvoidAssertAsIdentifier/ /ruleset参数说明include的path是相对于 PMD 库内规则的路径。exclude的name要写被包含规则集里规则的name不是文件名。rule ref可以直接引用另一个 ruleset 文件甚至用ruleset.xml/规则名引用其中某一条。注意直接用rule ref引入规则时被引用的规则里的priority会被保留除非你在引用处覆盖。覆盖方式是在rule ref...内再写priority这个特性常用于统一团队告警级别。这个结构看起来简单但实际写起来碰到最多的就是路径和名字匹配问题。我遇过的情况是include 的 path 写错时 PMD 不报错只是静默跳过导致你以为规则生效了实际上扫描结果里一条相关告警都没有。所以引用外部规则集一定要先跑一次扫描并确认规则数量而不是“写完就当它生效了”。3. 手写一个可运行的规则文件从零到告警的最小路径3.1 准备一个最小 XML 骨架并放置在正确位置假设我们的目标是写一个规则文件检查 Java 代码中不允许出现System.out.println调用。这是一个非常典型的练习也是新团队最常用的“打招呼”规则。首先创建文件custom-rules.xml放到项目根目录或rules目录下。我建议放在rules/子目录这样和源码、CI 配置分开。骨架如下?xml version1.0 encodingUTF-8? ruleset nameNoPrintlnRules 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禁止在业务代码中使用System.out.println请使用统一定义好的Logger。/description rule nameNoSystemOutPrintln languagejava classnet.sourceforge.pmd.lang.rule.XPathRule message检测到System.out.println请使用Logger priority2/priority properties property namexpath value![CDATA[ //PrimaryExpression[PrimaryPrefix/Name[ImageSystem.out.println]] ]]/value /property /properties /rule /ruleset这里使用了 XPath 规则。//PrimaryExpression表示在 AST 中查找所有主表达式通过限定 PrimaryPrefix 的 Name 的 Image 等于System.out.println来匹配具体调用。CDATA 包裹是必须的因为 XPath 里会有[]等符号不包的话 XML 解析器会误判断。文件编码必须是 UTF-8尤其当 description 里写了中文时。直接用系统记事本另存为可能会带上 BOM 头PMD 解析时会把 BOM 当作字符导致根节点声明错误。我遇到过一次整个规则文件被加载失败报错信息却是“Premature end of file”排查了很久才想到是编码问题。3.2 理解 XPath 规则与 Java 规则的区别上面这种直接写 XPath 的规则优点是无需编译 Java 代码修改规则后立刻能扫缺点是比较难处理复杂的上下文逻辑比如“只有捕获了异常却不记录的 catch 块才告警”这种用 XPath 写会非常痛苦。PMD 内部把这两种规则分别叫作“XPath 规则”和“Java 规则”。如果是正经要做成产品级规范我建议优先用现成的 Java 规则类再不行就用 XPath 规则最后才考虑写自定义 Java 规则类。这里我补充一个选型理由XPath 规则适合“结构匹配”比如禁止某个类型的方法调用、禁止某种嵌套层级需要分析数据流、跨方法状态时XPath 就无解了必须写 Java 规则。作为团队规范的第一版XPath 规则能覆盖 80% 的“禁止某写法”诉求而且没有编译门槛改完 XML 再跑一遍 PMD 就能看到效果。如果你决定写自定义 Java 规则那你需要额外建一个 Java 工程编译出 jar 包再通过pmd命令的-auxclasspath参数把它加载进来。这会引入不少构建复杂度所以我建议新手不要一上来就写 Java 规则先把 XPath 规则玩熟再逐步过渡。3.3 在命令行和 Maven 里把规则文件跑起来有了规则文件下一步就是用 PMD 执行它。以命令行方式为例假设项目目录是src/main/java规则文件在rules/custom-rules.xmlpmd check -d src/main/java -R rules/custom-rules.xml -f text参数说明-d指定扫描目录-R指定规则文件路径可以是文件或目录-f指定输出格式text适合本地调试。如果pmd命令不在系统 PATH 里可以用完整路径。需要注意-R指向规则文件时PMD 会读取文件内所有规则如果你给了个目录则会加载目录下所有 XML。在 Maven 工程里通常通过maven-pmd-plugin插件执行。配置示例plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-pmd-plugin/artifactId configuration rulesets rulesetrules/custom-rules.xml/ruleset /rulesets includeTestsfalse/includeTests failOnViolationtrue/failOnViolation /configuration /plugin插件版本请用你项目里已验证过的版本不要随手抄一个最新版本号。failOnViolationtrue表示只要 PMD 发现违规Maven 构建就失败这是做门禁的基本姿势。includeTestsfalse表示不扫描测试目录因为测试代码里用 println 可能情有可原。3.4 用一段触发代码验证规则真的生效写完规则后必须验证。我们准备一个最简单的 Java 文件放在临时目录public class Demo { public void doSomething() { System.out.println(hello); } }执行上面命令行后预期输出里会有NoSystemOutPrintln的告警并且 message 内容是“检测到System.out.println请使用Logger”。如果没输出不要急着改代码先确认规则文件路径是否对、XML 是否合法。我一般会先执行pmd check -d src/main/java -R rules/custom-rules.xml -f text看有没有异常堆栈。如果规则文件有语法错误PMD 通常直接抛异常而如果规则文件合法但规则没匹配到输出为空。这时可以用一个已经知道会触发问题的代码去测试这就是最小验证。这个流程走通后我们才算真正拥有了一条自定义规则。下一步要考虑的是为什么我们写了 XPath 却不生效这里涉及 PMD 的 XPath 版本差异我在避坑章单独讲。4. PMD 规则文件避坑指南5 个让我翻车的细节4.1 坑 1language 属性写错规则静默失效现象规则文件加载正常扫描执行不报错但就是不出结果。原因PMD 根据language属性决定这条规则注册到哪个语言的规则引擎里。java写成了JAVA或者拼成jav都会被归入错误的语言。PMD 的语言代码是小写例如java、xml、ecmascript写错后规则虽然被加载但匹配不到任何 AST 节点。更坑的是PMD 在早期版本不会对 language 做严格校验静默失败。解决先确认你要扫描的源码语言然后在规则文件里显式写明languagejava。不要依赖目录名或文件后缀去推断因为规则文件是独立的它不会自动继承。排查时可以在同一个 ruleset 里临时加一条内置规则作对照看内置规则是否有输出。如果内置规则有输出而自定义规则没有大概率就是 language 或 XPath 的问题。4.2 坑 2XPath 版本差异导致规则行为不一致现象同一段 XPath 在 PMD 6.x 的某个版本里生效升级到新的主版本后突然失效或者报错。原因PMD 在大的主版本升级时会调整 AST 表示特别是方法调用相关的节点类型会有变化。旧版PrimaryExpression可能在新版中被拆成多个节点类型导致原来能匹配的 XPath 不再命中。解决先明确团队固定在哪个 PMD 主版本然后只参考对应版本的文档和内置规则。调试 XPath 时打开 PMD 自带的 Designer 工具粘贴触发代码查看当前版本真正的 AST 节点名称再据此调整表达式。我日常习惯是先在旧版环境验证规则再在目标版本跑一遍测试样例。如果团队里同时存在多个 PMD 大版本规则文件必须单独维护不要指望一份文件通吃所有版本。4.3 坑 3priority 范围搞混告警级别不生效现象规则能触发告警但严重级别没有出现在结果里或者 CI 过滤条件匹配不上。原因PMD 的priority取值是 1 到 5数字越小越严重。很多人按直觉认为 1 是低严重级别把普通规则写成 1导致 CI 认为所有文件都阻断构建反过来把该阻断的规则写成 5它又不会阻止合并。解决先定义团队的严重级别映射比如 priority 1-2 为阻断级3 为警告级4-5 为建议级并写进规则文件里的 description。再检查 XML 中priority3/priority是否被正确解析。注意如果你想在引用内置规则时覆盖 priority写法是rule refcategory/java/design.xml/ExcessiveMethodLength priority1/priority /rule这里 priority 是rule ref的子元素而不是属性。写错位置会导致覆盖不生效。4.4 坑 4外部规则集引用路径解析失败现象include pathcategory/java/design.xml/加载后扫描报告里只有零条规则或者报 “Resource not found” 异常。原因path属性的解析基础是 PMD 内部规则目录而不是你的项目文件系统。直接写pathrules/design.xml会找不到。同样的exclude的 name 必须和规则定义里的 name 完全一致大小写、空格都不能差否则排除不了。解决include 这种机制更多用于“包含 PMD 自带分类规则集”。如果想引用项目内另一个规则文件应该使用rule refrules/other.xml形式或者用file:前缀指向绝对路径。我习惯把所有自定义规则放到同一个文件里减少跨文件引用。如果你有很多规则需要拆分建议用rule ref而不是include因为 ref 的路径相对当前规则文件而 include 相对 PMD 的类路径。4.5 坑 5property 类型配置错误规则启动即失败现象PMD 启动时报错Cannot set property max或者NumberFormatException。原因规则实现类定义了 int 类型的 setterXML 里 property 的 value 却是空字符串或非数字。另一个常见情况是布尔属性写成true和1PMD 只认true/false字符串写1会解析失败。解决查阅规则文档确认属性类型。遇到诡异错误时把 property 写完整包括value子元素形式因为部分规则需要的是元素内容而不是value属性值。两种写法如下property namemax value10/ !-- 等价写法 -- property namemax value10/value /property这两种写法在绝大多数情况下可互换但某些自定义规则或旧版规则只实现了对其中一种的解析。搞不定的时候换成另一种试试能节省半天排查时间。如果你用的是自定义 Java 规则类property 名称会映射到该类的 setter 方法。例如property namemax value10/会去找setMax(int)方法。如果类里没有这个方法PMD 会直接抛异常。所以自定义规则时property 的 name 必须与 setter 命名严格一致这是最容易忽略的细节。5. 把规则文件变成团队规范增量落地与自检技巧5.1 用“基线法”控制规则文件引入的告警量直接全量开启一批新规则团队基本会炸。我采用“基线法”先在目标项目上跑一遍新规则文件把现有告警全部记录为基线快照之后只允许新增告警出现。具体做法是第一轮扫描结果存档为基线文件CI 中对比当前输出与基线只对超出的部分报错。规则文件本身在这种模式下很稳定团队能逐步消化历史技术债而不是一次性爆发几千条 warning。5.2 为规则文件单独建立测试用例规则文件也是代码需要测试。常见的做法是用 PMD 自带的扩展测试框架但如果你不想引入额外依赖可以准备一组小 Java 文件分别命名为“触发”和“不触发”然后用脚本断言扫描结果的差异。例如下面的 bash 脚本pmd check -d ./trigger -R rules/custom-rules.xml -f text | grep -q NoSystemOutPrintln echo TRIGGER PASS pmd check -d ./not_trigger -R rules/custom-rules.xml -f text | grep -q NoSystemOutPrintln echo TRIGGER FAIL注意这不是为了自动化而自动化而是防止你改了一个规则的 XPath 后把另一个不该触发的情况放大。给规则文件配套测试是我维持稳定规则集的关键习惯。当然如果团队规模不大也可以用小型 CI job 来跑这套脚本不必上完整单测框架。5.3 用 PMD Designer 快速调试 XPath 规则如果你手里只有规则文件没有复杂调试环境可以启动 PMD 自带的 Designer 组件。它的作用是粘贴一段 Java 代码查看该代码对应的 AST 树再编写和验证 XPath 表达式。这比“改了扫描再翻报告”快得多。我通常在处理一个复杂规则前先用 Designer 复制触发代码确认 AST 节点名称再写 XPath最后贴回规则文件。这一步能避开不少前面提到的 XPath 版本坑。另外规则文件会随着时间膨胀我建议每个规则节点都写清description包含这条规则的业务背景。这样多人维护时不会出现“看了 message 不知道这条规则在干嘛”的情况。规则文件本身不值得追求大而全我见过维护了上千条规则的团队最终大部分被 disable不如用几十条核心规则配合基线机制来得有效。最后说一个我的教训以前为了追求“零告警”的指标我把规则文件里的 priority 都改成了 4结果整个团队开始无视告警。后来我重新把 priority 调回真实严重级别并用基线法冻结历史告警才让规则文件重新被信任。规则文件不是死的清单它是你和团队关于代码质量的共识。希望帮到你。本文还有配套的精品资源点击获取