背景:
现代软件架构的复杂性需要协同开发完成,如何高效的协同呢?对软件来说,适当的规范和标准不是消灭代码内容的创造性、优雅性,而是限制过度个性化,以一种普遍认可的统一方式一起做事,提升协作效率,降低沟通成本。代码质量的提升是尽可能少踩坑,杜绝踩重复的坑,切实提升系统稳定性,码出质量。所以每个程序员都有必要提高自己的代码质量,对自己的代码负责的同时也对自己的工作负责。特别是对于同一团队,统一的代码规范非常重要,形成统一的风格,有利于看懂他人代码,排查问题等。
插件说明:
为了让开发者更加方便、快速将规范推动并实行起来,阿里巴巴基于手册内容,研发了一套自动化的IDE检测插件(IDEA、Eclipse)。该插件在扫描代码后,将不符合规约的代码按Blocker/Critical/Major三个等级显示在下方。在IDEA上,基于Inspection机制提供了实时检测功能,编写代码的同时也能快速发现问题所在。对于历史代码,部分规则还实现了批量一键修复功能。
1、下载安装
在setting-plugins-marketplace中查找Alibaba Java Coding Guidelines插件,点击Installed 安装
点击Installed后,重启Restart
安装完成后,在toolsbar菜单栏可以看到检测图标
2、项目中实际使用
选中某个文件或者module,右键,弹出编码规约扫描,点击进行扫描源码
3、扫描代码后,在下方Inspection Results查看扫描结果,将不符合规约的代码按Blocker/Critical/Major三个等级显示在下方
检查结果一共分三个等级:
4、双击可以定位至代码处,并且鼠标放上去会弹出提示信息,在源码里按提示进行修改使符合Java开发规约
5、可以在File->Settings->Editor->inspections按自己要求调整约束,建议默认
6、maven集成pmd插件
代码规范插件实现了开发手册中的53条规则,基本都是基于PMD实现,不仅能扫描工程已有的代码,也能实时检查,及时发现问题代码、规避风险。
在pom文件里添加plugin以及一些规则文件
org.apache.maven.plugins
maven-pmd-plugin
3.13.0
rulesets/java/ali-comment.xml
rulesets/java/ali-concurrent.xml
rulesets/java/ali-constant.xml
rulesets/java/ali-exception.xml
rulesets/java/ali-flowcontrol.xml
rulesets/java/ali-naming.xml
rulesets/java/ali-oop.xml
rulesets/java/ali-orm.xml
rulesets/java/ali-other.xml
rulesets/java/ali-set.xml
true
check
com.alibaba.p3c
p3c-pmd
2.0.1
6、源码修改完成后,可在命令行中执行 mvn verify 进行验证
7、代码提交时检测
在提交代码框勾选Alibaba Code Guidelines选项
如果有违反手册的地方会提示是否继续提交,选择后会自动对修改的代码进行扫描
8、建议开发前先阅读阿里开发手册,附件为Java开发手册黄山版下载链接
https://github.com/alibaba/p3c
9、检测内容简要说明,具体不明白的可参考附件Java开发手册
Ali-Check 全部检测内容:
【】ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException异常。iBATIS自带的queryForList(String statementName,int start,int size)不推荐使用
【】long或者Long初始赋值时,必须使用大写的L,不能是小写的l,小写容易跟数字1混淆,造成误解。
【-】Map/Set的key为自定义对象时,必须重写 hashCode 和 equals。
【】Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。
【-】POJO类中的任何布尔类型的变量,都不要加is,否则部分框架解析会引起序列化错误
【-】POJO类必须写toString()方法。如果继承了另一个POJO类,注意在前面加一下super.toString()。
【-】SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类。
如果是JDK8的应用,可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat
官方给出的解释:simple beautiful strong immutable thread-safe
【】不允许任何魔法值(即未经定义的常量)直接出现在代码中。
【】不能使用过时的类或方法。
接口提供方既然明确是过时接口,那么有义务同时提供新的接口
作为调用方来说,有义务去考证过时方法的新实现是什么
【-】不能在finally块中使用return,finally块中的return返回后方法结束执行,不会再执行try块中的return语句。
【】不要在foreach循环里进行元素的remove/add操作,remove元素请使用Iterator方式。
【】中括号是数组类型的一部分,数组定义如下:String[] args
事务场景中,抛出异常被catch后,如果需要回滚,一定要手动回滚事务。
【-】使用CountDownLatch进行异步转同步操作,每个线程退出前必须调用countDown()方法,线程执行代码注意catch异常,确保countDown()方法可以执行,避免主线程无法执行至await方法,直到超时才返回结果。
注意,子线程抛出异常堆栈,不能在主线程try-catch到
【】使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportedOperationException异常。
【-】使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一样的数组,大小就是list.size()
反例:Integer[] a = (Integer[]) list.toArray();
正例:Integer[] b = (Integer[]) list.toArray(new Integer[list.size()]);
【-】基本数据类型与包装数据类型的使用标准:所有的POJO类属性必须使用包装数据类型;RPC方法的返回值和参数必须使用包装数据类型;所有的局部变量推荐使用基本数据类型。
POJO类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何NPE问题,或者入库检查,都由使用者来保证。
【-】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。创建线程池的时候请使用带ThreadFactory的构造函数,并且提供自定义ThreadFactory实现或者使用第三方实现。
【】包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式
【】单个方法的总行数不超过80行。除注释之外的方法签名、结束右大括号、方法内代码、空行、回车及任何不可见字符的总行数不超过80行。
【】及时清理不再使用的代码段或配置信息。对于垃圾代码或过时配置,坚决清理干净,避免程序过度臃肿,代码冗余。
后台输送给页面的变量必须加感叹号,${var}——中间加感叹号!。如果var=null或者不存在,那么${var}会直接显示在页面上。
【】在if/else/for/while/do语句中必须使用大括号,即使只有一行代码,避免使用下面的形式:if (condition) statements;
【-】在subList场景中,高度注意对原列表的修改,会导致子列表的遍历、增加、删除均产生ConcurrentModificationException异常。
【】在一个switch块内,每个case要么通过break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch块内,都必须包含一个default语句并且放在最后,即使它什么代码也没有。
【】在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。
不要在方法体内定义:Pattern pattern = Pattern.compile(规则);,应该定义为类属性
【-】在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且在lock方法与try代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在finally中无法解锁。
说明一:如果在lock方法与try代码块之间的方法调用抛出异常,那么无法解锁(因为没有try就不会执行finally),造成其它线程无法成功获取锁。
说明二:如果lock方法在try代码块之内,可能由于其它方法抛出异常,导致在finally代码块中,执行unlock时对未加锁的对象解锁,抛出IllegalMonitorStateException异常。
说明三:如果lock方法在try代码块之内,由于Lock对象的lock方法实现中可能抛出unchecked异常,产生与说明二相同的问题。
【-】多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题。
【-】定义DO/DTO/VO等POJO类时,不要加任何属性默认值。
【】对于Service和DAO类,基于SOA的理念,暴露出来的服务一定是接口,内部的实现类用Impl的后缀与接口区别
【】常量命名应该全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长
【】异常类命名使用Exception结尾
【】循环体内,字符串的联接方式,使用StringBuilder的append方法进行扩展。
反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象,造成内存资源浪费。
【-】必须回收自定义的ThreadLocal变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。
【】所有的包装类对象之间值的比较,全部使用equals方法比较。
对于Integer在[-128,128)之间的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断
但是[-128,128)这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。
【】所有的抽象方法(包括接口中的方法)必须要用javadoc注释,除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。
【】所有的枚举类型字段必须要有注释,说明每个数据项的用途。
【】所有的类都必须添加创建者信息。在设置模板时,注意IDEA的@author为${USER},而eclipse为${user},大小写有区别,而日期的设置统一为yyyy/MM/dd的格式。
【】所有的覆写方法,必须加@Override注解。加@Override可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。
【】所有编程相关的命名均不能以下划线或美元符号开始
【】抽象类命名使用Abstract或Base开头
【】方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释。注意与代码对齐。
【】方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase,必须遵从驼峰形式
【】日期格式化字符串[%s]使用错误,应注意使用小写y表示当天所在的年,大写Y代表week in which year。
日期格式化时,yyyy表示当天所在的年,而大写的YYYY代表是week in which year(JDK7之后引入的概念),意思是当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,返回的YYYY就是下一年。
【】注意 Math.random() 这个方法返回是double类型,注意取值的范围[0,1),如果想获取整数类型的随机数,不要将x放大10的若干倍然后取整,直接使用Random对象的nextInt或者nextLong方法。
【】测试类命名以它要测试的类的名称开始,以Test结尾
【-】浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用equals来判断。
浮点数采用尾数+阶码的编码方式,类似于科学计数法的有效数字+指数的表示方式,二进制无法精确表示大部分的十进制小数
【-】禁止使用构造方法BigDecimal(double)的方式把double值转化为BigDecimal对象
反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象,造成内存资源浪费。
反例:BigDecimal bad = new BigDecimal(0.1);
正例:BigDecimal good = new BigDecimal("0.1"); 或 BigDecimal good = BigDecimal.valueOf(0.1);
【】类、类属性、类方法的注释必须使用javadoc规范,使用/**内容*/格式,不得使用//xxx方式和/*xxx*/方式。
【】类名使用UpperCamelCase风格,必须遵从驼峰形式,但以下情形例外:(领域模型的相关命名)DO / BO / DTO / VO / DAO
【-】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
FixedThreadPool和SingleThreadPool:由于允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
CachedThreadPool:由于允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
【】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。
如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者过度切换的问题。
【】获取当前毫秒数:System.currentTimeMillis();而不是new Date().getTime();。如果想获取更加精确的纳秒级时间值,用System.nanoTime。
【】返回类型为基本数据类型,return包装数据类型的对象时,(return null时)自动拆箱有可能产生NPE
【】避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed导致的性能下降。说明:Random实例包括java.util.Random的实例或者Math.random()的方式。
避免用Apache Beanutils进行属性的copy,因为性能较差。可以使用其他方案比如Spring BeanUtils, Cglib BeanCopier。
【】避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。
【】避免采用取反逻辑运算符。取反逻辑不利于快速理解,并且取反逻辑写法必然存在对应的正向逻辑写法。
【】除常用方法(如getXxx/isXxx)等外,不要在条件判断中执行复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量,以提高可读性。
【】集合初始化时,指定集合初始值大小。如果暂时无法确定集合大小,那么指定默认值16即可。