扩展阿里巴巴Java开发规约插件 男娘i 2022-06-01 11:29 298阅读 0赞 # 1.前言 # 工作中难免会遇到维护别人代码的情况,那么首先就得看懂别人写的代码。如果对方写的代码混乱臃肿,维护成本必然很高,如果对方写的代码优雅清晰,那维护的人看起来必然心情愉悦。正所谓“前人栽树,后人乘凉;前人埋坑,后人骂娘”。 ![这里写图片描述][70] 代码首先是给人看的,其次才是给机器看到,如何编写出任何人都看到懂的代码?答案是制定规范! 每个公司都会有自己的编码规范,但是往往的情况是赶项目进度或者懒惰或者个人水平习惯等原因,加上没有code review,最后代码就写的千奇百怪了。原因就在于规范是有了,但是没人遵守。所以,编码规范需要强制执行,交给工具来强制执行。 本文将通过介绍java静态代码检查工具PMD、阿里巴巴p3c开源项目到最后编写自定义编码规约来学习如何规范代码的编写。 # 2.PMD静态代码扫描 # ## 2.1.[PMD官网][PMD] ## ## 2.2.概述 ## PMD是一种开源分析Java代码错误的工具。它通过静态分析获知代码错误。也就是说,在不运行Java程序的情况下报告错误。PMD附带了许多可以直接使用的规则,利用这些规则可以找出Java源程序的许多问题,例如: * 潜在的bug:空的try/catch/finally/switch语句 * 未使用的代码:未使用的局部变量、参数、私有方法等 * 可选的代码:String/StringBuffer的滥用 * 复杂的表达式:不必须的if语句、可以使用while循环完成的for循环 * 重复的代码:拷贝/粘贴代码意味着拷贝/粘贴bugs * 循环体创建新对象:尽量不要再for或while循环体内实例化一个新对象 * 资源关闭:Connect,Result,Statement等使用之后确保关闭掉 此外,用户还可以自己定义规则,检查Java代码是否符合某些特定的编码规范。例如,你可以编写一个规则,要求PMD找出所有创建Thread和Socket对象的操作。 ## 2.3.工作原理 ## PMD的核心是JavaCC解析器生成器。PMD结合运用JavaCC和EBNF(扩展巴科斯-诺尔范式,Extended Backus-Naur Formal)语法,再加上JJTree,把Java源代码解析成抽象语法树(AST,Abstract Syntax Tree) 从根本上看,Java源代码只是一些普通的文本。不过,为了让解析器承认 这些普通的文本是合法的Java代码,它们必须符合某种特定的结构要求。这种结构可以用一种称为EBNF的句法元语言表示,通常称为“语法” (Grammar)。JavaCC根据语法要求生成解析器,这个解析器就可以用于解析用Java编程语言编写的程序。 ## 2.4.规则分类 ## * [最佳实践][Link 1]:公认的最佳实践的规则。 * [代码风格][Link 2]:这些规则强制执行特定的编码风格。 * [设计][Link 3]:帮助您发现设计问题的规则。 * [文档][Link 4]:这些规则与代码文档有关。 * [容易出错的规则][Link 5]:用于检测被破坏的、非常混乱的或容易发生运行时错误的结构的规则。 * [多线程][Link 6]:这些规则在处理多个执行线程时标记问题。 * [性能][Link 7]:标记存在性能问题的代码的规则。 * [安全][Link 8]:显示潜在安全缺陷的规则。 ## 2.5.编写PMD自定义规则 ## [How to write a PMD rule][] [静态代码扫描 (一)——PMD 自定义规则入门][_PMD] [XPath教程][XPath] # 3.阿里巴巴Java开发规约插件p3c # ## 3.1.[GITHUB地址][GITHUB] ## ## 3.2.概述 ## 阿里巴巴`p3c`项目包含三个部分: * p3c-pmd,提供大部分规则实现,基于PMD框架开发,如果想实现自己的规则,可以基于该模块开发(该模块基于maven编译打包) * IntelliJ IDEA插件,即idea-plugin模块(该模块基于gradle编译打包) * Eclipse插件,即eclipse-plugin,本文不介绍 ## 3.3.阿里编码规约IDEA插件使用 ## [传送门][Link 9] # 4.基于p3c编写自定义编码规则 # ## 4.1.自定义规则 ## 假设现在需要开发这么一个规则:方法请求参数列表不允许超过(含)5个 ## 4.2.开发步骤 ## ### 4.2.1.找出问题代码,使用pmd图形化工具解析成抽象语法树 ### 代码示例: package org.p3c.demo; public class Demo { public void methodA(int a) { } public void methodB(int a, int b, int c, int d, int e) { } } 将源码放入Source Code框,点击Go按钮,解析结果显示在左下框 ![pmd1][] ### 4.2.2.分析抽象语法树 ### 可以看到,整棵树的根节点是CompilationUnit,即编译单元,代表每个java源文件。我们首先要找到所有的方法声明,根据树节点名称大概也能看出来是MethodDeclaration,点击相应的节点,看看光标是否定位到源码方法声明位置。 仔细分析MethodDeclaration节点,可以看到他有以下几个直接子节点:ResultType、MethodDeclarator、Block,即返回类型、方法声明、方法体 ![pmd2][] MethodDeclarator是我们想找的节点XPATH表达式可以这么写: //CompilationUnit//MethodDeclarator 验证表达式是否正确,将它写到PMD图形界面XPATH Query框中,点击Go按钮 ![pmd3][] 接下来,我们需要找到每个方法对应的参数列表,参数列表节点是方法节点的直接子节点,完整XPATH表达式为: //CompilationUnit//MethodDeclarator/FormalParameters ![pmd4][] 获取到参数列表节点后,我们查看该节点的属性,找出参数个数的属性,观察可以发现是ParameterCount属性。 到现在为止,抽象语法树已经分析完,我们知道这么找出代码中参数列表大于等于5个的方法了。 ### 4.2.3.p3c-pmd项目编写自定义代码规则 ### 打开阿里p3c-pmd工程,开始编写我们的自定义规则。 阿里已经写了很多规则,我们现在要编写的规则属于面向对象范畴,可以把规则写到opp包下,新建一个规则类MethodParameterCountRule,继承自AbstractAliRule,重写 visit方法: ![oop1][] package com.alibaba.p3c.pmd.lang.java.rule.oop; import com.alibaba.p3c.pmd.lang.java.rule.AbstractAliRule; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters; import java.util.List; /** * 方法参数列表个数不宜过长 * * @auther qingjian.wu * @create 2018-01-27 14:59 */ public class MethodParameterCountRule extends AbstractAliRule{ private static final String METHOD_XPATH = "//MethodDeclarator"; private static final Integer PARAMETER_COUNT_LIMIT = 5; @Override public Object visit(ASTCompilationUnit node, Object data) { try { // 找到所方法节点 List<Node> methodNodes = node.findChildNodesWithXPath(METHOD_XPATH); if (methodNodes != null && methodNodes.size() > 0) { for (Node methodNode : methodNodes) { // 找到每个方法的参数列表声明 List<ASTFormalParameters> formalParameters = methodNode.findChildrenOfType(ASTFormalParameters.class); if (formalParameters.get(0).getParameterCount() >= PARAMETER_COUNT_LIMIT) { // 违反规则提示信息,第二个参数是提示信息位置,第三个参数是提示信息key,第四个参数用来替换提示信息 // 中的占位符,这里获取的节点image属性就是方法名称 addViolationWithMessage(data, methodNode, "java.oop.MethodParameterCountRule.violation.msg", new Object[]{ methodNode.getImage()}); } } } } catch (Exception e) { e.printStackTrace(); } return super.visit(node, data); } } ### 4.2.4.p3c-pmd项目配置规则 ### 将编写好规则配置到ali-oop.xml文件中 ![oop2][] <rule name="MethodParameterCountRule" language="java" message="java.oop.MethodParameterCountRule.rule.msg" class="com.alibaba.p3c.pmd.lang.java.rule.oop.MethodParameterCountRule"> <!--级别,1强制,2推荐,3参考--> <priority>1</priority> <example> <![CDATA[ Negative example: public void methodB(int a, int b, int c, int d, int e) { } ]]> </example> <example> <![CDATA[ Positive example: public void methodA() { } ]]> </example> </rule> ### 4.2.5.p3c-pmd项目编写提示信息 ### 上两步使用的提示信息和规则信息需要编写到message.xml配置文件中,message\_en.xml中是英文提示,这里就先不演示了 ![oop3][] <entry key="java.oop.MethodParameterCountRule.violation.msg"> <![CDATA[方法【%s】参数列表过长。 ]]> </entry> <entry key="java.oop.MethodParameterCountRule.rule.msg"> <![CDATA[说明:方法参数列表不允许超过(含)5个,建议封装到一个对象中。]]> </entry> ### 4.2.6.单元测试 ### 编写测试样例,将要测试的源代码写到test目录对应的xml文件中 ![oop4][] <?xml version="1.0" encoding="UTF-8"?> <test-data> <code-fragment id="测试样例"> <![CDATA[ package org.p3c.demo; public class Demo { public void methodA(int a) { } public void methodB(int a, int b, int c, int d, int e) { } } ]]> </code-fragment> <test-code> <!-- 预期问题个数 --> <expected-problems>0</expected-problems> <code-ref id="测试样例" /> </test-code> </test-data> 编写单元测试 ![oop5][] 运行单元测试,因为样例代码中methodB不符合规范,但是我们预期问题个数写的是0,所以单元测试会不通过: ![oop6][] ## 4.3.配置插件 ## ### 4.3.1.p3c-pmd打包安装到本地maven仓库 ### 先把用不到的插件`maven-javadoc-plugin`和`maven-gpg-plugin`注释掉,然后运行mvn命令: mvn -DskipTests=true clean install ### 4.3.2.idea-plugin项目打包插件 ### idea-plugin项目基于gradle构建,配置根目录下build.gradle,让构建使用本地私有maven仓库构建 ![idea1][] 然后运行开始gradle构建: clean buildDependents build 打包成功后会在`idea-plugin\p3c-idea\build\distributions\`目录下生成`Alibaba Java Coding Guidelines-1.0.0.zip`文件,这个就是我们加入了自己拓展阿里开发规约的插件,IDEA中安装此插件 Settings->Plugins->Install plugin from disk ![idea2][] ### 4.3.3.IDEA中使用编码规约插件 ### 安装完插件重启IDEA,用之前的代码测试下插件是否生效。 右键点击“编码规约扫描” ![idea3][] 结果: ![idea4][] # 5.Maven打包加入PMD校验 # 到目前为止,我们已经做到了能在开发阶段实时校验自己的代码了,最后我们需要把规约检查加入到代码打包中,这样才能做到部署到生产环境的代码都是符合规范的,如果不符合规范,打包会失败。 考虑到大多数项目使用maven管理,可以把自定义pmd规则整合到maven,这样就可以使用maven校验代码规则了 在maven项目中加入pmd插件,配置插件在`package`阶段执行。通常我们的项目都有一个公共的父pom,那将插件加入到父pom中就行 <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-pmd-plugin</artifactId> <version>3.8</version> <configuration> <rulesets> <ruleset>rulesets/java/ali-comment.xml</ruleset> <ruleset>rulesets/java/ali-concurrent.xml</ruleset> <ruleset>rulesets/java/ali-constant.xml</ruleset> <ruleset>rulesets/java/ali-exception.xml</ruleset> <ruleset>rulesets/java/ali-flowcontrol.xml</ruleset> <ruleset>rulesets/java/ali-naming.xml</ruleset> <ruleset>rulesets/java/ali-oop.xml</ruleset> <ruleset>rulesets/java/ali-orm.xml</ruleset> <ruleset>rulesets/java/ali-other.xml</ruleset> <ruleset>rulesets/java/ali-set.xml</ruleset> </rulesets> <printFailingErrors>true</printFailingErrors> <!--扫描级别,小于等于这个级别的错误代码将不通过扫描。不配默认是5--> <minimumPriority>1</minimumPriority> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>check</goal> </goals> </execution> </executions> <dependencies> <dependency> <groupId>com.alibaba.p3c</groupId> <artifactId>p3c-pmd</artifactId> <version>1.3.3</version> </dependency> </dependencies> </plugin> </plugins> </build> 如果存在不符合规范代码打包将失败: ![idea5][] 关于maven pmd插件更详细介绍参考[官网][Link 10] # 6.总结 # 本文篇幅确实有点长,看懂需要有点耐心。不过其实也挺简单,关键点就是分析抽象语法树,找出问题代码节点,剩下的工作就很简单了。 PMD也有局限性,比如只能校验java源文件,对于XML等配置规约就没辙了。还有最最重要的,代码逻辑等关键性问题是没法校验的,也没法做。PMD只是一定程度上规范了我们的代码,要写出优雅的代码,还得多思考多实践呐。 [70]: /images/20220601/4d6f7fe1d4ee426bb8c8353431b59742.png [PMD]: https://pmd.github.io/ [Link 1]: https://pmd.github.io/pmd-6.4.0/pmd_rules_java_bestpractices.html [Link 2]: https://pmd.github.io/pmd-6.4.0/pmd_rules_java_codestyle.html [Link 3]: https://pmd.github.io/pmd-6.4.0/pmd_rules_java_design.html [Link 4]: https://pmd.github.io/pmd-6.4.0/pmd_rules_java_documentation.html [Link 5]: https://pmd.github.io/pmd-6.4.0/pmd_rules_java_errorprone.html [Link 6]: https://pmd.github.io/pmd-6.4.0/pmd_rules_java_multithreading.html [Link 7]: https://pmd.github.io/pmd-6.4.0/pmd_rules_java_performance.html [Link 8]: https://pmd.github.io/pmd-6.4.0/pmd_rules_java_security.html [How to write a PMD rule]: https://pmd.github.io/pmd-5.4.1/customizing/howtowritearule.html [_PMD]: https://testerhome.com/topics/4918 [XPath]: http://www.w3school.com.cn/xpath/index.asp [GITHUB]: https://github.com/alibaba/p3c [Link 9]: https://github.com/alibaba/p3c/wiki/IDEA%E6%8F%92%E4%BB%B6%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3 [pmd1]: /images/20220601/b6862aca44b24a2bbe706b6e89366c55.png [pmd2]: /images/20220601/cdf24caccef84788a5bb5e94b022db16.png [pmd3]: /images/20220601/b8afae7df3a944f3a4ac3e5544e8dcfd.png [pmd4]: /images/20220601/f087d51ba18a47aaa8f3c7790f251d72.png [oop1]: /images/20220601/8a968f23db884c9c836037a24b398a9f.png [oop2]: /images/20220601/dd13808c9cc44268a697f187bf4e1e09.png [oop3]: /images/20220601/d51fc2deebcd478b85a53caa2b9ac0af.png [oop4]: /images/20220601/3621a4f194214452a7473ad9effeb05d.png [oop5]: /images/20220601/8a06e92ab28346aaa903a3b5beaf2474.png [oop6]: /images/20220601/3e6e433c0ed64bbeb895b4d2c064d9d3.png [idea1]: /images/20220601/9c0f47cf5c854fdd9e3463ffd1851118.png [idea2]: /images/20220601/ba68a2379b164560b40d7af86a946385.png [idea3]: /images/20220601/e8c7d25fa3c84dfb8020d9244c1b845a.png [idea4]: /images/20220601/1eed30c80110413eb7e85f7f83f9ec87.png [idea5]: /images/20220601/9bbee2e0156f4089b6e312baed83bcc0.png [Link 10]: http://maven.apache.org/plugins/maven-pmd-plugin/pmd-mojo.html
还没有评论,来说两句吧...