本文字数: 2007字
阅读时间: 7分钟
很多公司都在用阿里巴巴的java开发规范,本文会简要谈一下规范的必要性,然后介绍两个底层原理。最后,手把手教你怎么扩展阿里巴巴Java开发规范插件: P3C,添加你的自定义规则。
本心法注重 内功修养, 普通人修炼需要 三个月, 而任督二脉打通之高手, 三个小时 即可炼成。
任督二脉打通条件如下:
1. 熟悉maven
2. 熟悉gradle
3. 熟悉pmd
4. 经常抓网站(熟悉xpath)
5. 会写java代码
代码是谁维护?是gitlab?还是jdk?说到底,还是程序猿这些 人类 来维护!
代码首先是给人看的,其次才是给机器。没人能读懂,也没人能维护的代码那就是个渣 ( ̄_, ̄ )
那么, 问题来了 ,如何编写出任何人都看到懂的代码?答: 制——定——规——范 !
然后, 问题又来了 ,虽然每个公司都有自己的编码规范,但往往出于赶进度、怠惰、个人心情&水平&习惯等原因,加之 没有review ,最后的代码就让人望而生畏。
规范有了没人遵守,怎么强制执行呢? 答:交——给——工——具!(哎~看这锅甩的弧线像不像研发的泪?)
本文通过介绍 java静态代码检查工具PMD 、 阿里巴巴p3c开源项目 ,让你学会怎么潇洒的制定自己的代码规范,解放你的双手,以及滔滔不绝的说教。
古人云,君子生非异也,善假于物也。意思是大佬都是会借力的,可四两拨千斤。现在就介绍下本期的 千斤顶 。
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 规则分类
最佳实践:公认的最佳实践的规则 ② ;
代码风格:这些规则强制执行特定的编码风格 ③ ;
设计:帮助您发现设计问题的规则 ④ ;
文档:这些规则与代码文档有关 ⑤ ;
容易出错的规则:用于检测被破坏的、非常混乱的或容易发生运行时错误的结构的规则 ⑥ ;
多线程:这些规则在处理多个执行线程时标记问题 ⑦ ;
性能:标记存在性能问题的代码的规则 ⑧ ;
安全:显示潜在安全缺陷的规则 ⑨ ;
p3c主要包含三个部分:
p3c-pmd,大部分规则实现,基于PMD开发,若想实现自己的规则,可基于此模块开发(基于maven编译打包)
idea-plugin, IntelliJ IDEA插件(基于gradle编译打包) ⑩
说了这么多,那到底怎么扩展p3c编写自定义规则呢?( ͡° ͜ʖ ͡°)
以常用的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}
下载PMD ⑪
拉P3C代码 ⑫
冲杯咖啡 (o≖◡≖)
解压PMD,打开/pmd-bin-6.17.0/bin/designer.bat
使用图形化工具构建语法树,揪出问题代码。
相关代码如下:
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}
可以看到,整棵树的根节点是CompilationUnit,即编译单元,代表每个java源文件。
我们首先要找到@RequestMapping的位置,点击相应的节点,看看光标是否定位到源码方法声明位置。
仔细分析ClassOrInterfaceBody,发现存在Annotation节点,这就是我们需要下手的地方了。
注意,由于写法不同,被分为了NormalAnnotation及SingleMemberAnnotation,为了后续处理XPath可以这么写:
1//ClassOrInterfaceBody//Annotation[@AnnotationName='RequestMapping']
来验证一下表达式是否正确,将它丢到PMD图形界面XPATH Expression框中:
打开p3c-pmd工程,开始编写自定义规则。
阿里已经写了很多规则,我们现在要编写的规则属于面向对象范畴,可以把规则写到oop包下。
新建一个规则类RequestMappingRule,继承自AbstractAliRule,重写 visit方法:
相关代码:
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}
将编写好规则配置到ali-oop.xml文件中:
相关代码:
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>
上两步使用的提示信息和规则信息需要编写到message.xml配置文件中,message_en.xml中为英文提示,选修
编写测试样例,将要测试的源代码写到test目录对应的xml文件中:
相关代码:
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>
编写单元测试:
运行单元测试,因为样例代码中21、27行不符合规范,但是我们预期问题个数写的是0,所以单元测试会不通过:
将p3c-pmd安装到本地maven仓库
先将不用的插件 maven-javadoc-plugin 和 maven-gpg-plugin 注释掉,然后运行mvn命令:
1mvn -DskipTests=true clean install
idea-plugin项目基于gradle构建,配置根目录下build.gradle,让构建使用本地私有maven仓库构建。
然后运行开始gradle构建:
1cd p3c-idea 2gradle clean buildPlugin
打包成功后会在 idea-plugin/p3c-idea/build/distributions/ 目录下生成 Alibaba Java Coding Guidelines-1.0.0.zip 文件,这个就 是我们加入了自己拓展阿里开发规约的插件。
IDEA安装后效果如下:
我很懒,懒得关注一些细如编码质量的问题。但我又想要写一些漂亮的代码,起码遵循规范的代码。借助工具,能够节省不少的力气,小伙伴们体验之后,也直呼好用。
这些,都是我值得欣慰的事情,也是让我懒下去的动力。
参考资料:
[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
作者简介: 小姐姐味道 (xjjdog),一个不允许程序员走弯路的公众号。聚焦基础架构和Linux。十年架构,日百亿流量,与你探讨高并发世界,给你不一样的味道。我的个人微信xjjdog0,欢迎添加好友,进一步交流。
近期热门文章
《 996的乐趣,你是无法想象的 》
魔幻现实主义,关爱神经衰弱
《 一切荒诞的傲慢,皆来源于认知 》
不要被标题给骗了,画面感十足的消遣文章
《必看!java后端,亮剑诛仙》
后端技术索引,中肯火爆。全网转载上百次。
《学完这100多技术,能当架构师么?(非广告)》
精准点评100多框架,帮你选型