P 3 C (源码地址: https://github.com/alibaba/p3c )

P 3 C 一款代码规范的检查工具,有对应的 ide 插件,能在编码过程中对设置的规则进行提示,可以针对公司编码规范对它原来基础上做了进一步的拓展;

P 3 C 主要包括 3 部分:

  • PMD 实现 (p3c-pmd):使用 PMD 来实现代码规范检查
  • Intellij IDEA 插件
  • Eclipse 插件

PMD

P 3 c 使用了 PMD。PMD 是一款静态代码扫描工具,该工具可以做到检查 Java 代码中是否含有未使用的变量、是否含有空的抓取块、是否含有不必要的对象等。PMD 使用 JavaCC 生成解析器来解析源代码并生成 AST (抽象语法树),通过对 AST 的检查可以直接从源代码文本层面来对代码进行检查,在 PMD 内部称为规则。即是否符合规则指的是,穷举源码各种可能的写法,然后在 AST 上检查是否出现。而规则的实现,重点便在对 AST 的处理上。

pmd-bin-6.55.0【PMD 可执行版本】 ( https://pmd.github.io/ )

解压后会发现主要有两个目录:

  • bin
    • designer.bat:【界面工具,能将 java 源代码转化为 AST(抽象语法树),个人推荐使用】
    • bgastviewer.bat:【界面工具,与 designer.bat 功能相似】
    • cpd.bat:【用来查找重复代码的工具,命令行版】
    • cpdgui.bat:【用来查找重复代码的工具,GUI 版】
    • pmd.bat:【Window 平台下运行 PMD 需要使用的文件】
    • run.sh:【Linux 平台下运行 PMD 需要使用的文件】
  • lib 【该目录存放 PMD 运行依赖的 jar 包,包括第三方 jar 包和各种语言的模块 jar 包】

AST

关于 AST 的介绍网上有很多,可以直接搜索,这里重要提两点:

  • AST 是源代码的抽象语法结构的树状表示.
  • 抽象语法树并不依赖于原语言的语法,也就是说同语法分析阶段所采用的上下文无关.

PMD 使用 JavaCC 来生成 AST。关于 JavaCC 也可以在网上查看相关资料,这里不多介绍,只要知道 JavaCC 是一个词法分析生成器和语法分析生成器便行。

开始实践

确定自定义规则

自定义规则:  方法参数不能超过5个,如果参数无法减少,可以将多个参数封装成一个对象

列举会触犯这种规则的所有不同的写法。

public void fn(int a, int b, int c,int d, int f, int g) {
	dosomething();
}

使用 designer.bat 分析所有写法的抽象语法树的特点

如果在启动 designer.bat 时报错,则需要安装 JavaFX

安装 JavaFX

下载 JavaFX

https://gluonhq.com/products/javafx/
image-20230722123623077

解压保存在自定义的目录下

我保存在了 JDK 目录下
image-20230722123659174

配置环境变量

JAVAFX_HOME
image-20230722130103251

在 Path 中配置 lib 和 bin 的路径
image-20230722130142887

配置完成后在次执行 designer.bat 即可

image-20230722130246189

开始分析

不可以直接分析方法,需要包在一个类中

public class Example {
	public void fn(int a, int b, int c,int d, int f, int g) {
	   // ...
	}
}

image-20230722130600712

==注意==: 这个树形结构和源代码是有对应关系的。其中我们需要重点关注的 FormalParameters 的抽象树结构如下:注意标红的节点,根据定义的方法参数, FormalParameter 这个节点会对应的增加或减少,这样我们只需要写一个规则检查 FormalParameters 下的 FormalParameter 节点是否大于5就可以了,就可以报警告知这里是有问题的。

具体详细的节点的信息可以看对应的 jar 文件

image-20230722131205331

编写规则代码捕捉这种特点

代码规范实现的主要模块,使用 pmd 来实现。p3c-pmd 模块在代码组织上很工整,可以按照相同的模式增加自定义的规则/规则集。对于本文需求,打算在该模块的基础上增加一个 extend 模块,用于实现自定义规则集。如下,为对应的源码路径好规则集路径。
image-20230722131258818

public class MethodParamsNumRule extends AbstractAliRule {  
  
	private static final int PARAMSNUM = 5;  
	  
	@Override  
	public Object visit(ASTFormalParameters node, Object data) {  
		if (node.jjtGetNumChildren()>PARAMSNUM) {  
		addViolationWithMessage(data,node,"java.extend.MethodParamsNumRule.rule.msg");  
		};  
		return super.visit(node, data);  
	}  
  
}

创建自己的规则

创建自己的 xml 规则文件,内容包括规则的相关信息

现在规则已经写完了,我们需要告诉 PMD 运行时执行这条规则,就得将这个规则文件的相关信息放在 XML 规则集文件中。例如: pmd-java/src/main/resources/rulesets/java/extend.xml ;这里面有很多规则的定义,复制粘贴一下,改成一个新的规则集文件,名字自己随便取: MethodParamsNumRule.xml ,自己填充一下元素和属性。

  • Name - MethodParamsNumRule
  • Message - java. Extend. MethodParamsNumRule. Rule. Msg
  • class - com. Alibaba. P 3 c. Pmd. Lang. Java. Rule. Extend. MethodParamsNumRul 放哪都行. 注意,没有必要放在 net.sourceforge.pmd 目录下,可以放在 com.yourcompany.util.pmd
  • Description - 具体描述信息
  • Example - 通过代码片段展示违反的规则样例

image-20230722131537702

<?xml version="1.0"?>  
  
<ruleset name="AlibabaJavaExceptions" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"  
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">  
	<description>ExtendJavaExceptions</description>  
  
	<!-- 方法的参数 -->  
	<rule name="MethodParamsNumRule"  
		language="java"  
		message="java.extend.MethodParamsNumRule.rule.msg"  
		class="com.xenoamess.p3c.pmd.lang.java.rule.extend.MethodParamsNumRule">  
		<priority>2</priority>  
		<example>  
			<![CDATA[  
			public class Example {  
				public void fn(int a, int b, int c,int d, int f, int g) {  
					dosomething();  
				}  
			}  
			]]>  
		</example>  
	</rule>  
</ruleset>

java.extend.MethodParamsNumRule.rule.msg 设置在这里
image-20230722131856912

messages.xml

<entry key="java.extend.MethodParamsNumRule.rule.msg">  
	<![CDATA[  
	方法的参数不要超过5个,如果确实需要5个以上的参数,请创建参数对象。  
	]]>  
</entry>

messages_en.xml

<entry key="java.extend.MethodParamsNumRule.rule.msg">  
	<![CDATA[  
	The parameters of the method should not exceed 5. If you really need more than 5 parameters, please create a parameter object.  
	]]>  
</entry>

测试规则

PMD 推荐对于每个规则,至少要有一个正向和逆向的测试用例,来验证规则出现和不出现的情况。对于规则的测试,PMD 也提供了一套框架,只要按照约定好的方式添加 xml 测试文件即可。
PMD 约定了几个规则,用来加载测试案例

  • 测试类要继承 `net.sourceforge.pmd.testframework.SimpleAggregatorTst类,该整合了Junt,可以在里面增加需要的测试方法。
  • 对于在 src/test/resource 和测试类对应的路径下增加一个 xml 目录,在增加同第一步同名的 xml 文件,该文件用于书写测试集。

编写测试代码

image-20230722132627563

public class MethodParamsNumRuleTest extends SimpleAggregatorTst {  
	private static final String RULESET = "java-ali-extend";  
	  
	@Override  
	public void setUp() {  
		addRule(RULESET, "MethodParamsNumRule");  
	}  
}

编写测试 XML

image-20230722132749537

<?xml version="1.0" encoding="UTF-8"?>  
<test-data xmlns="http://pmd.sourceforge.net/rule-tests"  
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests https://pmd.sourceforge.io/rule-tests_1_0_0.xsd">  
  
<code-fragment id="constants-ok"><![CDATA[  
public class Add{  
public void fn(int a) {  
// ...  
}  
public void fn(int a,int b) {  
// ...  
}  
public void fn(int a,int b,int c) {  
// ...  
}  
}  
]]>  
</code-fragment>  
  
<test-code>  
<description>params less than five</description>  
<expected-problems>0</expected-problems>  
<code-ref id="constants-ok"/>  
</test-code>  
  
<code-fragment id="constants-err"><![CDATA[  
public class Add{  
public void fn(int a,int b,int c,int d,int e,int f) {  
// ...  
}  
}  
]]>  
</code-fragment>  
  
<test-code>  
<description>params more than five</description>  
<expected-problems>1</expected-problems>  
<expected-linenumbers>2</expected-linenumbers>  
<code-ref id="constants-err"/>  
</test-code>  
  
</test-data>

开始测试

  • 执行 mvn clean
  • 执行 mvn test
  • 执行 mvn install 安装到本地,方便后面 idea-plugin 项目打包使用