1. 熟悉maven

2. 熟悉gradle

3. 熟悉pmd

4. 经常抓网站(熟悉xpath)

5. 会写java代码

一、为什么要定制规范

代码是谁维护?是gitlab?还是jdk?说到底,还是程序猿这些人类来维护!

代码首先是给人看的,其次才是给机器。没人能读懂,也没人能维护的代码那就是个渣 ( ̄_, ̄ )

那么,问题来了,如何编写出任何人都看到懂的代码?答:制——定——规——范

然后,问题又来了,虽然每个公司都有自己的编码规范,但往往出于赶进度、怠惰、个人心情&水平&习惯等原因,加之没有review,最后的代码就让人望而生畏。

规范有了没人遵守,怎么强制执行呢? 答:交——给——工——具!(哎~看这锅甩的弧线像不像研发的泪?)

本文通过介绍java静态代码检查工具PMD阿里巴巴p3c开源项目,让你学会怎么潇洒的制定自己的代码规范,解放你的双手,以及滔滔不绝的说教。

二、基本原理

古人云,君子生非异也,善假于物也。意思是大佬都是会借力的,可四两拨千斤。现在就介绍下本期的千斤顶

1、PMD静态代码扫描

1.1 概述

PMD(Project Manager Design)是一种开源分析Java代码错误的工具,它通过静态分析获知代码错误。即在不运行Java程序的情况下报告错误。其本身附带了许多可以直接使用的规则,利用这些规则可以找出Java源程序的许多问题。

例如:

潜在的bug:空的if/try/catch/finally/switch语句 ;

未使用的代码:未使用的局部变量、参数、私有方法等 ;

可选的代码:String/StringBuffer的滥用 ;

复杂的表达式:不必须的if语句、可以使用while循环完成的for循环;

重复的代码:糊代码==糊bug ;

循环体创建新对象:创建对象一时爽,一直创建一直爽 ;

资源关闭:Connect,Result,Statement等使用之后确保关闭 。

此外,用户还可以自己定义规则,检查Java代码是否符合某些特定的编码规范。例如,你可以编写一个规则,要求PMD找出所有创建Thread和Socket对象的操作

1.2 工作原理

PMD的核心是JavaCC解析器生成器。其结合运用JavaCC和EBNF(扩展巴科斯-诺尔范式,Extended Backus-Naur Formal)语法,再加上JJTree,把Java源代码解析成抽象语法树(AST,Abstract Syntax Tree)

从根本上看,Java源代码只是一些普通的文本。不过,为了让解析器承认 这些普通的文本是合法的Java代码,它们必须符合某种特定的结构要求。

这种结构可以用一种称为EBNF的句法元语言表示,通常称为“语法” (Grammar)。JavaCC根据语法要求生成解析器,这个解析器就可以用于解析用Java编程语言编写的程序。

1.3 规则分类

最佳实践:公认的最佳实践的规则

代码风格:这些规则强制执行特定的编码风格

设计:帮助您发现设计问题的规则

文档: 这些规则与代码文档有关

容易出错的规则: 用于检测被破坏的、非常混乱的或容易发生运行时错误的结构的规则

多线程:这些规则在处理多个执行线程时标记问题

性能:标记存在性能问题的代码的规则

安全:显示潜在安全缺陷的规则

2、阿里巴巴Java开发规约插件p3c

p3c主要包含三个部分:

p3c-pmd

idea-plugin , IntelliJ IDEA插件(基于gradle编译打包)

三、如何做?

说了这么多,那到底怎么扩展p3c编写自定义规则呢?( ͡° ͜ʖ ͡°)

1、自定义规则设计

以常用的spring-mvc为例。

假设,需要开发一个规则:方法上的@RequestMapping 注解需指定method参数。(不然swagger生成文档能生出一堆葫芦娃)


1//前方swagger葫芦娃预警
 2@RequestMapping("/hehe")
 3public String sayHeHe() {
 4    return "hehe";
 5}
 6//仅GET、POST方法
 7@RequestMapping(value = "/hehe",method = {RequestMethod.GET,RequestMethod.POST})
 8public String sayHeHe() {
 9    return "hehe";
10}


2、准备阶段

下载PMD

拉P3C代码

冲杯咖啡 (o≖◡≖)

3、PMD图形化工具

解压PMD,打开/pmd-bin-6.17.0/bin/designer.bat

使用图形化工具构建语法树,揪出问题代码。


阿里java规约插件 idea idea阿里规范插件_java


相关代码如下:


1package com.test.rest.controller;
 2
 3import lombok.extern.slf4j.Slf4j;
 4import org.springframework.beans.factory.annotation.Autowired;
 5import org.springframework.web.bind.annotation.RequestMapping;
 6import org.springframework.web.bind.annotation.RestController;
 7import java.util.List;
 8
 9@Slf4j
10@RestController
11@RequestMapping("/test")
12public class TestController {
13    @RequestMapping(value="/hehe",method = {RequestMethod.GET,RequestMethod.POST})
14    public String sayHeHe() {
15        return "hehe";
16    }
17    @RequestMapping("/heihei")
18    public String sayHeiHei() {
19        return "heihei";
20    }
21    @RequestMapping(value="/haha")
22    public String sayHaHa() {
23        return "haha";
24    }
25}


4、分析语法树

可以看到,整棵树的根节点是CompilationUnit,即编译单元,代表每个java源文件。

我们首先要找到@RequestMapping的位置,点击相应的节点,看看光标是否定位到源码方法声明位置。

仔细分析ClassOrInterfaceBody,发现存在Annotation节点,这就是我们需要下手的地方了。


阿里java规约插件 idea idea阿里规范插件_java_02


注意,由于写法不同,被分为了NormalAnnotation及SingleMemberAnnotation,为了后续处理XPath可以这么写:


1//ClassOrInterfaceBody//Annotation[@AnnotationName='RequestMapping']


来验证一下表达式是否正确,将它丢到PMD图形界面XPATH Expression框中:


阿里java规约插件 idea idea阿里规范插件_java_03


5、p3c-pmd编写

打开p3c-pmd工程,开始编写自定义规则。

阿里已经写了很多规则,我们现在要编写的规则属于面向对象范畴,可以把规则写到oop包下。

新建一个规则类RequestMappingRule,继承自AbstractAliRule,重写 visit方法:


阿里java规约插件 idea idea阿里规范插件_maven怎么强制updating_04


相关代码:


1public class RequestMappingRule extends AbstractAliRule {
 2    private static final String IMPORT_XPATH = "//ImportDeclaration[@ImportedName='org.springframework.web.bind.annotation.RequestMapping']";
 3    private static final String REQUESTMAPPING_XPATH = "//ClassOrInterfaceBody//Annotation[@AnnotationName='RequestMapping']";
 4    private static final String METHOD_XPATH = "MemberValuePairs/MemberValuePair[@MemberName='method']";
 5    @Override
 6    public Object visit(ASTCompilationUnit node, Object data) {
 7        try {
 8            List<Node> importNodes = node.findChildNodesWithXPath(IMPORT_XPATH);
 9            if (null != importNodes && importNodes.size() > 0) {
10                List<Node> resquestMappingNodes = node.findChildNodesWithXPath(REQUESTMAPPING_XPATH);
11                Node annotation = null;
12                for (Node resquestMappingNode:resquestMappingNodes) {
13                    annotation = resquestMappingNode.jjtGetChild(0);
14                    if(annotation instanceof ASTSingleMemberAnnotation){
15                        addViolationWithMessage(data, resquestMappingNode, "java.oop.RequestMappingRule.rule.msg", new Object[]{});
16                    }else if(annotation instanceof ASTNormalAnnotation){
17                        if(!annotation.hasDescendantMatchingXPath(METHOD_XPATH)){
18                            addViolationWithMessage(data, resquestMappingNode, "java.oop.RequestMappingRule.rule.msg", new Object[]{});
19                        }
20                    }
21                }
22            }
23        } catch (Exception e) {
24        }
25        return super.visit(node, data);
26    }
27}


6、规则配置

将编写好规则配置到ali-oop.xml文件中:


阿里java规约插件 idea idea阿里规范插件_maven怎么强制updating_05


相关代码:


1<rule name="RequestMappingRule"
 2          language="java"
 3          message="java.oop.RequestMappingRule.rule.msg"
 4          class="com.alibaba.p3c.pmd.lang.java.rule.oop.RequestMappingRule">
 5        <description>java.oop.StringConcatRule.rule.msg.desc</description>
 6        <!--级别,1强制,2推荐,3参考-->
 7        <priority>2</priority>
 8        <example>
 9            <![CDATA[
10        Negative example:
11            @RequestMapping("/heihei")
12            @RequestMapping(value="/haha")
13        ]]>
14        </example>
15        <example>
16            <![CDATA[
17        Positive example:
18            @RequestMapping(value="/hehe",method = {RequestMethod.GET,RequestMethod.POST})
19        ]]>
20        </example>
21    </rule>


7、规则提示信息编写

上两步使用的提示信息和规则信息需要编写到message.xml配置文件中,message_en.xml中为英文提示,选修


阿里java规约插件 idea idea阿里规范插件_3c_06


8、单元测试

编写测试样例,将要测试的源代码写到test目录对应的xml文件中:


阿里java规约插件 idea idea阿里规范插件_阿里java规约插件 idea_07


相关代码:


1<?xml version="1.0" encoding="UTF-8"?>
 2<test-data xmlns="http://pmd.sourceforge.net/rule-tests"
 3           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4           xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests https://pmd.sourceforge.io/rule-tests_1_0_0.xsd">
 5    <code-fragment id="RequestMapping-use">
 6        <![CDATA[
 7package com.test.rest.controller;
 8
 9import lombok.extern.slf4j.Slf4j;
10import org.springframework.beans.factory.annotation.Autowired;
11import org.springframework.web.bind.annotation.RequestMapping;
12import org.springframework.web.bind.annotation.RestController;
13import java.util.List;
14@Slf4j
15@RestController
16@RequestMapping("/test")
17public class TestController {
18    @RequestMapping(value="/hehe",method = {RequestMethod.GET,RequestMethod.POST})
19    public String sayHeHe() {
20        return "hehe";
21    }
22    @RequestMapping("/heihei")
23    public String sayHeiHei() {
24        return "heihei";
25    }
26    @RequestMapping(value="/haha")
27    public String sayHaHa() {
28        return "haha";
29    }
30}
31        ]]>
32    </code-fragment>
33    <test-code>
34        <description>RequestMapping use rule</description>
35        <expected-problems>0</expected-problems>
36        <expected-linenumbers>21,27</expected-linenumbers>
37        <code-ref id="RequestMapping-use"/>
38    </test-code>
39</test-data>


编写单元测试:


阿里java规约插件 idea idea阿里规范插件_阿里java规约插件 idea_08


运行单元测试,因为样例代码中21、27行不符合规范,但是我们预期问题个数写的是0,所以单元测试会不通过:


阿里java规约插件 idea idea阿里规范插件_3c_09


9、p3c-pmd打包

将p3c-pmd安装到本地maven仓库

先将不用的插件maven-javadoc-plugin 和maven-gpg-plugin注释掉,然后运行mvn命令:


1mvn -DskipTests=true clean install


idea-plugin插件打包

idea-plugin项目基于gradle构建,配置根目录下build.gradle,让构建使用本地私有maven仓库构建。


阿里java规约插件 idea idea阿里规范插件_阿里java规约插件 idea_10


然后运行开始gradle构建:


1cd p3c-idea
2gradle clean buildPlugin


打包成功后会在idea-pluginp3c-ideabuilddistributions 目录下生成Alibaba Java Coding Guidelines-1.0.0.zip 文件,这个就是我们加入了自己拓展阿里开发规约的插件。

IDEA安装后效果如下:


阿里java规约插件 idea idea阿里规范插件_github_11


End

我很懒,懒得关注一些细如编码质量的问题。但我又想要写一些漂亮的代码,起码遵循规范的代码。借助工具,能够节省不少的力气,小伙伴们体验之后,也直呼好用。

这些,都是我值得欣慰的事情,也是让我懒下去的动力。

参考资料:

[1] https://pmd.github.io/

[2] https://pmd.github.io/pmd-6.4.0/pmd_rules_java_bestpractices.html

[3] https://pmd.github.io/pmd-6.4.0/pmd_rules_java_codestyle.html

[4] https://pmd.github.io/pmd-6.4.0/pmd_rules_java_design.html

[5] https://pmd.github.io/pmd-6.4.0/pmd_rules_java_documentation.html

[6] https://pmd.github.io/pmd-6.4.0/pmd_rules_java_errorprone.html

[7] https://pmd.github.io/pmd-6.4.0/pmd_rules_java_multithreading.html

[8] https://pmd.github.io/pmd-6.4.0/pmd_rules_java_performance.html

[9] https://pmd.github.io/pmd-6.4.0/pmd_rules_java_security.html

[10] https://github.com/alibaba/p3c

[11] https://pmd.github.io/

[12] https://github.com/alibaba/p3c