代码不规范,这些问题你一定遇到过

3,880 阅读9分钟

统计了内部一个准备开源的Java项目不规范的代码数量及种类,数据比较敏感,不便公开。但是最经常出现的不规范类型可以说一下。

早先自己也去分享过代码规范,试着猜过哪些代码规范问题可能会犯,但靠猜不能解决问题,用内部的项目插件扫了一下,统计所有出现代码问题如下。

代码规范问题

命名

最常见的问题,代码规范中接近大半的命名问题都有犯。

1 包名应该全部小写

【强制】包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用 单数形式,但是类名如果有复数含义,类名可以使用复数形式。 正例:应用工具类包名为 com.alibaba.ai.util、类名为 MessageUtils(此规则参考 spring 的框架结构)

2 命名不能以_或者$开头

代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。

反例:__name / $name

3 类名使用UpperCamelCase风格

【强制】类名使用 UpperCamelCase 风格,但以下情形例外:DO / BO / DTO / VO / AO /

PO / UID 等。

正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion

反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion

4 方法名没有使用lowerCamelCase风格

【强制】方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从 驼峰形式。 正例: localValue / getHttpMessage() / inputUserId

5 常量命名应该全部大写并且以下划线分隔

【强制】常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。

正例:MAX_STOCK_COUNT 反例:MAX_COUNT

6 抽象类名应该以Abstract或者Base开头

【强制】抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类

命名以它要测试的类的名称开始,以 Test 结尾。

7 POJO布尔类型变量,不要加is前缀

【强制】POJO 类中布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。 反例:定义为基本数据类型 Boolean isDeleted 的属性,它的方法也是 isDeleted(),RPC框架在反向解析的时候,“误以为”对应的属性名称是 deleted,导致属性获取不到,进而抛 出异常。

常量定义

1 魔法值

【强制】不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。

反例:

String key = "Id#taobao_" + tradeId;

cache.put(key, value);

2 Long类型的值,要以大写的L结尾

【强制】在 long 或者 Long 赋值时,数值后使用大写的 L,不能是小写的 l,小写容易跟数字 1 混淆,造成误解。 说明:Long a = 2l; 写的是数字的 21,还是 Long 型的 2?

3 不要一个类维护所有的常量

【推荐】不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。 说明:大而全的常量类,杂乱无章,使用查找功能才能定位到修改的常量,不利于理解和维护。

正例:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类 ConfigConsts 下。

4 应该使用常量或者确认值的内容来调用equals

【强制】Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。

正例:"test".equals(object);

反例:object.equals("test");

说明:推荐使用 java.util.Objects#equals(JDK7 引入的工具类)

代码格式

1 代码行数太长

【推荐】单个方法的总行数不超过80行。有效代码行数40行左右

说明:包括方法签名、结束右大括号、方法内代码、注释、空行、回车及任何不可见字符的总行数不超过80

行。

正例:代码逻辑分清红花和绿叶,个性和共性,绿叶逻辑单独出来成为额外方法,使主干代码 更加清晰;共性逻辑抽取成为共性方法,便于复用和维护。

2 文件编码不统一

【强制】IDE 的 text file encoding 设置为 UTF-8; IDE 中文件的换行符使用 Unix 格式,不要使用 Windows 格式。

集合处理

1 初始化HashMap等集合时,尽量指定初始值大小

【推荐】集合初始化时,指定集合初始值大小。 说明:HashMap 使用 HashMap(int initialCapacity) 初始化。

正例:initialCapacity=(需要存储的元素个数 / 负载因子) + 1。注意负载因子(即 loaderfactor)默认为0.75,如果暂时无法确定初始值大小,请设置为16(即默认值)。

反例:HashMap需要放置1024个元素,由于没有设置容量初始大小,随着元素不断增加,容量 7 次被迫扩大,resize需要重建hash表,严重影响性能。

2 遍历Map的方式

【推荐】使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。

说明:keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出 key 所对应的 value。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用 Map.foreach 方法。 正例:values()返回的是 V 值集合,是一个 list 集合对象;keySet()返回的是 K 值集合,是

一个 Set 集合对象;entrySet()返回的是 K-V 值组合集合。

控制语句

1 控制层数不要太高

表达异常的分支时,少用 if-else 方式,这种方式可以改写

if (condition) {

​ ...

​ return obj;

}

如果非得使用 if()...else if()...else...方式表达逻辑,避免后续维护护困难,请勿超过 3 层。

超过 3 层的 if-else 的逻辑判断代码可以 其中卫语句示例如下:

public void today() {

​ if (isBusy()) {

​ System.out.println(“change time.”);

​ return;

​ }

​ if (isFree()) {

​ System.out.println(“go to travel.”);

​ return;

​ }

​ System.out.println(“stay at home to learn Alibaba Java Coding Guidelines.”);

​ return;

}

2 不要在条件中使用复杂的表达式

【推荐】除常用方法(如 getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将 复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。 说明:很多 if 语句内的逻辑相当复杂,阅读者需要分析条件表达式的最终结果,才能明确什么 样的条件执行什么样的语句,那么,如果阅读者分析逻辑表达式错误呢? 正例:

// 伪代码如下

final boolean existed = (file.open(fileName, "w") != null) && (...) || (...);

if (existed) {

...

}

反例:

if ((file.open(fileName, "w") != null) && (...) || (...)) {

...

}

3 if语句缺少大括号

【强制】在 if/else/for/while/do 语句中必须使用大括号。即使只有一行代码,避 单行的编码方式:if (condition) statements;

注释规约

1 使用行尾注释

【强制】方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释 使用/* */注释,注意与代码对齐。

2 缺少@author信息

【强制】所有的类都必须添加创建者和创建日期。

@author

@date

3 使用Javadoc注释

【强制】类、类属性、类方法的注释必须使用 Javadoc 规范,使用/*内容/格式,不得使用 // xxx 方式。

说明:在 IDE 编辑窗口中,Javadoc 方式会提示相关注释,生成 Javadoc 可以正确输出相应注 释;在 IDE 中,工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高 阅读效率。

8 及时清理不再使用的代码段或配置信息

【推荐】及时清理不再使用的代码段或配

说明:对于垃圾代码或过时配置,坚决清理干净,避免程序过度臃肿,代码冗余。

正例:对于暂时被注释掉,后续可能恢复使用的代码片断,在注释代码上方,统一规定使用三 个斜杠(///)来说明注释掉代码的理由。

4 方法的参数缺少javadoc注释

【强制】所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数、 异常说明外,还必须指出该方法做什么事情,实现什么功能。

说明:对子类的实现要求,或者调用注意事项,请一并说明。

5 枚举字段缺少注释

【强制】所有的枚举类型字段必须要有注释,说明每个数据项的用途。

其它

1 循环体内字符串的拼接采用StringBuilder的方式

【推荐】循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展。 说明:下例中,反编译出的字节码文件显示每次循环都会 new 出一个 StringBuilder 对象, 然后进行 append 操作,最后通过 toString 方法返回 String 对象,造成内存资源浪费。

反例:

String str = "start";

for (int i = 0; i < 100; i++) {

str = str + "hello";

}

2 显示创建线程池

【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决 资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或 者“过度切换”的问题。

3 参数列表长度过长

【强制】相同参数类型,相同业务含义,才可以使用 Java 的可变参数,避免使用 Object。 说明:可变参数必须放置在参数列表的最后。(提倡同学们尽量不用可变参数编程) 正例:public List listUsers(String type, Long... ids) {...}

4 多个参数进行换行

方法调用中的多个参数需要换行时,在逗号后进行。

5 重定向问题

【参考】服务器内部重定向使用 forward;外部重定向地址使用 URL 拼装工具类来生成,否则 会带来 URL 维护不一致的问题和潜在的安全风险。

最后

以上为写代码时最常犯的问题,以及在代码规范上提出的处理措施。