Java开发手册提炼

Java开发手册提炼

  • 一、编程规约
    • 1、命名风格
    • 2、常量定义
    • 3、代码格式
    • 4、OOP 规约
    • 6、集合处理
    • 7、并发处理
    • 8、 控制语句
    • 10、其它
  • 二、异常日志
    • (二) 异常处理
    • (三) 日志规约
  • 三、单元测试
  • 四、安全规约
  • 五、MYSQL数据库
    • (一) 建表规约
    • (二) 索引规约
    • (三) SQL 语句
    • (四) ORM 映射
  • 六、工程结构
  • 七、设计规约

【强制】
【推荐】
【参考】
反例:
说明:
正例:
本文是基于阿里-Java开发手册,对其研读之后个人认为在日常开发中相对频繁注意的点进行摘出,方便暂时没时间进行阅读的读者,快速的进行查阅和学习以及查漏补缺。

一、编程规约

1、命名风格

  1. 【强制】POJO 类中的任何布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。

  2. 【推荐】如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。 说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。
    正例:
    public class OrderFactory;
    public class LoginProxy;
    public class ResourceObserver;

  3. 接口和实现类的命名有两套规则:
        1)【强制】对于 Service 和 DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部的实现类用 Impl 的后缀与接口区别。
    正例:CacheServiceImpl 实现 CacheService 接口。
        2)【推荐】如果是形容能力的接口名称,取对应的形容词为接口名(通常是–able 的形容词)。
    正例:AbstractTranslator 实现 Translatable 接口。

  4. 【参考】各层命名规约:
    A) Service/DAO 层方法命名规约
        1) 获取单个对象的方法用 get 做前缀。
        2) 获取多个对象的方法用 list 做前缀,复数结尾,如:listObjects。
        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。

2、常量定义

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

  2. 【推荐】不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。
    说明:大而全的常量类,杂乱无章,使用查找功能才能定位到修改的常量,不利于理解,也不利于维护。
    正例:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类 ConfigConsts 下。

  3. 【推荐】常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包 内共享常量、类内共享常量。
        1) 跨应用共享常量:放置在二方库中,通常是 client.jar 中的 constant 目录下。
        2) 应用内共享常量:放置在一方库中,通常是子模块中的 constant 目录下。
    反例: 易懂变量也要统一定义成应用内共享常量,两位工程师在两个类中分别定义了“YES”的变量:
    类 A 中:public static final String YES = "yes";
    类 B 中:public static final String YES = "y";
    A.YES.equals(B.YES),预期是 true,但实际返回为 false,导致线上问题。
        3) 子工程内部共享常量:即在当前子工程的 constant 目录下。
        4) 包内共享常量:即在当前包下单独的 constant 目录下。
        5) 类内共享常量:直接在类内部 private static final 定义。

3、代码格式

  1. 【强制】左小括号和右边相邻字符之间不出现空格;右小括号和左边相邻字符之间也不出现空格;而左大括号前需要加空格。详见第 5 条下方正例提示。
    反例:if (空格 a == b 空格)

  2. 【强制】if/for/while/switch/do 等保留字与括号之间都必须加空格。

  3. 【强制】采用 4 个空格缩进,禁止使用 tab 字符。 说明:如果使用 tab 缩进,必须设置 1 个 tab 为 4 个空格。IDEA 设置 tab 为 4 个空格时,请勿勾选 Use tab character;而在 eclipse 中,必须勾选 insert spaces for tabs。

public static void main(String[] args) { 
// 缩进 4 个空格 
String say = "hello"; 
// 运算符的左右必须有一个空格 
int flag = 0; 
// 关键词 if 与括号之间必须有一个空格,括号内的 f 与左括号,0 与右括号不需要空格 
if (flag == 0) { 
	System.out.println(say); 
} 
// 左大括号前加空格且不换行;左大括号后换行 
if (flag == 1) { 
	System.out.println("world"); 
	// 右大括号前换行,右大括号后有 else,不用换行 
} else { 
System.out.println("ok"); 
// 在右大括号后直接结束,则必须换行 
	} 
}
  1. 【强制】注释的双斜线与注释内容之间有且仅有一个空格。
  2. 【强制】在进行类型强制转换时,右括号与强制转换值之间不需要任何空格隔开。
  3. 【强制】IDE 的 text file encoding 设置为 UTF-8; IDE 中文件的换行符使用 Unix 格式,不要 使用 Windows 格式。

4、OOP 规约

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

5.【强制】不能使用过时的类或方法。

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

6、集合处理

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

8.【强制】在 subList 场景中,高度注意对父集合元素的增加或删除,均会导致子列表的遍历、 增加、删除产生 ConcurrentModificationException 异常。

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

10.【强制】在使用 Collection 接口任何实现类的 addAll()方法时,都要对输入的集合参数进行 NPE 判断。
说明:在 ArrayList#addAll 方法的第一行代码即 Object[] a = c.toArray(); 其中 c 为输入集合参数,如果 为 null,则直接抛出异常。

7、并发处理

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 代替 DateLocalDateTime 代替 CalendarDateTimeFormatter 代替 SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe。

6.【强制】并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用 version 作为更新依据。
说明:如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于 3 次。

12.【强制】多线程并行处理定时任务时,Timer 运行多个 TimeTask 时,只要其中之一没有捕获抛 出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。

17.【参考】volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。
说明:如果是 count++操作,使用如下类实现:AtomicInteger count = new AtomicInteger(); count.addAndGet(1);
          如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。

8、 控制语句

5.【强制】在高并发场景中,避免使用”等于”判断作为中断或退出的条件。 说明:如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件来代替。
反例:判断剩余奖品数量等于 0 时,终止发放奖品,但因为并发处理错误导致奖品数量瞬间变成了负数, 这样的话,活动无法终止。

7.【推荐】表达异常的分支时,少用 if-else 方式,这种方式可以改写成:

if (condition) { 
	... 
	return obj; 
} 
// 接着写 else 的业务逻辑代码;

说明:如果非使用 if()…else if()…else…方式表达逻辑,避免后续代码维护困难,请勿超过 3 层。
正例:超过 3 层的 if-else 的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现,其中卫语句 示例如下:

public void findBoyfriend (Man man){ 
if (man.isUgly()) { 
	System.out.println("本姑娘是外貌协会的资深会员"); 
	return; 
} 
if (man.isPoor()) { 
	System.out.println("贫贱夫妻百事哀"); 
	return; 
} 
if (man.isBadTemper()) { 
	System.out.println("银河有多远,你就给我滚多远"); 
	return; 
} 
System.out.println("可以先交往一段时间看看"); }

10、其它

1.【强制】在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。
说明:不要在方法体内定义:Pattern pattern = Pattern.compile(“规则”);

2.【强制】避免用 Apache Beanutils 进行属性的 copy。 说明:Apache BeanUtils 性能较差,可以使用其他方案比如 Spring BeanUtils, Cglib BeanCopier,注意 均是浅拷贝。

二、异常日志

(二) 异常处理

6.【强制】finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。 说明:如果 JDK7 及以上,可以使用 try-with-resources 方式。

7.【强制】不要在 finally 块中使用 return。
说明:try 块中的 return 语句执行成功后,并不马上返回,而是继续执行 finally 块中的语句,如果此处存在 return 语句,则在此直接返回,无情丢弃掉 try 块中的返回点。
反例:

 private int x = 0; 
 public int checkReturn() { 
 try { 
 	// x 等于 1,此处不返回 
 	return ++x;
 } 
 finally { 
	 // 返回的结果是 2 
	 return ++x; 
 	} 
 }

(三) 日志规约

4.【强制】在日志输出时,字符串变量之间的拼接使用占位符的方式。
说明:因为 String 字符串的拼接会使用 StringBuilder 的 append()方式,有一定的性能损耗。使用占位符仅是替换动作,可以有效提升性能。
正例:

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

5.【强制】对于 trace/debug/info 级别的日志输出,必须进行日志级别的开关判断。
说明:虽然在 debug(参数)的方法体内第一行代码 isDisabled(Level.DEBUG_INT)为真时(Slf4j 的常见实现 Log4j 和 Logback),就直接 return,但是参数可能会进行字符串拼接运算。此外,如果 debug(getName()) 这种参数内有 getName()方法调用,无谓浪费方法调用的开销。
正例:

 // 如果判断为真,那么可以输出 trace 和 debug 级别的日志 
 if (logger.isDebugEnabled()) { 
	 logger.debug("Current ID is: {} and name is: {}", id, getName()); 
 }

7.【强制】生产环境禁止直接使用 System.out 或 System.err 输出日志或使用 e.printStackTrace()打印异常堆栈。
说明:标准日志输出与标准错误输出文件每次 Jboss 重启时才滚动,如果大量输出送往这两个文件,容易 造成文件大小超过操作系统大小限制。

三、单元测试

四、安全规约

五、MYSQL数据库

(一) 建表规约

1.【强制】表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint (1 表示是,0 表示否)。
说明:任何字段如果为非负数,必须是 unsigned。
注意:POJO 类中的任何布尔类型的变量,都不要加 is 前缀,所以,需要在设置从 is_xxx 到 Xxx 的映射关系。数据库表示是与否的值,使用 tinyint 类型,坚持 is_xxx 的命名方式是为了明确其取值含 义与取值范围。
正例:表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除。

4.【强制】禁用保留字,如 desc、range、match、delayed 等,请参考 MySQL 官方保留字。

6.【强制】小数类型为 decimal,禁止使用 float 和 double。
说明:在存储的时候,float 和 double 都存在精度损失的问题,很可能在比较值的时候,得到不正确的 结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数并分开存储。

10.【推荐】表的命名最好是遵循“业务名称_表的作用”。
正例:alipay_task / force_project / trade_config

13.【推荐】字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应遵循:
    1) 不是频繁修改的字段。
    2) 不是唯一索引的字段。
    3) 不是 varchar 超长字段,更不能是 text 字段。
正例:各业务线经常冗余存储商品名称,避免查询时需要调用 IC 服务获取。

15.【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索 速度。
正例:无符号值可以避免误存负数,且扩大了表示范围。

对象 年龄区间 类型 字节 表示范围
150 岁之内 tinyint unsigned 1 无符号值:0 到 255
数百岁 smallint unsigned 2 无符号值:0 到 65535
恐龙化石 数千万年 int unsigned 4 无符号值:0 到约 43 亿
太阳 约 50 亿年 bigint unsigned 8 无符号值:0 到约 10 的 19 次方

(二) 索引规约

1.【强制】业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。
说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外, 即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。

2.【强制】超过三个表禁止 join。需要 join 的字段,数据类型保持绝对一致;多表关联查询时, 保证被关联的字段需要有索引。 说明:即使双表 join 也要注意表索引、SQL 性能。

3.【强制】在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据 实际文本区分度决定索引长度。
说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达 90% 以上,可以使用 count(distinct left(列名, 索引长度))/count(*) 的区分度来确定。

4.【强制】】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。

5.【推荐】如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。
正例: where a=? and b=? order by c; 索引:a_b_c
反例:索引如果存在范围查询,那么索引有序性无法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 无法排序。

7.【推荐】利用延迟关联或者子查询优化超多分页场景。
说明:MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。
正例:先快速定位需要获取的 id 段,然后再关联:
SELECT a.* FROM 表 1 a, (select id from 表 1 where 条件 LIMIT 100000,20 ) b where a.id=b.id

(三) SQL 语句

6.【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
说明:(概念解释)学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学 生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库 的插入速度。

7.【强制】禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。

8.【强制】数据订正(特别是删除或修改记录操作)时,要先 select,避免出现误删除,确认无误才能执行更新语句。

9.【强制】对于数据库中表记录的查询和变更,只要涉及多个表,都需要在列名前加表的别名(或 表名)进行限定。
说明:对多表进行查询记录、更新记录、删除记录时,如果对操作列没有限定表的别名(或表名),并且 操作列在多个表中存在时,就会抛异常。
正例:select t1.name from table_first as t1 , table_second as t2 where t1.id=t2.id;
反例:在某业务中,由于多表关联查询语句没有加表的别名(或表名)的限制,正常运行两年后,最近在 某个表中增加一个同名字段,在预发布环境做数据库变更后,线上查询语句出现出 1052 异常:Column ‘name’ in field list is ambiguous。

(四) ORM 映射

2.【强制】POJO 类的布尔属性不能加 is,而数据库字段必须加 is_,要求在 resultMap 中进行 字段与属性之间的映射。
说明:参见定义 POJO 类以及数据库字段定义规定,在 sql.xml 增加映射,是必须的。

6.【强制】不允许直接拿 HashMap 与 Hashtable 作为查询结果集的输出。 反例:某同学为避免写一个,直接使用 HashTable 来接收数据库返回结果,结果出现日常是把 bigint 转成 Long 值,而线上由于数据库版本不一样,解析成 BigInteger,导致线上问题。

9.【强制】@Transactional 事务不要滥用。事务会影响数据库的 QPS,另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。

六、工程结构

七、设计规约

你可能感兴趣的:(Java,java,开发语言)