• 一、 编程规范

    • (一)命名风格
    • (二)常量定义
    • (三)代码格式
    • (四)OOP规约
    • (五)集合处理
    • (六)并发处理
    • (七)控制语句
    • (八)注释规约
    • (九)其它
  • 二、 异常日志

    • (一)异常处理
    • (二)日志规约
  • 三、 Oracle数据库

    • (一)索引规约
    • (二)SQL语句
    • (三)ORM映射
  • 四、 安全规约

  • 附 1 :版本历史

  • 附 2 :本手册专有名词

Java开发手册

1
2
版本号 更新日期 备注
1.0. 1 2017/6/1 讨论后补充修改调整Mapper层命名,redis使用

一、 编程规范

(一)命名风格

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

1
反例: _name / __name /name_object/$Object / name_ / name$ / Object$

2. 代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。

说明:正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使纯拼音命名方式 也要避免采用。

1
2
正例: alibaba / taobao / youku / hangzhou 等国际通用的名称,可视同英文。
反例: DaZhePromotion [打折] / getPingfenByName() [评分] / int 某变量 = 3

3. 类名使用 UpperCamelCase 风格,必须遵从驼峰形式,但以下情形例外:DO / BO / DTO / VO / AO

1
2
正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion

4. 方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从驼峰形式。

1
2
正例: localValue / getHttpMessage() / inputUserId
说明:id,uid固定小写,userId则使用驼峰形式

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

正例: MAX_STOCK_COUNT

反例: MAX_COUNT

6. 抽象类命名使用 Abstract ,父类命名使用Base 开头;异常类命名使用 Exception 结尾;实现类命名使用Impl结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾。

1
正例:AbstractDispatcher / BaseClient / TxxyBussinessException / UserClientInfoTest

7. 中括号是数组类型的一部分,数组定义如下:String[] args;反例:使用 String args[]的方式来定义

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

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

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

1
正例: 应用工具类包名为 com.txxy.framework.util类名为 StringUtil(此规则参考common-lang的框架结构)

10. 杜绝完全不规范的缩写,避免望文不知义。

反例:
AbstractClass“缩写”命名成 AbsClass;condition“缩写”命名成 condi,此类随意缩写严重降低
了代码的可阅读性。

11.【推荐】如果使用到了设计模式,建议在类名中体现出具体模式。

说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计思想。
正例:

1
2
3
public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;

13. 接口和实现类的命名有两套规则:

1 )对于 Service 和 Client 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部的实现类用 Impl 的后缀与接口区别。
正例:

1
CacheServiceImpl 实现 CacheService 接口。

2 )【推荐】 如果是形容能力的接口名称,取对应的形容词做接口名(通常是*able 的形式)。
正例:

1
AbstractTranslator 实现 Translatable。

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

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

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

15. 各层命名规约:

A) Mapper 层方法命名规约

1
2
3
4
5
6
7
8
9
10
1 ) 获取单个对象的方法用 one 做前缀。正例:oneById
2 ) 获取多个对象的方法用 list 做前缀(app端使用)。正例:listByCondition/listPageByCondition
3 ) 获取统计值的方法用 count 做前缀(app端使用)。正例:countByCondition
4 ) 插入的方法用 insert (推荐)或save做前缀。正例:insert
5 ) 删除的方法用 delete(推荐)或 remove做前缀。正例:deleteById
6 ) 修改的方法用 update 做前缀。正例:update
7 )获取多个分页对象的方法用page做前缀(后台使用)。
正例:pageByCondition
8 )获取统计值的方法用 count 做前缀(后台使用)。
正例:statisticByCondition

B) 领域模型命名规约

1
2
3
1 ) 数据对象:xxxDO,xxx 即为数据表名。
2 ) 数据传输对象:xxxDTO,xxx 为业务领域相关的名称。
3 ) POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO

(二)常量定义

1.不允许任何魔法值(即未经定义的常量)直接出现在代码中。

反例:

1
2
 String key = "Id#taobao_" + tradeId;
cache.put(key, value);

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

1
说明:Long a = 2l; 写的是数字的 21 ,还是 Long 型的 2?

3. 【推荐】不要使用一个常量类维护所有常量,应该按常量功能进行归类,分开维护。如:缓存相关的常量放在类:CacheConsts 下;系统配置相关的常量放在类:ConfigConsts 下。

说明:大而全的常量类,非得使用查找功能才能定位到修改的常量,不利于理解和维护。

4. 【推荐】常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量。

1 ) 跨应用共享常量:放置在二方库中,通常是 client.jar 中的 constant 目录下。

2 ) 应用内共享常量:放置在一方库的 modules 中的 constant 目录下。

反例:易懂变量也要统一定义成应用内共享常量,两位攻城师在两个类中分别定义了表示“是”的变量:

类 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 定义。

5. 【推荐】如果变量值仅在一个范围内变化,且带有名称之外的延伸属性,定义为枚举类。下面 正

例中的数字就是延伸信息,表示星期几。

正例:

1
2
public Enum { MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6),
SUNDAY(7);}

(三)代码格式

1. 缩进采用 4 个空格,禁止使用 tab 字符,eclipse-IDE请导入对应的format模板

说明:如果使用 tab 缩进,必须设置 1 个 tab 为 4 个空格。IDEA 设置 tab 为 4 个空格时, 请勿勾选

Use tab character;而在 eclipse 中,必须勾选 insert spaces for tabs。

正例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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");
// 在右大括号后直接结束,则必须换行
}
}

2. 方法参数在定义和传入时,多个参数逗号后边必须加空格。

正例:下例中实参的”a”,后边必须要有一个空格。method(“a”, “b”, “c”);

3.单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则:

1 ) 第二行相对第一行缩进 4 个空格,从第三行开始,不再继续缩进,参考示例。

2 ) 运算符与下文一起换行。

3 ) 方法调用的点符号与下文一起换行。

4 ) 在多个参数超长,在逗号后换行。

5 ) 在括号前不要换行,见反例。

正例:

1
2
3
4
5
6
StringBuffer sb = new StringBuffer();
//超过 120 个字符的情况下,换行缩进 4 个空格,并且方法前的点符号一起换行
sb.append("zi").append("xin")...
.append("huang")...
.append("huang")...
.append("huang");

反例:

1
2
3
4
5
6
7
8
9
StringBuffer sb = new StringBuffer();
//超过 120 个字符的情况下,不要在括号前换行


sb.append("zi").append("xin")...append
("huang");
//参数很多的方法调用可能超过 120 个字符,不要在逗号前换行
method(args1, args2, args3, ...
, argsX);

4. 单个函数的有效代码长度应尽量控制在 100 行以内,单个类的长度包括注释行不超过 1000 行。

说明:使用子函数(业务类)等将相应功能抽取出来,既避免单个方法(类)过大时导致的可阅读性差,也有利于提高

代码的重用度。

5.【推荐】没有必要增加若干空格来使某一行的字符与上一行对应位置的字符对齐。

1
2
3
4
5
6
7
正例:

int a = 3;
long b = 4L;
float c = 5F;
StringBuffer sb = new StringBuffer();
说明:增加 sb 这个变量,如果需要对齐,则给a、b、c都要增加几个空格,在变量比较多的情况下,是一种累赘的事情。

(四)OOP规约

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

2.所有的覆写方法,必须加@Override 注解。

说明:getObject()与 get0bject()的问题。一个是字母的 O,一个是数字的 0 ,加@Override 可以准确判断是否覆
盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。

3. 相同参数类型,相同业务含义,才可以使用 Java 的可变参数,避免使用 Object。

说明:可变参数必须放置在参数列表的最后。(提倡同学们尽量不用可变参数编程)

1
正例:public User getUsers(String type, Integer... ids) {...}

4. 调用或者第三方库依赖的接口过时必须加@Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么。

5. 不能使用过时的类或方法。

说明:java.net.URLDecoder 中的方法 decode(String encodeStr) 这个方法已经过时,应该使用双参数
decode(String source, String encode)。接口提供方既然明确是过时接口, 那么有义务同时提供新的接口;作为调
用方来说,有义务去考证过时方法的新实现是什么。

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

1
2
3
正例: "test".equals(object);
反例: object.equals("test");
说明:推荐使用 java.util.Objects#equals (JDK7 引入的工具类)

7.所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。

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

8.定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值。

反例:POJO 类的 gmtCreate 默认值为 new Date();但是这个属性在数据提取时并没有置入具 体值,在更新其它
字段时又附带更新了此字段,导致创建时间被修改成当前时间。

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

说明:注意 serialVersionUID 不一致会抛出序列化运行时异常。

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

11. POJO 类必须写 toString 方法或是使用@Data注解。

1
说明:在方法执行抛出异常时,可以直接调用 POJO 的 toString()方法打印其属性值,便于排查问题。

12 .【推荐】使用索引访问用 String 的 split 方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛 IndexOutOfBoundsException 的风险。

说明:

1
2
3
4
String str = "a,b,c,,";
String[] ary = str.split(",");
//预 期 大 于 3 ,结果是 3
System.out.println(ary.length);

13.【推荐】当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起, 便于阅读。

14. 【推荐】 类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter 方法。

说明:公有方法是类的调用者和维护者最关心的方法,首屏展示最好;保护方法虽然只是子类关心,也可能是“模

板设计模式”下的核心方法;

15.【推荐】循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展。

说明:反编译出的字节码文件显示每次循环都会 new 出一个 StringBuilder 对象,然后进行 append 操作,最后
通过 toString 方法返回 String 对象,造成内存资源浪费。
反例:

1
2
3
4
String str = "start";
for (int i = 0; i < 100; i++) {
str = str + "hello";
}

16. 【推荐】final 可以声明类、成员变量、方法、以及本地变量,下列情况使用 final 关键字:

1 ) 不允许被继承的类,如:String 类。

2 ) 不允许修改引用的域对象,如:POJO 类的域变量。

3 ) 不允许被重写的方法,如:POJO 类的 setter 方法。

4 ) 不允许运行过程中重新赋值的局部变量。

5 ) 避免上下文重复使用一个变量,使用 final 描述可以强制重新定义一个变量,方便更好地进行重构。

17.【推荐】类成员与方法访问控制从严:

1 ) 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private。
2 ) 工具类不允许有 public 或 default 构造方法。
3 ) 类非 static 成员变量并且与子类共享,必须是 protected。
4 ) 类非 static 成员变量并且仅在本类使用,必须是 private。
5 ) 类 static 成员变量如果仅在本类使用,必须是 private。
6 ) 若是 static 成员变量,必须考虑是否为 final。
7 ) 类成员方法只供类内部调用,必须是 private。
8 ) 类成员方法只对继承类公开,那么限制为 protected。

说明:任何类、方法、参数、变量,严控访问范围。过于宽泛的访问范围,不利于模块解耦。 思考:如果是一

个 private 的方法,想删除就删除,可是一个 public 的 service 方法,或者 一个 public 的成员变量,删除一下,不得
手心冒点汗吗?变量像自己的小孩,尽量在自己的视线内,变量作用域太大,如果无限制的到处跑,那么你会担心
的。

(五)集合处理

1. ArrayList 的 subList 结果不可强转成 ArrayList,否则会抛出 ClassCastException 异常:

1
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,其它数组元素保持原值,因
此最好将方法入参数组大小定义与集合元素 个数一致。
正例:

1
2
3
4
5
6
7
List<String> list = new ArrayList<String>(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 体现的是适配器模式,只
是转换接口,后台的数据仍是数组。

1
2
String[] str = new String[] { "a", "b" };
List list = Arrays.asList(str);

第一种情况:list.add(“c”); 运行时异常。

第二种情况:str[0] = “gujin”; 那么 list.get(0)也会随之修改。

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

正例:

1
2
3
4
5
6
7
8
9
10
11
12
Iterator<String> it = a.iterator(); while
(it.hasNext()) {
String temp = it.next();
if (删除元素的条件) {
it.remove();
}
}
反例:
List<String> a = new ArrayList<String>();
a.add("1");
a.add("2");
for (String temp : a) {
1
2
3
4
if("1".equals(temp)) {
a.remove(temp);
}
}

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

说明:keySet其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出 key 所对应的 value。而 entrySet只是遍历了一次就把 key 和 value 都放到了 entry 中,效 率更高。 JDK 8 ,使用 Map.foreach 方法。

正例:values()返回的是 V 值集合,是一个 list 集合对象;keySet()返回的是 K 值集合,是 一个 Set 集合对象;
entrySet()返回的是 K-V 值组合集合。

7.【参考】利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的contains 方法进行遍历、对比、去重操作

(六)并发处理

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

正例:

1
2
3
4
public class TimerTaskThread extendsThread{
public TimerTaskThread() {
super.setName("TimerTaskThread"); ...
}

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

说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资 源不足的问题。如

果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者 “过度切换”的问题。

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

1
2
说明:Executors 返回的线程池对象的弊端如下:
1 )FixedThreadPool 和 SingleThreadPool:
1
2
3
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2 )CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

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

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

1
2
3
4
5
6
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 代替
Simpledateformatter,官方给出的解释:simple beautiful strong immutable thread-safe。

5. 分布式并发锁注解@RedisCacheLock,是基于aop切面方法生效的,注同一个类中不论是public or private 的方法该并发锁是无效的。

说明:默认乐观锁重试 10 次,如果入参是Map类型,则锁的key后缀取uid,或通过cacheKeySuffix指定其
他key做为后缀;配置kickOff=true,强占锁模式,先到先得,未抢到锁的线程则异常丢出
正例:Action类访问Manager类,

1
2
3
4
5
@RedisCacheLock(cacheKeyPrefix=xxxx)
public void payment(Long uid){
//订单支付
payService.pay();
}

反例:同一个Manager类

1
2
3
4
5
6
7
@RedisCacheLock(cacheKeyPrefix=xxxx)
public void payment(Long uid){
//订单支付


payService.pay();
}

(七)控制语句

1. 在一个 switch 块内,每个 case 要么通过 break/return 等来终止,要么注释说明程 序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default 语句并且 放在最后,即使它什么代码也没有。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (condition) {
...
return obj;
}
// 接着写 else 的业务逻辑代码;
说明:如果非得使用 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 Guideline.”);
return;

}

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

说明:很多 if 语句内的逻辑相当复杂,阅读者需要分析条件表达式的最终结果,才能明确什么样的条件执行什么
样的语句,那么,如果阅读者分析逻辑表达式错误呢?
正例:
//伪代码如下

1
2
3

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

}
反例:

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

(八)注释规约

1.所有的类都必须添加创建者和创建日期。

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

3. 【参考】好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的 一个极端:过多过滥的注释,代码的逻辑一旦修改,修改注释是相当大的负担。

反例:

1
2
3
// put elephant into fridge
put(elephant, fridge);
方法名 put,加上两个有意义的变量名 elephant 和 fridge,已经说明了这是在干什么,语义清晰的代码不需要额外的注释。

4. 【参考】对于注释的要求:第一、能够准确反应设计思想和代码逻辑;第二、能够描述业务含义,使别的程序员能够迅速了解到代码背后的信息。完全没有注释的大段代码对于阅读者形同 天书,注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路;注释也是给继任者看的,使其能够快速接替自己的工作。

5. 【参考】合理处理注释掉的代码,而不是简单的注释掉。如果无用,则删除。

说明:代码被注释掉有两种可能性:

1 )后续会恢复此段代码逻辑。

2 )永久不用。前者如果没有备注信息,难以知晓注释动机。后者建议直接删掉(代码仓库保存了历史代码)

6. 【参考】新框架spring-cloud下暴露出来的client包接口,方法注释尽可能描述详细。

说明:如查询哪张表,返回的属性参数等,尤其相似名称的方法名更需要详述。

(九)其它

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

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

2. 注意 Math.random() 这个方法返回是 double 类型,注意取值的范围 0≤x<1(能够取到零值,注意除零异常),如果想获取整数类型的随机数,不要将 x 放大 10 的若干倍然后取整,直接使用 Random 对象的 nextInt 或者 nextLong 方法。

3. 获取当前毫秒数 System.currentTimeMillis(); 而不是 new Date().getTime();

说明:如果想获取更加精确的纳秒级时间值,使用 System.nanoTime()的方式。在 JDK8 中, 针对统计时间等场景,推荐使用 Instant 类。

4.不要使用难懂的技巧性很高的语句,除非很有必要。

说明:高技巧语句不等于高效率的程序,实际上程序的效率关键在于算法。 示例:如下表达式,考虑不周就可能出问题,也较难理解。

反例:

1
2
startPosition ++ += 1; //可改为 startPosition += 1; startPosition++;
++ startPosition += 1; //可改为 startPosition += 1; startPosition++;

5.原则上不允许在循环体内调用RPC或执行数据库CRUD。

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

说明:随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副 本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是共用模块。 正例:一个类中有多个 public 方法,都需要进行数行相同的参数校验操作,这个时候请抽取:private boolean checkParam(DTO dto) {…}

7.【参考】避免出现重复的代码(Don’t Repeat Yourself),即 DRY 原则。

说明:随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副 本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是共用模块。 正例:一个类中有多个 public方法,都需要进行数行相同的参数校验操作,这个时候请抽取:private boolean checkParam(DTO dto) {…}

8.【推荐】遇到需异步执行的场景,优选考虑RocketMQ方式去执行,尽量少用ThreadPoolExecutor线程池去实现。

9. 使用redis做缓存,放入的值对象空间不允许大于1M,否则会造成其他读写redis的线程出现阻塞。说明:redis是单线程的处理操作

10.使用redis的哈希key操作时,不允许放入的key过多。

二、 异常日志

(一)异常处理

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

2. 对大段代码进行 try-catch,这是不负责任的表现。catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的catch 尽可能进行区分 异常类型,再做对应的异常处理。

3. 捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。

1
说明:新框架spring-cloud下,请将异常从client层抛出,框架会统一处理并返回

TxxResponse对象给调用者

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

5. finally 块必须对资源对象、流对象进行关闭,有异常也要做try-catch。

说明:如果 JDK7 及以上,可以使用 try-with-resources 方式。

1
2
3
4
5
try (InputStream ins = new FileInputStream(new File("xxx"))) {
//业务逻辑
} catch (Exception e) {
log.error("操作异常", e);
}

6.调用Throwable.printStackTrace是不好的实践,生产代码禁止以System及Throwable.printStackTrace,请使用日志组件打印异常信息。

正例:

1
使用log.error(“msg”,e);

反例:

1
2
log.error(“msg”+ e);
log.error(“xxx”,e.getMessage());

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

8. 【推荐】方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回 null 值。调用方需要进行null 判断防止NPE 问题。

说明:本手册明确防止NPE 是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回null 的情况。

(二)日志规约

1.应用中不可直接使用日志系统(Log4j、Log4j2)中的 API,而应依赖使用日志框架 SLF4J 中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。新框架spring-cloud采用lomlbok的@Slf4j注解,而非自行去定义声明logger对象。

1
2
3
4
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

private static final Logger logger = LoggerFactory.getLogger(Abc.class);

2. 对 trace/debug/info/error 级别的日志输出,必须使用占位符的方式。

1
说明:logger.debug("Processing trade with id: " + id + " symbol: " + symbol); 如果日志级别是warn,上述日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象, 会执行 toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。
1
正例:logger.debug("Processing trade with id: {} symbol : {} ", id, symbol);

3.【推荐】日志打印将固定的或者是描述性的写在前,而不固定的或者变化的参数放在最后输出。

1
正例:log.info("查询用户数据|xxxxx|dto={}",dto);

4 .【推荐】需要被监控的外部接口或者第三方接口,打印日志格式按照标准规范,接口统一定义在MonitorTypeEnum枚举类或者打印TXXYMONITOR |INTERFACE | 接口编号-接口描述|[成功,失败]

1
2
3
正例:
log.info(MonitorTypeEnum.GEE_TEST.success()); //调用成功
log.info(MonitorTypeEnum. GEE_TEST.failure()); //调用失败

5.【推荐】log日志打印,框架脱敏用户的敏感数据信息,如姓名,手机号,身份证号,银行卡号

三、 Oracle数据库

(一)索引规约

1.业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。

1
说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。

2. 【推荐】建组合索引的时候,区分度最高的在最左边。

1
2
正例:如果 where a=? and b=? ,a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即可。
说明:存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如:where a>? and b=? 那么即使 a 的区分度更高,也必须把 b 放在索引的最前列。

(二)SQL语句

1. where 语句后面不允许写死1=1这样固定的条件,需通过mybatis 或者 ibatis 的标签。

1
反例:select id,name,idc from t_user_info where 1=1

2. 【推荐】in 操作能避免则避免,若实在避免不了,需要仔细评估 in 后边的集合元素数量,控制在 1000 个之内。

3. 在表查询中,一律不要使用* 作为查询的字段列表,需要哪些字段必须明确写明。 说明:增加查询分析器解析成本

4. 使用order 注意排序的字段有空的情况,默认会把为空的记录排序在前,必须配合where条件过滤。

(三)ORM映射

1.*Mapper.xml 配置参数使用:#{},#param#,而不要使用${} 此种方式容易出现 SQL 注入。

2.更 新 数 据 表 记 录 时 , 必 须 同 时 更 新 记 录 对 应 的 modify_time,update_time字段值为当前时间。

3.【推荐】不要写一个大而全的数据更新接口,传入为 DO 类,不管是不是自己的目标更新字段,都进行 update table set c1=value1,c2=value2,c3=value3;

这是不对的。执行 SQL时,不要更新无须改动的字段,一是易出错;二是效率低;三是增加 binlog 存储。

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

5 【参考】. 中的 compareValue 是与属性值对比的常量,一般是数字,表示相等时带上此条件;表示不为空且不为 null 时执行;表示不为 null 值时执行。

6.【参考】 通过工具,由iBatis转换成myBatis的xml文件,需要留意select,update等语句对应的动态条件语句,是否和原来的保持一致。

四、 安全规约

1.用户敏感数据禁止直接展示,必须对展示数据进行脱敏,如姓名,手机号,身份证号,银行卡等

说明:查看个人手机号码会显示成:158****9119,隐藏中间 4 位,防止隐私泄露

附 1:本手册专有名词

  1. POJO(Plain Ordinary Java Object):在本手册中,POJO 专指只有 setter/getter/

    toString 的简单类,包括 DO/DTO 等。

  2. DO(Data Object):本手册指数据库表一一对应的 POJO 类。

  3. GAV(GroupId、ArtifactctId、Version): Maven 坐标,是用来唯一标识 jar 包。

  4. OOP(Object Oriented Programming): 本手册泛指类、对象的编程处理方式。

  5. ORM(Object Relation Mapping): 对象关系映射,对象领域模型与底层数据

    之间的转换, 本文泛指 iBATIS, mybatis 等框架。

  6. NPE(java.lang.NullPointerException): 空指针异常。

  7. CRUD : 增加(Create)、读取查询(Retrieve)、更新(Update)和删除(Delete)

  8. RPC(Remote Procedure Call):远程过程调用

  9. 二方库:公司内部发布到中央仓库,可供公司内部其它应用依赖的库(jar 包)。

  10. 三方库:公司之外的开源库(jar 包)。