读《阿里Java开发手册》总结(2)

枚举成员名称需要全大写,单词间用下划线隔开 SUCCESS

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

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

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

大写的 L Long a = 2L

子工程内部共享常量:即在当前子工程的 constant 目录下。

包内共享常量:即在当前包下单独的 constant 目录下。

类内共享常量:直接在类内部 private static final 定义

任何二目、三目运算符的左右两边都需要加一个空格。

单句换行时不要在逗号或者括号前换行

1.尽量少用可变长度的参数  Long... ids

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

3. 浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。原因是,1.0000001 和 1.0 这两个数在绝大多数的情况下,认为它们是相等的

(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");

    }

(1)  使用 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");

    }

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

5.数据库字段的 bigint 必须与类属性的 Long 类型相对应。(若是用integer,随着 id 越来越大,超过 Integer 的表示范围而溢出成为负数。)

6.  BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。

推荐:BigDecimal recommend1 = new BigDecimal("0.1");

    BigDecimal recommend2 = BigDecimal.valueOf(0.1);

7。RPC 方法的返回值和参数必须使用包装数据类型。推荐】所有的局部变量使用基本数据类型。包装类型可为null

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

9.序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列失败;如果完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值。

10.toString 时,如果继承了另一个 POJO 类,注意在前面加一下 super.toString。

11.String 的 split 方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛 IndexOutOfBoundsException 的风险。

说明:

String str = "a,b,c,,";

String[] ary = str.split(",");

// 预期大于 3,结果是 3

System.out.println(ary.length);

12.慎用 Object 的 clone 方法来拷贝对象。

说明:对象 clone 方法默认是浅拷贝,若想实现深拷贝需覆写 clone 方法实现域对象的深度遍历式拷贝。

13. 工具类不允许有 public 或 default 构造方法。 类非 static 成员变量并且与子类共享,必须是 protected。

14.只要覆写 equals,就必须覆写 hashCode。因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须覆写这两个方法。如果自定义对象作为 Map 的键,那么必须覆写 hashCode 和 equals。说明:String 已覆写 hashCode 和 equals 方法,所以我们可以愉快地使用 String 对象作为 key 来使用

15.使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组。

16.  在使用 Collection 接口任何实现类的 addAll()方法时,都要对输入的集合参数进行 NPE 判断。

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

说明:扩展说一下 PECS(Producer Extends Consumer Super)原则:第一、频繁往外读取内容的,适合用<? extends T>。第二、经常往里插入的,适合用<? super T>

18.在无泛型限制定义的集合赋值给泛型限制的集合时,在使用集合元素时,需要进行 instanceof 判断,避免抛出 ClassCastException 异常。

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

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

21.创建线程或线程池时请指定有意义的线程名称,方便出错时回溯

22.线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

23.5.    SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为 static,必须加锁,或者使用 DateUtils 工具类。正例:注意线程安全,使用 DateUtils。亦推荐如下处理:

private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {     

 @Override        protected DateFormat initialValue() {            return new SimpleDateFormat("yyyy-MM-dd");      

}  

};  

说明:如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,

DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe。

24.必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。

尽量在代理中使用 try-finally 块进行回收。正例:

objectThreadLocal.set(userInfo); try {     // ... } finally {     objectThreadLocal.remove();

}

25.能用对象锁,就不要用类锁,   尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法。

26.对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。说明:线程一需要对表 A、B、C 依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序也必须是 A、B、C,否则可能出现死锁。

27.在使用阻塞等待获取锁的方式中,必须在 try 代码块之外,并且在加锁方法与 try 代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁

28.. volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。

29.HashMap 在容量不够进行 resize 时由于高并发可能出现死链,导致 CPU 飙升,在开发过程中可以使用其它数据结构或加锁来规避此风险。

30. ThreadLocal 对象使用 static 修饰,ThreadLocal 无法解决共享对象的更新问题。说明:这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享此静态变量,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义

的)都可以操控这个变量。

11. 在并发场景下,通过双重检查锁(double-checked locking)实现延迟初始化的优化

问题隐患(可参考 The "Double-Checked Locking is Broken" Declaration),推荐解决方案中较为简单一种(适用于 JDK5 及以上版本),将目标属性声明为 volatile 型。

31.线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这

样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors 返回的线程池对象的弊端如下:

1) FixedThreadPool 和 SingleThreadPool:

允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

2) CachedThreadPool:

允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为

static,必须加锁

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

private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {

@Override

protected DateFormat initialValue() {

return new SimpleDateFormat("yyyy-MM-dd");

}

};

如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,

DateTimeFormatter 代替 SimpleDateFormat

必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,

如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。

尽量在代理中使用 try-finally 块进行回收。

objectThreadLocal.set(userInfo);

try {

// ...

} finally {

objectThreadLocal.remove();

}

并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存

加锁,要么在数据库层使用乐观锁,使用 version 作为更新依据。

说明:如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于

3 次。

进行参数校验:

1.对外提供的开放接口,不管是 RPC/API/HTTP 接口。

2.  需要极高稳定性和可用性的方法。

条件不用取反逻辑1,即 !条件

类、类属性、类方法的注释 使用/**内容*/格式

所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释

1.    方法内部多行注释使用/* */注释,注意与代码对齐。

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

利用好正则表达式预编译功能,提高匹配速度

Pattern要定义为static final静态变量,以避免执行多次预编译

不能在方法体内定义Pattern pattern = Pattern.compile(“规则”);(每次需要用正则去匹配的时候,都需要把pattern编译一遍,效率低)

这样不是用预编译也是不行的:Pattern.matches(regexRule, content)

如果是 boolean 基本数据类型变量(boolean 命名不需要加 is 前缀),会自动调用 isXxx()方法。

velocity模板引擎:  后台输送给页面的变量必须加$!{var}——中间的感叹号。

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

获取当前毫秒数 System.currentTimeMillis()  纳秒级 System.nanoTime()

JDK8 中,针对统计时间等场景,推荐使用 Instant 时间戳类:https://blog.csdn.net/weixin_42124622/article/details/81944113

new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

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

来说明注释掉代码的理由。

NullPointerException不要去catch,参数检测

不要对大段代码进行 try-catch,无法对不同异常做出相应处理,也不利于定位问题

捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。

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

https://blog.csdn.net/qq_40784783/article/details/88060747

   

spring在捕获RuntimeException异常后,才会进行事务回滚。

故,在catch代码块中,添加:

} catch (Exception e) {

                e.printStackTrace();

                map.put("number", 0);

                // 手动设置当前事务回滚

                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

                // 或者手动抛出runTimeException

              throw new RuntimeException();

            }

不要在 finally 块中使用 return。无情丢弃掉 try 块中的返回点。

在调用 RPC、二方包、或动态生成类的相关方法时,捕捉异常必须使用 Throwable 类来进行拦截,可能抛出error

使用 JDK8 的 Optional 类来防止 NPE 问题https://www.cnblogs.com/rjzheng/p/9163246.html

当value值为null时,of(T value)会报NullPointerException异常;ofNullable(T value)不会throw Exception,ofNullable(T value)直接返回一个EMPTY对象。

不想隐藏NullPointerException。而是要立即报告,这种情况下就用Of函数。但是不得不承认,这样的场景真的很少

避免直接抛出 new RuntimeException(),更不允许抛出 Exception 或者 Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如:DAOException / ServiceException 等

使用 Result 方式,封装 isSuccess()方法、“错误码”、“错误简短信息”。

尽量复制和粘贴代码(抽取)

不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架  SLF4J 中的 API

所有日志文件至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。网络运行状态、安全相关信息、系统监测、管理后台操作、用户敏感操作需要留存相关的网络日志不少于 6 个月。

日志(如打点、临时监控、访问日志等)命名方式 appName_logType_logName.log。logType,:logType,如 stats/monitor/access 等;logName:日志描述

force-web 应用中单独监控时区转换异常,如:force_web_timeZoneConvert.log

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

异常信息应记录 案发现场信息和异常堆栈信息

logger.error(各类参数或者对象 toString() + "_" + e.getMessage(), e);

生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量 并记得及时删除这些观察日志

可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。如非必要,请不要在此场景打出 error 级别,避免频繁报警。 

对于trace/debug/info级别的日志输出,必须进行日志级别的开关判断

// 如果判断为真,那么可以输出trace和debug级别的日志

      if (logger.isDebugEnabled()) {            logger.debug("Current ID is: {} and name is: {}", id, getName());     }

单元测试:不符合业务插入规则,导致测试结果异常。 按常规来

禁止向 HTML 页面输出未经安全过滤或未正确转义的用户数据。 (不管显不显示)

数据库:

表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint(1 表示是,0 表示否)。UNSIGNED属性就是将数字类型无符号化

任何字段如果为非负数,必须是 unsigned。

POJO 类中的任何布尔类型的变量,都不要加 is 前缀

数据库名、表名、字段名,都不允许出现任何大写字母

表名不使用复数名词

小数类型为 decimal,禁止使用 float 和 double。float 和 double 都存在精度损失的问题,如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数并分开存储,对应java的BigDecimal

•       Manager 层:通用业务处理层,它有如下特征:

1)  对第三方平台封装的层,预处理返回结果及转化异常信息。

2)  对 Service 层通用能力的下沉,如缓存方案、中间件通用处理。

3)  与 DAO 层交互,对多个 DAO 的组合复用。

1.    DAO 层,产生的异常类型有很多,无法用细粒度的异常进

行 catch,使用 catch(Exception e)方式,并 throw new DAOException(e),不需要打印日志,因为日志在 Manager/Service 层一定需要捕获并打印到日志文件中去,如果同台服务器再打日志,浪费性能和存储。在 Service 层出现异常时,必须记录出错日志到磁盘,尽可能带上参数信息,相当于保护案发现场。如果 Manager 层与 Service 同机部署,日志方式与 DAO 层处理一致,如果是单独部署,则采用与 Service 一致的处理方式。Web 层绝不应该继续往上抛异常,如果意识到这个异常将导致页面无法正常渲染,那么就应该直接跳转到友好错误页面

开放接口层要将异常处理成错误码和错误信息方式返回。

•       DO(Data Object):此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。

•       DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象。

•       BO(Business Object):业务对象,由 Service 层输出的封装业务逻辑的对象。

•       AO(Application Object):应用对象,在 Web 层与 Service 层之间抽象的复用对象模型,极为贴近展示层,复用度不高。

•       VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。

•       Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类来传输。

utils层:工具类层,通用的、与业务无关的,可以独立出来,可供其他项目使用;

tools层:与某些业务有关,通用性只限于某几个业务类之间;

manager层:通用业务处理层,它有如下特征,对第三方平台封装的层,预处理返回结果及转化异常信息; 对 Service 层通用能力的下沉,如缓存方案、中间件通用处理;与 DAO 层交互,对多个 DAO 的组合复用。

service层:业务处理层,在大系统中,该层比较复杂,故可抽取出通用处理层(manager层),并且一个service层可以对应多个manager层,但小系统的话,往往没必要抽取出manager层,一个service层足够了。

helper层:辅助类层,一般是一些功能辅助,如SqlHelper封装数据库连接操作提供数据库操作对象,ConfigHelper帮助创建配置信息用于模块初始化构建,其实作用与工具类很像,但没有工具类通用性好。

猜你喜欢

转载自www.cnblogs.com/shineipangtuo/p/12395588.html