阅读阿里巴巴Java开发手册(嵩山版) 记录一些不熟悉的强制规范
(一) 命名风格
-
【强制】POJO 类中的任何布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列 化错误。
-
【推荐】接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁 性,并加上有效的 Javadoc 注释。尽量不要在接口里定义变量,如果一定要定义变量,确定 与接口方法相关,并且是整个应用的基础常量。
-
接口方法签名 void commit(); 接口基础常量 String COMPANY = "alibaba";
-
-
【参考】各层命名规约:
- 1) 数据对象:xxxDO,xxx 即为数据表名。
- 2) 数据传输对象:xxxDTO,xxx 为业务领域相关的名称。
- 3) 展示对象:xxxVO,xxx 一般为网页名称。
- 4) POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO。
(二) 常量定义
- 【强制】==在 long 或者 Long 赋值时,数值后使用大写字母 L,==不能是小写字母 l,小写容易跟 数字混淆,造成误解。
(四) OOP 规约
-
【强制】不能使用过时的类或方法。
(这个有待商榷,有时不得不用) -
【强制】Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。
-
推荐使用 JDK7 引入的工具类 java.util.Objects#equals(Object a, Object b)
-
-
【强制】任何货币金额,均以最小货币单位且整型类型来进行存储。
-
【强制】所有整型包装类对象之间值的比较,全部使用 equals 方法比较。
说明:对于 Integer var = ? 在
-128 至 127
之间的赋值,Integer 对象是在IntegerCache.cache
产生, 会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数据,都 会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方法进行判断。 -
【强制】浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。
二进 制无法精确表示大部分的十进制小数
-
【强制】如上所示 BigDecimal 的等值比较应使用 compareTo()方法,而不是 equals()方法。
equals()方法会比较值和精度(1.0 与 1.00 返回结果为 false),而 compareTo()则会忽略精度。
-
【强制】禁止使用构造方法 BigDecimal(double)的方式把 double 值转化为 BigDecimal 对象。 有精度损失
- 优先推荐入参为 String 的构造方法,或使用 BigDecimal 的 valueOf 方法
-
【强制】定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值。
(五) 日期时间
-
【强制】日期格式化时,传入 pattern 中表示年份统一使用小写的 y
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
-
【强制】获取当前毫秒数:System.currentTimeMillis();
-
【强制】不允许在程序任何地方中使用:1)java.sql.Date。 2)java.sql.Time。 3)java.sql.Timestamp。
-
【强制】不要在程序中写死一年为 365 天,避免在公历闰年时出现日期转换错误或程序逻辑 错误。
// 获取今年的天数 int daysOfThisYear = LocalDate.now().lengthOfYear(); // 获取指定某年的天数 LocalDate.of(2011, 1, 1).lengthOfYear();
-
【推荐】使用枚举值来指代月份。如果使用数字,注意 Date,Calendar 等日期相关类的月份 month 取值在 0-11 之间。
(六) 集合处理
-
【强制】判断所有集合内部的元素是否为空,使用 isEmpty()方法,而不是 size()==0 的方式。
在某些集合中,前者的时间复杂度为 O(1),而且可读性更好。
-
【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。
List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String item = iterator.next(); if (删除元素的条件) { iterator.remove(); } }
-
【推荐】高度注意 Map 类集合 K/V 能不能存储 null 值的情况,如下表格:
(七) 并发处理
-
【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。 如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题
-
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这 样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2) CachedThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
-
【强制】必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用, 如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。 尽量在代理中使用 try-finally 块进行回收。
objectThreadLocal.set(userInfo); try { // ... } finally { objectThreadLocal.remove(); }
-
【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能 锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
-
【强制】在使用阻塞等待获取锁的方式中,必须在 try 代码块之外,并且在加锁方法与 try 代 码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁。
Lock lock = new XxxLock(); // ... lock.lock(); try { doSomething(); doOthers(); } finally { lock.unlock(); }
-
【强制】在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否 持有锁。锁的释放规则与锁的阻塞等待方式相同。
Lock lock = new XxxLock(); // ... boolean isLocked = lock.tryLock(); if (isLocked) { try { doSomething(); doOthers(); } finally { lock.unlock(); } }
-
【强制】并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加 锁,要么在数据库层使用乐观锁,使用 version 作为更新依据。
如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于 3 次。
-
【强制】多线程并行处理定时任务时,Timer 运行多个 TimeTask 时,只要其中之一没有捕获抛 出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。
-
【推荐】资金相关的金融敏感信息,使用悲观锁策略。
乐观锁在获得锁的同时已经完成了更新操作,校验逻辑容易出现漏洞,另外,乐观锁对冲突的解决策 略有较复杂的要求,处理不当容易造成系统压力或数据异常,所以资金相关的金融敏感信息不建议使用乐观 锁更新。
悲观锁遵循一锁、二判、三更新、四释放的原则。
何为“一锁二判三更新”? 简单来说就是当任何一个并发请求过来的时候
\1. 我们先锁定关联单据
\2. 然后判断关联单据状态,是否之前已经更新过对应状态了
\3. 如果基于第2步判断,之前并没有请求更新过对应状态,则本次请求可以更新并完成相关业务逻辑。
如果之前已经有更新过状态了,则本次不能更新,也不能完成业务逻辑。 (转自支付宝防并发方案之"一锁二判三更新") -
【强制】在高并发场景中,避免使用”等于”判断作为中断或退出的条件。
如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件 来代替。如判断剩余奖品数量等于 0 时,终止发放奖品,但因为并发处理错误导致奖品数量瞬间变成了负数, 这样的话,活动无法终止。
(二) 异常处理
-
. 【强制】不要在 finally 块中使用 return。
try 块中的 return 语句执行成功后,并不马上返回,而是继续执行 finally 块中的语句,如果此处存 在 return 语句,则在此直接返回,无情丢弃掉 try 块中的返回点。
private int x = 0; public int checkReturn() { try { // x 等于 1,此处不返回 return ++x; } finally { // 返回的结果是 2 return ++x; } }
(三) 日志规约
-
【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 (SLF4J、JCL–Jakarta Commons Logging)中的 API,使用门面模式的日志框架,有利于维护和 各个类的日志处理方式统一。
使用 SLF4J: import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger logger = LoggerFactory.getLogger(Test.class);
-
【推荐】谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑 爆,并记得及时删除这些观察日志。
- 大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。记录日志时请思考:这些 日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?
五、MySQL 数据库
-
【强制】表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint (1 表示是,0 表示否)。
- 表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除。
-
【强制】表名不使用复数名词。
-
【推荐】表的命名最好是遵循“业务名称_表的作用”。
正例:alipay_task / force_project / trade_config
-
【强制】表必备三字段:id, create_time, update_time
-
【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索 速度。
(二) 索引规约
-
【强制】业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。
不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外, 即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。
-
【强制】超过三个表禁止 join。需要 join 的字段,数据类型保持绝对一致;多表关联查询时, 保证被关联的字段需要有索引。
- 即使双表 join 也要注意表索引、SQL 性能。
-
【强制】在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据 实际文本区分度决定索引长度。
索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达 90% 以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度来确定。
-
. 【推荐】利用覆盖索引来进行查询操作,避免回表。
如果一本书需要知道第 11 章是什么标题,会翻开第 11 章对应的那一页吗?目录浏览一下就好,这 个目录就是起到覆盖索引的作用。
- 能够建立索引的种类分为主键索引、唯一索引、普通索引三种,而覆盖索引只是一种查询的一种效 果,用 explain 的结果,extra 列会出现:using index。
-
. 【推荐】利用延迟关联或者子查询优化超多分页场景。
MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。
先快速定位需要获取的 id 段,然后再关联: SELECT t1.* FROM 表 1 as t1, (select id from 表 1 where 条件 LIMIT 100000,20 ) as t2 where t1.id=t2.id
-
【推荐】SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。
1) consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
2) ref 指的是使用普通的索引(normal index)。
3) range 对索引进行范围检索。
-
.【参考】创建索引时避免有如下极端误解:
- 1) 索引宁滥勿缺。认为一个查询就需要建一个索引。
- 2) 吝啬索引的创建。认为索引会消耗空间、严重拖慢记录的更新以及行的新增速度。
- 3) 抵制惟一索引。认为惟一索引一律需要在应用层通过“先查后插”方式解决。
-
(17)使用TIMESTAMP存储时间(timestamp索引)
-
(14)必须使用varchar(20)存储手机号