简介:PMD(Poor Man's Dynamic Code Analyzer)规则文件是面向Java开发者的静态代码检查配置包,用于在Eclipse等环境中自定义编码规范、识别潜在bug与冗余代码。压缩包内共10个文件,以9个XML规则集文件为主,涵盖basic、design、codesize、imports、empty、finalizers、unusedcode等典型类别,并附1个说明文档,整体约35KB,便于直接导入PMD插件使用。规则文件详细定义了规则集、规则、类别、参数与排除项,开发者可按项目需要调整阈值或忽略特定文件,实现精细化检查。已有1056人学习下载,适合希望提升代码质量、规范团队编码风格的Java工程师及PMD入门者。通过对规则结构的解析,读者可理解每条检测项的触发条件与配置方法,快速搭建符合自身项目的PMD规则体系。
1. PMD的规则文件是什么:CI 里那份决定扫描结果的 XML
接手一个老项目时,最让我头疼的不是业务代码,而是 CI 上那台 PMD 扫描机——它每天报几十条问题,但真正该改的只有两条,其余全是噪音。查到最后发现,问题不在代码,在 PMD 的规则文件。PMD 的规则文件,就是用 XML 描述“扫描器检查什么、怎么检查、违规时提示什么”的清单,它直接决定一次静态检查是精准命中团队规范,还是把内置规则全量扫一遍让报警滚成雪球。适合被 PMD 误报烦过、想自定义检查项、或想在团队里统一静态检查口径的开发者读。下面直接从规则文件本身的结构拆起。
2. 规则文件的结构解剖:ruleset.xml 的骨架与内置规则的引用
2.1 一份最小可用的 ruleset.xml:先看懂根节点与 rule
PMD 的规则文件也叫 ruleset 文件,约定俗成命名成 ruleset.xml。它不是一个简单的开关列表,而是把所有检查规则组织成一棵树:根节点是<ruleset>,下面每个<rule>是一条检查规则。新建一个最小规则文件,内容是这样:
<?xml version="1.0" encoding="UTF-8"?> <ruleset name="team-rules" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"> <description>团队自定义规则集</description> <rule name="NoSystemOut" message="不要直接用 System.out 输出,统一走 logger" class="net.sourceforge.pmd.lang.rule.XPathRule" language="java"> <priority>3</priority> <properties> <property name="xpath"> <value> //PrimaryPrefix/Name[@Image = 'System.out.println'] </value> </property> </properties> <description>检查 System.out.println 调用</description> </rule> </ruleset>这段 XML 里,<rule>有四个关键属性。name是这条规则在报告里的名字;message是违规时展示给开发的话,写的时候要写“该怎么改”,而不是“你错了”;class指定规则执行器,这里用的是XPathRule,意思是“用 XPath 表达式去 AST 上找节点”;language告诉解析器按什么语言的语法树来跑,XPath 规则不写 language 经常出现“规则加载成功但一匹配就零结果”的玄学问题。
<priority>是严重级别,取值 1 到 5,数字越小越严重,这直接决定后续 Maven/Gradle 集成时“哪些违规要拦断构建”。而<property name="xpath">里的 value 是真正干活的表达式,它匹配 PMD 解析 Java 代码后生成的抽象语法树节点,命中一个节点就报一条违规。可能有读者会问:为什么是PrimaryPrefix/Name这种路径?因为 PMD 内部对 Java AST 的定义就是这样,第 6 章会讲怎么用官方 Designer 查这个路径。
2.2 ref 引用内置规则:category 路径与属性覆盖
实际项目里很少把所有规则手写出来,更多是“先引用 PMD 自带规则,再微调参数”。引用方式是用 ref 属性,指定内置规则集文件里的某一条,或者整包规则集:
<rule ref="category/java/bestpractices.xml/AvoidMagicNumbers"> <properties> <property name="ignoreNumbers" value="-1,0,1,2" /> </properties> </rule>这里的 ref 路径分三段:第一段category/java是规则集所属分类,PMD 6.x 把内置规则按 bestpractices、errorprone、performance 等分类目录拆开;第二段bestpractices.xml是具体规则集文件;第三段AvoidMagicNumbers是规则的名字。注意路径分隔是斜杠,规则名与规则文件之间也是斜杠。很多人从旧版本迁移时把路径写成rulesets/java/basic.xml这种老格式,在新版 PMD 上直接加载失败。
ref 后面可以跟<properties>覆盖默认参数。AvoidMagicNumbers默认忽略 -1、0、1 这些魔法数,把ignoreNumbers改成-1,0,1,2就能让数字 2 也放行。要查内置规则有哪些可调参数,最可靠的做法是打开 PMD 发行包里对应的规则定义 xml,属性名对不上时配置会被静默忽略而不是报错——这是后面要讲的一个大坑。
有的团队会直接写<rule ref="category/java/errorprone.xml"/>把整个规则集拉进来,省事但代价很大:一个分类下可能有几十条规则,每条还带自己的默认参数,结果 CI 报警量一夜之间翻倍。我一般建议按需引用单条规则,让规则文件本身成为团队规范的“白名单”。
2.3 规则文件放哪:工程目录约定与资源加载
规则文件在工程里的摆放位置没有强制规定,但不同构建工具加载它的方式不同。常见做法是放在模块根目录下的config/pmd/ruleset.xml,或者src/main/resources/rulesets/custom.xml。前者用相对路径引用,后者可以直接作为 classpath 资源被 Maven 插件加载。两种都行,关键是保持路径稳定,不要放在个人电脑的绝对路径下,否则换人接手构建就挂。
规则文件里还可以控制“哪些文件参与检查”,通过 exclude-pattern 过滤:
<exclude-pattern>.*/generated/.*</exclude-pattern> <exclude-pattern>.*/build/.*</exclude-pattern>这个正则匹配的是以正斜杠分割的完整文件路径,不管 Windows 还是 Linux,PMD 内部都按正斜杠处理。常见误用是写成反斜杠,结果排除不生效。另一个注意点:exclude-pattern 只对当前规则集有效,如果你引用了一整个规则集,就得在对应节点里分别加 exclude,或者用构建工具层面的排除列表,两者混用时要先想清楚以哪个为准。
3. 写一条自定义检查规则:XPath 规则从 0 到 1
3.1 场景与选型:为什么第一首选 XPathRule 而不是写 Java 规则
PMD 提供两种自定义规则的方式:XPathRule 和 Java 规则。前者只需要在 XML 里写一条 XPath 表达式,类名写net.sourceforge.pmd.lang.rule.XPathRule;后者要继承AbstractJavaRule或AbstractRule,在 visit 方法里操作 AST 节点,最终打成 jar 放进 PMD 的 classpath。团队里没有专门去做工具开发的,第一首选 XPathRule。
为什么?因为团队里 90% 的自定义检查需求都是“语法结构级”的:禁止调用某个类的方法、禁止某种 try-catch 写法、强制某些语句的形态。这些需求用 XPath 直接在 AST 上找节点就能实现,改一条表达式不用重新编译,可以直接在 CI 上迭代。而 Java 规则适合要跨方法分析、要类型信息、要做控制流分析的场景,比如“判断一个方法是否调用过某个 API 且没有释放资源”,这类需求 XPath 做不了。
选型还有一个参考维度:规则的报错信息要不要带变量上下文。XPath 规则只能在 message 里写固定文案,最多把匹配节点的 image 带出来;Java 规则可以读几个节点的属性拼接提示。我的经验是:先看检查项能不能用一条 XPath 描述清楚,能就别上 Java。
3.2 完整规则文件:从 XML 到命令行跑通
拿最常用的“禁止 System.out.println”来说,把第 2 章的规则文件存成config/pmd/ruleset.xml,然后命令行先本地验证:
pmd check -d ./src/main/java -R ./config/pmd/ruleset.xml -f text如果用的是 PMD 6.x,启动命令是bin/run.sh pmd(Windows 是 pmd.bat),PMD 7 之后统一为pmd check子命令。逐个参数拆开说:-d或--dir指向要检查的源码目录或单个文件;-R或--rulesets指向规则文件,可以写多次加载多个规则集;-f或--format是输出格式,text 适合人看,想进报表可以换成 csv、xml、html。
在命令行跑通的意义不只是验证规则本身,还验证了规则文件路径和 PMD 版本是否匹配。如果输出为空,说明规则没命中;如果 PMD 直接报规则文件 invalid,说明 XML 结构或路径有问题。我会先拿一个故意违规的样例来验证:建一个只有System.out.println的 Test.java,再把-d指到它,看到一条违规输出就说明规则生效了。
pmd check -d ./Test.java -R ./config/pmd/ruleset.xml -f text这一步也顺便验证了-d支持文件级输入,很多团队在做增量扫描时会用到这个特性。
3.3 三个必调参数:priority、message、language 的选择
自定义规则有三个参数会直接影响团队使用体验。
第一个是priority。取值 1 到 5,1 最严重。CI 集成时通过minimumPriority拦断级别来控制“什么违规必须修”。比如设成 3,priority 为 1、2、3 的违规会报出来,4 和 5 的只记录不拦。不少人以为 priority 越大越严重,方向反了,结果把严重问题设成 5,CI 上不拦,等于没写。
第二个是message。这条给的是报错时开发者看到的话。它要直接说清楚:这里不能这么写,应该怎么改。差的 message 是“Bad practice”,好的 message 是“请用 log.info 替代 System.out.println,避免生产日志被 stdout 污染”。一条规则值不值得保留,一半看 message 写得好不好。
第三个是language。XPathRule 必须明确告诉 PMD 按什么语法解析。不写 language 时规则能通过加载,但 AST 上匹配永远为空,很难排查。另外 language 与代码扩展名要对应:Java 写 java,XML 写 xml,JSP 写 jsp。混语言项目里每条规则都要单独指定 language,不能靠全局默认值。
写 XPath 表达式还有个细节:value 里的表达式如果包含<或&这类 XML 特殊字符,会被解析器误读。首先表达式尽量改写法避开,实在避不开就用 CDATA 包起来。这个坑遇到一次就能让你在 CI 日志里对着乱码排查半小时。
4. 把规则文件接到构建流程:命令行、Maven、Gradle 三路落地
4.1 命令行先试跑:pmd check 的参数怎么给
接入 CI 之前,先把规则文件在命令行里跑熟。除了-d、-R、-f,常用的还有--aux-classpath:当规则需要解析第三方类型时,把依赖 jar 的路径传进去。--fail-on-violation控制有无违规时的退出码:有这个参数时,发现违规退出码非 0,CI 可以直接拿退出码判断“这次过没过”。
一个完整的试跑命令:
pmd check \ -d ./src/main/java \ -R ./config/pmd/ruleset.xml \ -f text \ --fail-on-violation-f text的结果是纯文本,每行一条违规。如果后续要接报表工具,把-f改成 xml 或 sarif,但格式要以当前 PMD 版本支持的为准。命令行试跑还有一个价值:确认规则文件里 ref 的内置规则路径在当前 PMD 版本下有效,路径写错时 check 大概率直接报错,这比进了 CI 再炸好处理得多。
4.2 Maven 插件接入:rulesets 与 failOnViolation
Maven 项目用 maven-pmd-plugin,配置上核心就两件事:指定规则文件、指定是否拦断构建。一个可抄的配置:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-pmd-plugin</artifactId> <version>3.21.2</version> <configuration> <rulesets> <ruleset>config/pmd/ruleset.xml</ruleset> </rulesets> <failOnViolation>true</failOnViolation> <printFailingErrors>true</printFailingErrors> <minimumPriority>3</minimumPriority> <includeTests>false</includeTests> </configuration> <executions> <execution> <goals> <goal>check</goal> </goals> </execution> </executions> </plugin>说明几个关键点。rulesets下可以写多个规则文件,路径相对于模块根目录;failOnViolation设为 true 表示有违规就让构建失败;printFailingErrors让 Maven 在失败时把违规明细打到输出里,否则还得翻 report;minimumPriority设成 3,只拦 priority 1 到 3;includeTests控制是否检查src/test/java,一般单测不建议套同样的规则。
注意一个容易踩的点:maven-pmd-plugin 的 check goal 默认绑定在 verify 阶段。如果只想本地手动跑,不绑定执行就行;如果想每次构建都拦,务必声明 execution。另外规则文件路径如果找不到,插件是跳过还是报错取决于插件版本,别赌,用上文命令先试跑一次确认路径能读到。
4.3 Gradle 插件接入:ruleSets 清空与 ruleSetFiles
Gradle 项目用官方 pmd 插件,写法更紧凑:
plugins { id 'java' id 'pmd' } pmd { toolVersion = '7.5.0' ruleSetFiles = files('config/pmd/ruleset.xml') ruleSets = [] sourceSets = [sourceSets.main] ignoreFailures = false maxFailures = 0 consoleOutput = true } tasks.withType(Pmd) { reports { xml.required = true html.required = true } }这里最关键的是ruleSets = []。Gradle 的 pmd 插件默认会把内置的rulesets/java/basic.xml等一批规则集塞进来,如果不清空,你自己的规则文件会跟默认规则叠加,CI 立刻多出一堆你没评审过的报警。ruleSetFiles指向项目里的自定义规则文件,路径相对于项目根目录。
toolVersion是 PMD 引擎版本,要和规则文件写法匹配。maxFailures设为 0,表示一个违规都不容忍;ignoreFailures设为 false,违规时让构建失败。consoleOutput打开后,Gradle 控制台直接显示违规明细,不用翻build/reports/pmd/下的 HTML。
Gradle 插件有一点和 Maven 不同:pmd 任务的源码集默认包含 test。不想检查测试代码,在sourceSets里只留 main,或者在配置里排除。我的做法是保留默认,因为单测里也经常有 System.out,把这些漏掉等于白设规则。
4.4 规则文件是共享资产:版本管理里怎么放
无论选哪条路,规则文件都该进 Git 仓库,跟被检查代码同一个工程,走同样的 review。不要只存在某个同事的 IDE 配置里,也不要把规则文件放在 CI 机器上的固定路径——换一台机器就丢。
规则文件的变更要当代码变更看:每次加规则,在 MR 里附上样例代码和预期报错,方便 review 的人验证。另外,规则文件里 ref 的内置规则路径和 PMD 版本强绑定,升级 PMD 时规则文件要一并验证,不要只升引擎不测规则集。
如果你所在公司要求全局统一规范、每个项目不许自己改,常见做法是把规则文件抽成独立共享配置仓,CI 里的插件先从仓库拉取规则文件再跑 pmd check。但有个前提:共享配置的 PMD 版本和落地项目一致,否则规则集加载行为会漂。团队规模小、迭代快的阶段,规则文件留在代码仓库里最省事;等规范成熟、跨团队推广时再抽共享仓,不要过早搞基建。
5. 规则文件使用避坑:5 个最常见的翻车现场
5.1 规则写了不生效:先查 ref 路径和规则名
现象:规则文件在命令行下能加载,但扫描结果里完全没有新规则的那类违规。
原因:最常见的是 ref 路径写错。PMD 6.x 开始内置规则按 category 组织,网上很多老教程里的rulesets/java/basic.xml路径在新版已失效,ref 到不存在的路径时 PMD 会报规则文件无效;而 ref 的规则名拼写错时,PMD 常常只给一个警告,扫描照常跑,结果你想查的规则根本没参与。
解决:在命令行加-R试跑,先用一个极小的样例类确认规则能命中;还不行就打开 PMD 自带的设计器加载规则文件,它会列出实际加载的规则列表。另外,ref 单个规则时名字大小写敏感,AvoidMagicNumbers写成avoidMagicNumbers是匹配不到的。
5.2 XPath 匹配不到代码:language 与属性名玄学
现象:自定义规则加载成功,执行完 0 条违规,但代码里明明有System.out.println。
原因:我见过最多的是language属性缺失,其次是 XPath 表达式里的属性名写错。Java AST 的 Name 节点属性是Image,必须写@Image而不是@image或@Name;PMD 7 里 XPath 版本从 1.0 切到 2.0 后,部分旧写法也会失效。这种问题最坑的地方是 PMD 不会报错,只默默返回空结果。
解决:先在命令行用单文件验证,再用 Designer 加载同一个样例,把 AST 节点逐个展开,确认你要匹配的属性名到底叫什么。表达式写出来后在 Designer 里实时测试,而不是一遍遍跑完整扫描。
5.3 exclude 排除失灵:同名规则在不同规则集里的干扰
现象:在自定义规则集里写了<exclude name="SomeRule"/>,但扫描时这条规则依然在报。
原因:exclude 的语义是排除“当前规则集里由 ref 引入的同名规则”。如果规则不是从你当前 ref 链上进来的,而是另一个被引用规则集里自带的,exclude 就管不到它。常见于团队把多个规则文件叠加复用,两个文件里都引用了同一分类下的规则。
解决:exclude 时写完整来源,比如<exclude name="category/java/errorprone.xml/SomeRule"/>。如果你引用整个 category 文件,里面不想用的规则逐条 exclude,路径格式和 ref 保持一致。写完后看扫描结果明细,确认那条规则真的不在列表里。
5.4 属性覆盖没生效:property 的 name 和 value 类型
现象:按文档给内置规则覆盖属性,比如ignoreNumbers,运行时表现跟没设一样。
原因:属性名大小写写错,或属性类型对不上。PMD 的属性定义在规则 xml 里,属性名严格区分大小写;value 在 XML 里始终是字符串,但规则内部会按定义类型转换,布尔值要写 true/false,枚举值要写合法枚举名,写错时 PMD 静默忽略。
解决:先到 PMD 发行包中找到对应规则的定义文件,确认属性名和可选值,再回来改。改完不要只看最终违规数,用一个应被放行的样例验证。属性覆盖这事最怕“看起来配了,实际没匹配上”。
5.5 升级 PMD 后面板变样:规则文件弹错先看 namespace
现象:项目里的 PMD 从 6.x 升到 7.x 后,CI 直接报规则文件加载失败,错误信息指向规则文件第一行。
原因:PMD 7 更新了规则文件的命名空间和部分规则实现,6.x 的 ruleset.xml 头部 namespace 不再被识别,同时 XPath 规则默认版本变了,部分表达式在 2.0 下解析失败。
解决:这类升级没有黑匣子可赌,先看官方发行说明的迁移章节,把规则文件头部 namespace 换成新版本,再逐个跑自定义 XPath 表达式。表达式兼容性是升级里最耗时间的,建议升级前给每条自定义规则配一个“必命中样例”,升级后批量回归。
6. 进阶技巧:用 PMD Designer 调 XPath,把规则文件做“活”
6.1 用 Designer 验证一条 XPath 规则
规则文件写到后期,卡点基本都在 XPath 表达式上。与其对着 AST 文档硬猜节点名,不如用 PMD 自带的 Designer 把样例代码的语法树可视化。启动方式很直接:拿到 PMD 发行包后在 bin 目录下找 designer 脚本,Windows 是 designer.bat,类 Unix 是 designer.sh,打开就是图形界面。
用 Designer 调规则的固定套路是:左侧贴一段故意违规的样例代码,点解析,中间出现完整 AST 树;想找System.out.println就展开 PrimaryExpression 节点,一路点到 Name,看到右侧属性面板里有Image='System.out.println'。然后把表达式写到 XPath 输入框,点执行,样例代码里命中的节点会高亮。表达式写对了,再粘回规则文件的 value 里,整个过程几分钟。
值得记住的小技巧:写 XPath 时不要从头拼整条路径,先找目标节点的局部路径,比如//Name,再逐步缩小筛选条件,这样能快速定位到最稳定的表达式。PMD 7 里 XPath 默认版本变了,Designer 也跟着变,所以界面上测出的结果就是 CI 里的结果,不存在“本地能匹配、CI 空白”的版本差。
我现在的习惯是:每条自定义规则都配一个刻意违规的样例文件,放在 test 资源里,规则文件改完就跑一次单文件扫描,确认还能命中;升级 PMD 版本时也靠这套样例做回归。静态检查规则的维护核心不是写,而是反复验证,Designer 就是那个把验证时间从半小时压到三分钟的东西。希望帮到你。
本文还有配套的精品资源,点击获取