前段时间针对公司老项目做了一次代码规范质量检查,采用的是阿里巴巴代码检测插件进行检测,检查结果并不理想,老项目比较重,开发迭代人员比较多,本文就是对这些检查点做一个记录,方便之后回顾。
Alibaba代码检测插件是2017年开发的,无论是IDEA还是Android Studio都可以安装使用,深受开发者好评。androidstudio可以直接搜索插件Alibaba Java Coding Guidelines
进行安装使用。
Blocker
-
if、for语句必须采用闭包形式,不允许采用无括号形势编写。
-
在使用正则表达式时,利用好其预编译功能,可以有效加快编译速度。
说明:不要在方法体内定义:Pattern pattern = Pattern.compile(规则);
public class XxxClass { // Use precompile private static Pattern NUMBER_PATTERN = Pattern.compile("[0-9]+"); public Pattern getNumberPattern() { // Avoid use Pattern.compile in method body. Pattern localPattern = Pattern.compile("[0-9]+"); return localPattern; } }
-
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
a. FixedThreadPool和SingleThreadPool: 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。 b. CachedThreadPool: 允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
个人开发建议,全局封装一个ThreadPoolExecutor线程池工具类,统一管理线程池申请。
Critical
-
Map/Set的key为自定义对象时,必须重写hashCode和equals。
关于hashCode和equals的处理,遵循如下规则:
a. 只要重写equals,就必须重写hashCode。 b. 因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法。 c. 如果自定义对象做为Map的键,那么必须重写hashCode和equals。
-
Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。
-
不能使用过时的类或方法。
-
创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。创建线程池的时候请使用带ThreadFactory的构造函数,并且提供自定义ThreadFactory实现或者使用第三方实现。
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() .setNameFormat("demo-pool-%d").build(); ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); singleThreadPool.execute(()-> System.out.println(Thread.currentThread().getName())); singleThreadPool.shutdown(); public class TimerTaskThread extends Thread { public TimerTaskThread(){ super.setName("TimerTaskThread"); … }
个人开发建议,全局封装一个ThreadPoolExecutor线程池工具类,暴露初始化线程名称定义。
-
在一个switch块内,每个case要么通过break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch块内,都必须包含一个default语句并且放在最后,即使它什么代码也没有。
-
常量命名应该全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
-
异常类命名使用Exception结尾。
-
所有枚举类型字段必须要有注释,说明每个数据项的用途。
-
所有编程相关的命名均不能以下划线或者美元符号命名。
-
抽象类命名使用Abstract或Base开头,抽象类一般需要被继承使用的。
-
方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase,必须遵从小驼峰形式
-
浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用equals来判断。 浮点数采用“尾数+阶码”的编码方式,类似于科学计数法的“有效数字+指数”的表示方式。二进制无法精确表示大部分的十进制小数,具体原理参考《码出高效》。
改进方式:1)指定一个误差范围,两个浮点数的差值在此范围之内,则认为是相等的 float a = 1.0f - 0.9f; float b = 0.9f - 0.8f; float diff = 1e-6f; if (Math.abs(a - b) < diff) { System.out.println("true"); } 2) 使用BigDecimal来定义值,再进行浮点数的运算操作 BigDecimal a = new BigDecimal("1.0"); BigDecimal b = new BigDecimal("0.9"); BigDecimal c = new BigDecimal("0.8"); BigDecimal x = a.subtract(b); BigDecimal y = b.subtract(c); if (x.equals(y)) { System.out.println("true"); } Negative example: float g = 0.7f-0.6f; float h = 0.8f-0.7f; if (g == h) { System.out.println("true"); } Positive example: double dis = 1e-6; double d1 = 0.0000001d; double d2 = 0d; System.out.println(Math.abs(d1 - d2) < dis);
-
线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
Major
- 不允许任何魔法值(即未经定义的常量)直接出现在代码中。
Negative example: //Magic values, except for predefined, are forbidden in coding. if (key.equals("Id#taobao_1")) { //... } Positive example: String KEY_PRE = "Id#taobao_1"; if (KEY_PRE.equals(key)) { //... }
- 中括号是数组类型的一部分,数组定义如下:String[] args
Negative example:
String extArrayString[] = { ".amr", ".ogg", ".mp3", ".aac", ".ape",
".flac", ".wma", ".wav", ".mp2", ".mid", ".3gpp" };
-
包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。
个人建议,如果遇到包名必须用两个或以上的单词才能表达,可以采用Spring的命名方式,两个单词用点分割;开发过程中我们尽量采用一个单词进行表达。
-
单个方法的总行数不超过80行。
说明:除注释之外的方法签名、结束右大括号、方法内代码、空行、回车及任何不可见字符的总行数不超过80行。
-
及时清理不再使用的代码段或配置信息。
说明:对于垃圾代码或过时配置,坚决清理干净,避免程序过度臃肿,代码冗余。
-
循环体内,字符串的联接方式,使用StringBuilder的append方法进行扩展。
说明:反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象,造成内存资源浪费。
Negative example: String result; for (String string : tagNameList) { result = result + string; } Positive example: StringBuilder stringBuilder = new StringBuilder(); for (String string : tagNameList) { stringBuilder.append(string); } String result = stringBuilder.toString();
-
所有的抽象方法(包括接口中的方法)必须要用javadoc注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。
说明:如有实现和调用注意事项,请一并说明。
-
方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释。注意与代码对齐。
public void method() { // Put single line comment above code. (Note: align '//' comment with code) int a = 3; /** * Some description about follow code. (Note: align '/**' comment with code) */ int b = 4; }
-
类、类属性、类方法的注释必须使用javadoc规范,使用/*内容/格式,不得使用//xxx方式和/xxx/方式。 说明:在IDE编辑窗口中,javadoc方式会提示相关注释,生成javadoc可以正确输出相应注释;在IDE中,工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高阅读效率。
/** * * XXX class function description. * */ public class XxClass implements Serializable { private static final long serialVersionUID = 113323427779853001L; /** * id */ private Long id; /** * title */ private String title; /** * find by id * * @param ruleId rule id * @param page start from 1 * @return Result<Xxxx> */ public Result<Xxxx> funcA(Long ruleId, Integer page) { return null; } }
-
类名使用UpperCamelCase风格,必须遵从驼峰形式,但以下情形例外:(领域模型的相关命名)DO / BO / DTO / VO / DAO
-
除常用方法(如getXxx/isXxx)等外,不要在条件判断中执行复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量,以提高可读性。
说明:很多if语句内的逻辑相当复杂,阅读者需要分析条件表达式的最终结果,才能明确什么样的条件执行什么样的语句,那么,如果阅读者分析逻辑表达式错误呢?
Negative example: if ((file.open(fileName, "w") != null) && (...) || (...)) { // ... } Positive example: boolean existed = (file.open(fileName, "w") != null) && (...) || (...); if (existed) { //... }
-
集合初始化时,指定集合初始值大小。
说明:HashMap使用如下构造方法进行初始化,如果暂时无法确定集合大小,那么指定默认值(16)即可。
Negative example: Map<String, String> map = new HashMap<String, String>(); Positive example: Map<String, String> map = new HashMap<String, String>(16);