美文网首页
Java代码规范中应该注意这几点,Java程序员建议观看收藏

Java代码规范中应该注意这几点,Java程序员建议观看收藏

作者: 卑微小徐在线敲代码 | 来源:发表于2019-07-15 10:51 被阅读0次

一、前言

本文参考《阿里巴巴Java开发手册》,这本书主要定义了一些代码的规范以及一些注意事项。我只根据我自己的不足,摘录了一些内容,方便以后查阅。

二、读书笔记

命名

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

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

3、抽象类命名使用 Abstract 或 Base 开头; 异常类命名使用 Exception 结尾; 测试类命名以它要测试的类的名称开始,以 Test 结尾。

4、中括号是数组类型的一部分,数组定义如下: String[] args;

5、POJO 类中布尔类型的变量,都不要加 is,否则部分框架解析会引起序列化错误。

反例: 定义为基本数据类型 Boolean isDeleted; 的属性,它的方法也是 isDeleted()。 RPC框架在反向解析的时候, “以为”对应的属性名称是 deleted,导致属性获取不到,进而抛出异常。

6、包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。

7、如果使用到了设计模式,建议在类名中体现出具体模式。有利于阅读者快速理解架构设计思想。

8、接口类中的方法和属性不要加任何修饰符号(public 也不要加) ,保持代码的简洁性,并加上有效的 Javadoc 注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。

9、枚举类名建议带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。

说明: 枚举其实就是特殊的常量类,且构造方法默认强制为私有。

正例: 枚举名字: DealStatusEnum, 成员名称: SUCCESS / UNKOWN_REASON。

10、各层命名规约:A) Service/DAO 层方法命名规约

1) 获取单个对象的方法用 get 做前缀。

2) 获取多个对象的方法用 list 做前缀。

3) 获取统计值的方法用 count 做前缀。

4) 插入的方法用 save(推荐) 或 insert 做前缀。

5) 删除的方法用 remove(推荐) 或 delete 做前缀。

6) 修改的方法用 update 做前缀。

B) 领域模型命名规约

1) 数据对象: xxxDO, xxx 即为数据表名。

2) 数据传输对象: xxxDTO, xxx 为业务领域相关的名称。

3) 展示对象: xxxVO, xxx 一般为网页名称。

4) POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO。

常量定义

1、不允许任何未经定义的常量直接出现在代码中。反例: String key = "Id#taobao_" + tradeId;cache.put(key, value);

2、long 或者 Long 初始赋值时,必须使用大写的 L,不能是小写的 l,小写容易跟数字1 混淆,造成误解。

3、不要使用一个常量类维护所有常量,应该按常量功能进行归类,分开维护。

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

OOP规约

1、避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。

2、外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加@Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么。

3、不能使用过时的类或方法。

4、Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals

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

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

5、所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。对于 Integer var = ? 在-128 至 127 范围内的赋值, Integer 对象是在IntegerCache.cache 产生,会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方法进行判断。

6、关于基本数据类型与包装数据类型的使用标准如下

:1) 【强制】 所有的 POJO 类属性必须使用包装数据类

2) 【强制】 RPC 方法的返回值和参数必须使用包装数

3) 【推荐】 所有的局部变量使用基本数据类型。说明:用基本数据类型数据默认值是0,而包装数据类型默认值是null,数据库的查询结果可能为null,因为自动拆箱,用基本数据类型接收有NPE风险。

7、定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值(在数据提取时并没有置入具体值,在更新其它字段时又附带更新了此字段,导致创建时间被修改成当前时间。)

8、序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列失败; 如果完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值。注意 serialVersionUID 不一致会抛出序列化运行时异常。

9、构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。

10、类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter方法。

11、循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展。

12、慎用 Object 的 clone 方法来拷贝对象。对象的 clone 方法默认是浅拷贝,若想实现深拷贝需要重写 clone 方法实现属性对象的拷贝。

13、工具类不允许有 public 或 default 构造方法。

集合处理

1、ArrayList的subList结果不可强转成ArrayList,否则会抛出 ClassCastException异常: java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList ,而是ArrayList 的一个视图,对于 SubList 子列表的所有操作最终会反映到原列表上。

2、在 subList 场景中, 高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、删除均产生 ConcurrentModificationException 异常。

3、使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一样的数组,大小就是 list.size()。

说明: 使用 toArray 带参方法,入参分配的数组空间不够大时, toArray 方法内部将重新分配内存空间,并返回新数组地址; 如果数组元素大于实际所需,下标为[ list.size() ]的数组元素将被置为 null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集合元素个数一致。

正例:

Listlist = new ArrayList(2);

list.add("guan");

list.add("bao");

String[] array = new String[list.size()];

array = list.toArray(array);

反例: 直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现 ClassCastException 错误

4、使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。

说明: asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。

5、泛型通配符? extends T来接收返回的数据,此写法的泛型集合不能使用 add 方法, 而? super T不能使用 get 方法,做为接口调用赋值时易出错。

说明: 扩展说一下 PECS(Producer Extends Consumer Super)原则:

1) 频繁往外读取内容的,适合用上界 Extends。

 2) 经常往里插入的,适合用下界 Super。

6、不要在 foreach 循环里进行元素的 remove/add 操作。 remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。

正例:

Iteratorit = a.iterator();

while (it.hasNext()) {

String temp = it.next();

if (删除元素的条件) {

it.remove();

}

}

反例:

Lista = new ArrayList();

a.add("1");

a.add("2");

for (String temp : a) {

if ("1".equals(temp)) {

a.remove(temp);

}

}

7、集合初始化时, 指定集合初始值大小。

说明: HashMap 使用 HashMap(int initialCapacity) 初始化,

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

8、使用 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 值组合集合。

10、高度注意 Map 类集合 K/V 能不能存储 null 值的情况,如下表格:

Hashtable不允许为 null不允许为 nullDictionary线程安全ConcurrentHashMap不允许为 null不允许为 nullAbstractMap分段锁技术TreeMap不允许为 null允许为 nullAbstractMap线程不安全HashMap允许为 null允许为 nullAbstractMap线程不安全

反例: 由于 HashMap 的干扰,很多人认为 ConcurrentHashMap 是可以置入 null 值,而事实上,存储 null 值时会抛出 NPE 异常。

控制语句

1、在 if/else/for/while/do 语句中必须使用大括号。 即使只有一行代码,避免使用单行的形式: if (condition) statements;

2、表达异常的分支时, 少用 if-else 方式。如果非得使用 if()...else if()...else...方式表达逻辑,避免后续代码维护困难, 请勿超过 3 层。

3、除常用方法(如 getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。

正例:

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

if (existed) {

...

}

反例:

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

...

}

异常处理

1、Java 类库中定义的一类 RuntimeException 可以通过预先检查进行规避,而不应该通过 catch 来处理,比如: IndexOutOfBoundsException, NullPointerException 等等。

正例: if (obj != null) {...}

反例: try { obj.method() } catch (NullPointerException e) {...}

2、异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低。

3、有 try 块放到了事务代码中, catch 异常后,如果需要回滚事务,一定要注意手动回滚事务。

4、不能在 finally 块中使用 return, finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句。

5、防止空指针异常,是程序员的基本修养。

6、避免出现重复的代码(Don’t Repeat Yourself) ,即 DRY 原则。

说明: 随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是共用模块。

日志规约

1、应用中不可直接使用日志系统(Log4j、 Logback) 中的 API,而应依赖使用日志框架SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。private static final Logger logger = LoggerFactory.getLogger(Abc.class);

2、日志文件推荐至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。

3、对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。说明: logger.debug("Processing trade with id: " + id + " symbol: " + symbol);如果日志级别是 warn,上述日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象,会执行 toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。

正例: (条件)

if (logger.isDebugEnabled()) {

logger.debug("Processing trade with id: " + id + " symbol: " + symbol);

}

正例: (占位符)

logger.debug("Processing trade with id: {} symbol : {} ", id, symbol);

4、避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。

5、谨慎地记录日志。生产环境禁止输出 debug 日志; 有选择地输出 info 日志; 如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志 。

其他

1、在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。

说明: 不要在方法体内定义: Pattern pattern = Pattern.compile(规则);

2、后台输送给页面的变量必须加$!{var}——中间的感叹号。

说明: 如果 var=null 或者不存在,那么${var}会直接显示在页面上。

3、不要在视图模板中加入任何复杂的逻辑。说明: 根据 MVC 理论,视图的职责是展示,不要抢模型和控制器的活。

4、任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存。

5、对于“明确停止使用的代码和配置”,如方法、变量、类、配置文件、动态配置属性等要坚决从程序中清理出去,避免造成过多垃圾。

6、SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为static,必须加锁,或者使用 DateUtils 工具类。

正例: 注意线程安全,使用DateTools 。亦推荐如下处理:

public class DateTools

{

private static ThreadLocal t1=new ThreadLocal<>();

 public static SimpleDateFormat getSdf(String pattern){

 SimpleDateFormat simpleDateFormat = t1.get();

 if (simpleDateFormat==null){

simpleDateFormat=new SimpleDateFormat(pattern);

 }

 t1.set(simpleDateFormat);

 return simpleDateFormat;

}

}

 说明: 如果是 JDK8 的应用,可以使用 Instant 代替 Date, LocalDateTime 代替 Calendar,DateTimeFormatter代替Simpledateformatter,官方给出的解释:simple beautiful strong immutable thread-safe。

读到此刻


是不是觉得Java很伟大?Java如此重要,所以在排行榜的第一名也是当之无愧。

小编为大家准备了一套资料,方便大家的学习。资料整理不易

这里是Java程序员的交流群894180257,群里也为大家准备了很多资料,以及视频,还有更多大佬讲解。

相关文章

网友评论

      本文标题:Java代码规范中应该注意这几点,Java程序员建议观看收藏

      本文链接:https://www.haomeiwen.com/subject/zepgkctx.html