ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

PMD规则文件完全指南:从ruleset.xml到自定义规则实战

PMD规则文件完全指南:从ruleset.xml到自定义规则实战 简介PMD是一款开源的Java静态代码分析工具这份压缩包提供了其核心的规则配置XML文件面向需要在Eclipse等IDE中开展代码质量检查的Java开发者。规则集按设计、代码规模、空值处理、导入规范、finalizers、未使用代码、基础规范等类别划分覆盖了从潜在Bug、冗余代码到可读性问题的常见检查场景可通过PMD插件自定义导入并灵活调整参数与排除项。包内共10个文件以9个XML规则文件和1个说明TXT为主整体仅35KB结构清晰、便于直接复用。通过这套规则文件开发者可以快速建立项目级的代码审查标准实时定位违反规范的位置也可作为自定义PMD规则的参考模板让静态检查更好地贴合团队编码约定。目前已有1056人学习下载适合希望提升代码质量、落地持续集成的Java开发者。1. 规则文件才是 PMD 的“灵魂”别只盯着官方规则列表给 Java 项目上静态检查多数人的第一步是打开 PMD 官方规则列表勾勾选选。真正负责落地的工程师动手改的第一样东西却是 PMD 的规则文件ruleset.xml。同样一套 PMD默认扫描能报出一堆不痛不痒的命名建议而一份贴合项目的规则文件可以只留下空 catch、高危魔法数字、System.out 这类 code review 真正会拦截的问题十几条规则就能让整个团队的提交质量上一个台阶。规则文件本质是“检查白名单 阈值表”决定 PMD 这个黑匣子吐出来的是噪音还是炮弹。想固化团队规范的人把它当制度文件维护想搞懂规则的人靠它反推一条检查到底靠什么触发。这篇就把规则文件从头拆到尾。2. 拆开一个 ruleset.xml头信息、rule 元素与 properties 三层结构2.1 根节点、namespace 与 description规则集的身份信息任何一个 PMD 规则文件都是 XML根节点固定为ruleset。见过不少同事直接复制网上的片段把第一行缩进都改了结果 PMD 启动就报 schema 解析失败。头三行不是摆设xmlnshttp://pmd.sourceforge.net/ruleset/2.0.0这个 namespace 决定了 PMD 用哪一代规则集语法来解析文件。PMD 6 和 PMD 7 都兼容这个 2.0.0 命名空间但内部字段的宽松程度不同这一点在第 5 章会专门讲。?xml version1.0 encodingUTF-8? ruleset nameCompany-Java-Core 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.io/ruleset_2_0_0.xsd description 核心规范只保留对上线有影响的检查项命名类问题一律压低优先级。 /description /rulesetname是规则集的对外名字在 CI 报告和日志里会出现建议用“公司/小组 语言 场景”的命名比如Company-Java-Core。description写清楚这套规则的定位它会在 PMD Designer 和 HTML 报告里展示团队新成员先看这段就能理解为什么要有这套规则。schemaLocation里的 URL 只是给 IDE 做校验提示用的PMD 运行时不联网拉它所以别因为内网环境去删除这行。2.2 rule 元素六个字段各管什么事规则集的核心单位是rule。一个我维护了两年多的最小规则文件里只放了一条规则也把六个关键字段都占满了rule nameNoSystemOut languagejava message不要直接使用 System.out / System.err请走 SLF4J classnet.sourceforge.pmd.lang.rule.XPathRule priority2/priority properties property namexpath value //PrimaryExpression[PrimaryPrefix/Name[ImageSystem.out or ImageSystem.err]] /value /property /properties example ![CDATA[ System.out.println(debug: x); // 违规 log.info(debug: {}, x); // 符合规范 ]] /example /rule各字段的职责拆开看字段作用常见取值name规则唯一 IDSuppressWarnings和报告里都用它NoSystemOutmessage违规时输出的提示语支持{0}占位符不要使用 System.outclass规则的实现类决定这条规则怎么扫描net.sourceforge.pmd.lang.rule.XPathRule或自定义类的全限定名language规则作用于哪种语言PMD 7 起必填java/xml/plsql/apexpriority严重程度1 最高、5 最低默认是 51~5externalInfoUrl团队规范 wiki 的链接报告里会导出公司 Confluence 地址name要当代码里的标识符来取别用中文也别带空格。message是给人看的写清楚“为什么不行 替代方案”比 PMD 默认的英文消息有用得多。class若是net.sourceforge.pmd.lang.rule.XPathRule说明这条规则靠 XPath 表达式匹配 AST 节点若是自定义 Java 类就是走访问者模式扫描两种形态在第 4 章展开。priority不是随便填的——CI 里通常只对 1、2 级规则 fail 构建3 级以下只出报告。2.3 properties规则的参数区配置最容易出错的地方properties是规则文件真正值钱的部分。官方规则大多带可调参数比如AvoidUsingHardCodedIP有ignoreNetmaskEmptyCatchBlock有allowCommentedBlocks。自定义 XPath 规则的xpath属性本质也是一个 property。引用官方规则并覆盖参数是定制规则文件最常见的动作rule refcategory/java/bestpractices.xml/AvoidUsingHardCodedIP message禁止硬编码 IP测试环境请走配置中心 priority1/priority properties property nameignoreNetmask valuefalse / /properties /ruleref指向官方规则集里的规则 ID格式是“分类路径/规则名”。PMD 6 之后规则按category/java/...组织不要再用老教程里的rulesets/java/...路径后者在 PMD 7 里已经失效。覆写参数时注意property 是“整表替换”不是增量修改——如果你只想改一个值最好把原规则在ruleset.xml里用ref引用后把要动的属性完整写出来。同一套规则文件里经常出现“同一规则两套阈值”的需求Web 项目允许方法 5 个参数核心账务模块只允许 3 个。做法是建两个ruleset.xml各自ref同一规则并填不同properties在模块的 pom 或 gradle 里分别引用。规则文件这时候就是配置文件路径、命名、参数都该走配置管理的评审流程。3. 从零写一个能跑的最小规则文件先打通验证链路再谈规则3.1 最小规则集只放一条规则验证文件本身没问题我搭任何新规则文件的习惯是先写一条必然命中的规则确认 PMD 能读文件、能匹配、能输出再往里面加业务规则。这条“冒险规则”用//CompilationUnit这种匹配任意 Java 文件的 XPath只要 PMD 正常解析它必报。?xml version1.0 encodingUTF-8? ruleset nameCanary-Ruleset 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.io/ruleset_2_0_0.xsd description验证 PMD 管线是否正常的探针规则集/description rule namePipelineCanary languagejava message规则链路正常这条必然触发 classnet.sourceforge.pmd.lang.rule.XPathRule priority5/priority properties property namexpath value //CompilationUnit /value /property /properties /rule /ruleset//CompilationUnit在 PMD 6 和 PMD 7 里都匹配任何 Java 源文件的根节点所以它一定会触发。文件保存为rulesets/canary.xml路径自己定但建议所有规则文件统一放源码库的rulesets/目录跟随项目走版本管理。3.2 用 pmd check 命令当场验证命令和输出怎么看规则文件是文本配错了不会立刻炸要跑一次才见分晓。PMD 6.29 之后的命令行子命令是pmd check老写法的pmd -R xxx -d xxx在新版本已废弃pmd check -R rulesets/canary.xml -d src/main/java -f text参数含义-R指定规则文件路径可以逗号分隔多个-d指定被扫描的目录或单个文件-f text是纯文本输出格式。如果没配--no-cachePMD 会在同目录生成.pmd缓存第二次扫描只增量检查变化文件调试规则时最容易踩这个缓存坑——改完规则重新跑结果还是老的。调试阶段我习惯加一个参数pmd check -R rulesets/canary.xml -d src/main/java -f text --no-cache正常输出长这样src/main/java/App.java:3: PipelineCanary: 规则链路正常这条必然触发格式是“文件:行号:规则名:消息”。看到这一行说明规则文件被正确加载、AST 解析成功、输出通道工作正常。如果这条探针规则没报问题一定出在规则文件或命令行而不是业务代码。3.3 挂进 Maven 和 Gradle让 CI 替你跑规则文件本地验证通过后规则文件要在构建链路里生效。Maven 项目用maven-pmd-plugin配置里最容易漏的是rulesets标签不写——不写就只跑官方默认规则集你的自定义规则一条都不会执行plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-pmd-plugin/artifactId version3.21.2/version configuration rulesets rulesetrulesets/canary.xml/ruleset rulesetrulesets/company-core.xml/ruleset /rulesets includeTestsfalse/includeTests failOnViolationfalse/failOnViolation /configuration executions execution goals goalcheck/goal /goals /execution /executions /pluginGradle 侧结构类似但有个坑Gradle 自带默认规则集如果你只想跑自己的规则文件必须把ruleSets清空pmd { ruleSetFiles files(rulesets/company-core.xml) ruleSets [] toolVersion 6.55.0 ignoreFailures false }ignoreFailures false表示违规即构建失败。第 6 章会讲渐进式放量新规则刚上线时这里应该设true只出报告不阻断。版本号建议锁定Gradle 的toolVersion不写默认用内置老 PMDMaven 插件默认带的 PMD 版本也偏旧团队统一锁版本才能避免“本地报、CI 不报”的玄学问题。4. 规则文件的两种实现形态XPath 表达式还是 Java 类4.1 XPath 规则适合“看结构”的检查改起来最快规则文件里class写成XPathRule时规则体就是一段 XPath 表达式。PMD 把源码解析成 AST 语法树XPath 在树上做模式匹配。上一章的NoSystemOut就是一个典型//PrimaryExpression定位所有表达式节点PrimaryPrefix/Name[ImageSystem.out]限定节点内容。结构类检查用 XPath 性价比极高比如“禁止单字符字段名”rule nameSingleCharFieldName languagejava message字段名不能是单个字符请用有业务含义的名字 classnet.sourceforge.pmd.lang.rule.XPathRule priority3/priority properties property namexpath value //FieldDeclaration/VariableDeclaratorId[string-length(Image) 1] /value /property /properties /rule表达式拆开看//FieldDeclaration匹配所有字段声明VariableDeclaratorId拿到变量名节点string-length(Image) 1过滤出名字长度为 1 的情况。写 XPath 规则最怕“凭直觉写”强烈建议用 PMD Designer 工具。Designer 能加载你的测试代码实时展开 AST 树左侧写 XPath、右侧即时高亮匹配节点比一遍遍跑pmd check调试快一个量级。4.2 Java 规则类适合需要上下文和数据流的检查XPath 的短板是“看不懂上下文”。比如“魔法数字”规则字面量60是违规常量但static final int SECONDS_PER_MINUTE 60里的60又是合法的——XPath 很难区分这两种场景Java 规则类可以。自定义规则类继承AbstractJavaRule重写对应节点的visit方法package com.company.pmd.rules; import net.sourceforge.pmd.lang.java.ast.ASTLiteral; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; /** * 自定义规则检测方法体内的魔法数字。 * PMD 6 的 visit 返回 ObjectPMD 7 改成 Void升级时注意签名。 */ public class AvoidMagicNumberRule extends AbstractJavaRule { Override public Object visit(ASTLiteral node, Object data) { // 只处理数值字面量字符串和字符不管 if (node.isIntLiteral() || node.isLongLiteral() || node.isDoubleLiteral()) { // 跳过 0 和 1这两个数字出现太频繁误报会很高 String image node.getImage(); if (0.equals(image) || 1.equals(image)) { return data; } addViolationWithMessage(data, node, 魔法数字 image 应提取为具名常量); } return data; } }这个类只做了两层判断先认字面量类型再跳过0和1。实际落地时你还需要判断“当前节点是否在静态常量初始化里”“是否在注解参数里”那些要访问父节点链ASTLiteral的jjtGetParent()一路往上找。规则类写好后在规则文件里注册成普通rule就行rule nameAvoidMagicNumber languagejava message魔法数字应提取为具名常量 classcom.company.pmd.rules.AvoidMagicNumberRule priority3/priority example ![CDATA[ int total count * 60; // 违规60 是魔法数字 int total count * SECONDS_PER_MINUTE; // 符合规范 ]] /example /ruleclass写全限定名规则类的 jar 要打进 PMD 的 classpath。Maven 项目里把这个模块打成依赖在maven-pmd-plugin的dependency里引入Gradle 里pmd配置块加dependencies { pmd project(:pmd-rules) }。4.3 抑制机制NOPMD 和 SuppressWarnings 的边界规则文件定得再细总有“这行我就想这么写”的场景。PMD 给两条后门但边界要讲清楚。// NOPMD是行级注释必须放在违规行末尾int retry 3; // NOPMD - 重试次数固定为 3不需要配置化SuppressWarnings(PMD.RuleName)是类级或方法级抑制作用范围是整个代码块SuppressWarnings(PMD.AvoidMagicNumber) public int legacyCompute() { return 86400; // 历史接口已冻结不做重构 }注意注解里的规则名必须和rule name完全一致。误用抑制是规则文件执行效果变差的第一杀手我的经验是// NOPMD必须带一句原因SuppressWarnings必须走 code review否则下个季度回头看满屏都是抑制注释规则形同虚设。5. 规则文件排查五次“规则不生效”的翻车记录5.1 Maven 里配了规则文件CI 却跑的是官方默认规则集现象本地pmd check -R能报出违规CI 构建却一片绿自定义规则一条没触发。原因maven-pmd-plugin的rulesets没写或写在了插件dependencies里而不是configuration里。插件没拿到规则文件路径就退回默认的 bestpractices、design 等官方规则集。解决把rulesets写进configuration同时给pmd:check的执行阶段加phaseverify/phase确认插件在verify阶段确实执行。最直接的办法是用mvn pmd:check -X看调试日志里加载的规则集路径。5.2 从 PMD 6 升到 PMD 7XPath 规则集体失灵现象升级 PMD 版本后原本正常的NoSystemOut等 XPath 规则不再报违规且没有任何报错。原因PMD 7 重写了 AST 节点体系。ASTPrimaryExpression、ASTPrimaryPrefix、ASTPrimarySuffix这一组节点被移除方法调用统一为ASTMethodCallASTClassOrInterfaceDeclaration拆成了ASTClassDeclaration和ASTInterfaceDeclaration。旧 XPath 表达式里的节点名在新树上根本不存在匹配结果为空。解决每一条 XPath 规则都用 PMD Designer新版本重新对照 AST 树改写。NoSystemOut在 PMD 7 里大致等价于匹配MethodCall节点的QualifiedName属性但每个 PMD 小版本 AST 定义都可能微调以 Designer 实际展示为准。升级前在 CI 里加上探针规则做回归对比能第一时间发现这类静默失效。5.3 XPath 表达式里的让 XML 解析直接报错现象新增一条“方法行数小于 10”的 XPath 规则PMD 启动报Content is not allowed in prolog或The element type ... must be terminated。原因XPath 里的比较运算符在 XML 里是非法字符必须转义。很多人写了//MethodDeclaration[count(...) 10]把 XML 解析器搞翻了。解决所有小于号写成lt;。这类规则建议用not(... ...)来绕开比如行数小于 10 写成not(count(...) 10)可读性差一点但避免转义遗漏。写完用xmllint --noout rulesets/xxx.xml先校验 XML 合法性再跑 PMD。5.4 properties 里配了数字规则类读到的是 null现象自定义规则类里定义了ignoredNumbers属性规则文件里property nameignoredNumbers value0,1,2/运行时getProperty(ignoredNumbers)返回 null。原因属性是按声明类型转换的。规则类里没有对这个属性做definePropertyDescriptor声明PMD 不知道它是什么类型字符串值进不来。解决规则类构造函数里先声明属性描述符public AvoidMagicNumberRule() { definePropertyDescriptor( PropertyFactory.stringProperty(ignoredNumbers) .defaultValue(0,1) .desc(不视为违规的数字列表逗号分隔) .build()); }声明后 PMD 才会把规则文件里的 value 按 string 类型注入。规则文件里配了但读不到九成是属性描述符没定义先查构造函数。5.5 规则改名之后抑制注解和缓存里的旧名字静默失效现象规则从AvoidSystemOut改成NoSystemOut老代码里的SuppressWarnings(PMD.AvoidSystemOut)不再起作用这些代码重新开始报违规。原因抑制注解按规则名精确匹配改名即失效PMD 不会给任何警告。解决改名时全局搜索两个地方.java里的SuppressWarnings(PMD.旧名)和行尾的// NOPMD注释。更稳妥的做法是名字定下来就不改规则名是团队规范的公共 API。另外PMD 的增量缓存也会存规则签名规则改名或改参数后CI 里先mvn clean或删掉.pmd缓存再回归否则容易在“以为修好了”的状态下误判。6. 规则文件要进版本库单测、灰度与一根“保险丝”规则文件是配置但它值得像代码一样被测试。pmd-test框架提供RuleTester对规则文件里的每条规则都能做定点验证。Maven 依赖引入net.sourceforge.pmd:pmd-test后规则测试写成 JUnit 用例public class NoSystemOutRuleTest { Test public void testDetectSystemOut() { RuleTester tester new RuleTester(); // 从规则文件加载指定规则 tester.addRule(rulesets/company-core.xml, NoSystemOut); // positive这段代码应该报违规 tester.positive(System.out.println(x);, 不要直接使用 System.out); // negative这段代码不应该报违规 tester.negative(log.info(\x{}\, x);); } }positive和negative的具体方法签名随 pmd-test 版本有细微出入落地时以你依赖版本的 javadoc 为准。这套测试的价值在于规则文件后续任何改动都能靠测试用例先拦住“把合法代码误判成违规”的回归。新规则上线不要直接failOnViolation true。我一般分三步走第一周只出报告ignoreFailures true把违规清单导出来人工过一遍误报率高就调阈值或加抑制第二周把误报率压到 10% 以下failOnViolation打开但只针对 priority 1、2一个月后把规则和它的测试用例一起转正。灰度期间看 SARIF 或 CSV 报表按模块统计违规密度比看总数更能判断规则是否合理。最后说一个我保留至今的习惯每个规则文件里放一条像第 3 章那样的探针规则priority设 5永远不删。规矩很简单——如果一次扫描里探针规则没触发说明 PMD 链路是断的这份报告不算数。很多团队排查“规则怎么不生效”查了半天最后发现是缓存、工具链或配置路径出了问题探针规则能用一条必报项把整条链路钉死。每次接手新项目或升级 PMD 版本先跑探针再谈规则优化这个顺序帮我少翻了不少车。希望帮到你。本文还有配套的精品资源点击获取
返回列表