一、 编程规约
(一) 命名风格
【强制】类名使用UpperCamelCase风格,但以下情形例外:DO / BO / DTO / VO / AO / PO / UID等。
正例:ForceCode / UserDO
反例:forcecode / UserDo
-
【强制】抽象类命名使用Abstract或Base开头;异常类命名使用Exception 结尾;测试类 命名以它要测试的类的名称开始,以Test结尾
-
【强制】POJO类中的任何布尔类型的变量,都不要加is前缀,否则部分框架解析会引起序列 化错误。
说明:在本文MySQL 规约中的建表约定第一条,表达是与否的值采用 is_xxx的命名方式,所以,需要在 设置从 is_xxx到xxx的映射关系。
9.【强制】包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用 单数形式,但是类名如果有复数含义,类名可以使用复数形式。
正例:应用工具类包名为 com.alibaba.ei.kunlun.aap.util、类名为 MessageUtils
-
【强制】避免在子父类的成员变量之间、或者不同代码块的局部变量之间采用完全相同的命名, 使可读性降低。
-
【推荐】在常量与变量的命名时,表示类型的名词放在词尾,以提升辨识度。 正例:startTime / workQueue / nameList / TERMINATED_THREAD_COUNT 反例:startedAt / QueueOfWork / listName / COUNT_TERMINATED_THREAD
-
【推荐】如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。 说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。
正例:
public class OrderFactory;
public class LoginProxy;
public class ResourceObserver; -
接口和实现类的命名有两套规则:
1)【强制】对于 Service和 DAO类,基于 SOA 的理念,暴露出来的服务一定是接口,内部的实现类用 Impl的后缀与接口区别。 正例:CacheServiceImpl 实现 CacheService 接口。 2)【推荐】如果是形容能力的接口名称,取对应的形容词为接口名(通常是–able 的形容词)。 正例:AbstractTranslator 实现 Translatable 接口。 -
【参考】各层命名规约:
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。
(二) 常量定义
3. 【推荐】不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。
- 【推荐】常量的复用层次有五层:
跨应用共享常量、应用内共享常量、子工程内共享常量、包 内共享常量、类内共享常量。 1) 跨应用共享常量:放置在二方库中,通常是 client.jar 中的constant 目录下。
2) 应用内共享常量:放置在一方库中,通常是子模块中的constant 目录下。
3) 子工程内部共享常量:即在当前子工程的 constant 目录下。
4) 包内共享常量:即在当前包下单独的 constant 目录下。
5) 类内共享常量:直接在类内部 private static final 定义。
(三) 代码格式
-
【强制】如果是大括号内为空,则简洁地写成{}即可
-
【强制】左小括号和右边相邻字符之间不出现空格;右小括号和左边相邻字符之间也不出现空 格;而左大括号前需要加空格
反例:if (空格a == b 空格) -
【强制】if/for/while/switch/do 等保留字与括号之间都必须加空格。
-
【强制】任何二目、三目运算符的左右两边都需要加一个空格。 说明:包括赋值运算符=、逻辑运算符&&、加减乘除符号等。
-
【强制】采用4个空格缩进,禁止使用tab字符。
-
【强制】单行字符数限制不超过120个,超出需要换行,换行时遵循如下原则:
1)第二行相对第一行缩进4 个空格,从第三行开始,不再继续缩进,参考示例。
2)运算符与下文一起换行。
3)方法调用的点符号与下文一起换行。
4)方法调用中的多个参数需要换行时,在逗号后进行。
5)在括号前不要换行, -
【强制】方法参数在定义和传入时,多个参数逗号后边必须加空格。
-
【强制】IDE的text file encoding 设置为UTF-8; IDE中文件的换行符使用Unix格式,不要 使用Windows格式。
-
【推荐】单个方法的总行数不超过80行。
正例:代码逻辑分清红花和绿叶,个性和共性,绿叶逻辑单独出来成为额外方法,使主干代码更加清晰;共 性逻辑抽取成为共性方法,便于复用和维护。 -
【推荐】不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以提升可读性。 说明:任何情形,没有必要插入多个空行进行隔开。
(四) OOP规约
3. 【强制】相同参数类型,相同业务含义,才可以使用 Java的可变参数,避免使用 Object。 说明:可变参数必须放置在参数列表的最后。
正例:public List listUsers(String type, Long… ids) {…}
-
【强制】所有整型包装类对象之间值的比较,全部使用equals方法比较
在-128至127 之间的问题 -
【强制】浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用equals 来判断。
-
【强制】定义数据对象DO类时,属性类型要与数据库字段类型相匹配。 正例:数据库字段的 bigint 必须与类属性的 Long 类型相对应。 反例:某个案例的数据库表id 字段定义类型bigint unsigned,实际类对象属性为 Integer,随着 id 越来 越大,超过 Integer 的表示范围而溢出成为负数。
-
【强制】禁止使用构造方法 BigDecimal(double)的方式把double值转化为 BigDecimal对象。
-
关于基本数据类型与包装数据类型的使用标准如下:
1) 【强制】所有的 POJO类属性必须使用包装数据类型。
2) 【强制】RPC 方法的返回值和参数必须使用包装数据类型。
3) 【推荐】所有的局部变量使用基本数据类型。
说明:POJO类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE问题,或 者入库检查,都由使用者来保证。
正例:数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。 反例:某业务的交易报表上显示成交总额涨跌情况,即正负 x%,x为基本数据类型,调用的 RPC 服务,调 用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线-。所以包装数据类型 的 null值,能够表示额外的信息,如:远程调用失败,异常退出。 -
【强制】定义DO/DTO/VO等POJO类时,不要设定任何属性默认值。
-
【强制】构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init方法中。
-
【推荐】使用索引访问用String的split方法得到的数组时,需做最后一个分隔符后有无内容 的检查,否则会有抛IndexOutOfBoundsException 的风险。
-
【推荐】当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,便 于阅读,此条规则优先于下一条。
-
【推荐】 类内方法定义的顺序依次是:公有方法或保护方法 > 私有方法 > getter / setter 方法。 说明:公有方法是类的调用者和维护者最关心的方法,首屏展示最好;保护方法虽然只是子类关心,也可 能是“模板设计模式”下的核心方法;
-
【推荐】setter方法中,参数名称与类成员变量名称一致,this.成员名 = 参数名。在 getter/setter方法中,不要增加业务逻辑,增加排查问题的难度。
-
【推荐】类成员与方法访问控制从严:
1) 如果不允许外部直接通过 new来创建对象,那么构造方法必须是 private。
2) 工具类不允许有 public或default 构造方法。
3) 类非static 成员变量并且与子类共享,必须是 protected。
4) 类非static 成员变量并且仅在本类使用,必须是private。
5) 类static 成员变量如果仅在本类使用,必须是 private。
6) 若是static 成员变量,考虑是否为final。
7) 类成员方法只供类内部调用,必须是 private。
8) 类成员方法只对继承类公开,那么限制为 protected
(五) 日期时间
-
【强制】日期格式化时,传入pattern 中表示年份统一使用小写的y
说明:日期格式化时,yyyy 表示当天所在的年,而大写的 YYYY代表是 week in which year(JDK7 之后 引入的概念),意思是当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,返回的 YYYY 就是下一年。
正例:表示日期和时间的格式如下所示:
new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”) -
【强制】在日期格式中分清楚大写的M和小写的m,大写的H和小写的h分别指代的意义。
说明:日期格式中的这两对字母表意如下:
1) 表示月份是大写的 M;
2) 表示分钟则是小写的 m;
3) 24 小时制的是大写的H;
4) 12 小时制的则是小写的 h。 -
【强制】获取当前毫秒数:System.currentTimeMillis(); 而不是new Date().getTime()。 说明:如果想获取更加精确的纳秒级时间值,使用 System.nanoTime 的方式。在 JDK8 中,针对统计时间 等场景,推荐使用 Instant 类。
-
【强制】不允许在程序任何地方中使用:1)java.sql.Date 2)java.sql.Time 3) java.sql.Timestamp。
-
【推荐】使用枚举值来指代月份。如果使用数字,注意Date,Calendar等日期相关类的月份 month 取值在0-11之间。
说明:参考JDK 原生注释,Month value is 0-based. e.g., 0 for January.
正例: Calendar.JANUARY,Calendar.FEBRUARY,Calendar.MARCH 等来指代相应月份来进行传参或 比较。
(六) 集合处理
2. 【强制】判断所有集合内部的元素是否为空,使用isEmpty()方法,而不是size()==0的方式
-
【强制】在使用java.util.stream.Collectors类的 toMap()方法转为Map集合时,一定要使 用含有参数类型为BinaryOperator,参数名为mergeFunction 的方法,否则当出现相同key 值时会抛出IllegalStateException 异常
-
【强制】在使用java.util.stream.Collectors类的 toMap()方法转为Map集合时,一定要注 意当value为null时会抛NPE异常。
-
【强制】使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一 致、长度为0的空数组。
String[] array = list.toArray(new String[0]); -
【强制】泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add方法, 而<? super T>不能使用get方法,两者在接口调用赋值的场景中容易出错。 说明:扩展说一下 PECS(Producer Extends Consumer Super)原则:第一、频繁往外读取内容的,适合用 <? extends T>。第二、经常往里插入的,适合用<? super T>
-
【强制】不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator 方式,如果并发操作,需要对Iterator对象加锁。
-
【推荐】使用 entrySet遍历 Map类集合 KV,而不是 keySet方式进行遍历。 说明:keySet 其实是遍历了2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出key所对应的 value。而 entrySet 只是遍历了一次就把 key和value都放到了entry中,效率更高。如果是 JDK8,使用 Map.forEach 方法。
(七) 并发处理
2. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯
-
【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
-
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,
-
【强制】SimpleDateFormat 是线程不安全的类,一般不要定义为 static变量,如果定义为 static, 必须加锁,或者使用 DateUtils工具类。
说明:如果是JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter 代替SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe。
6 【强制】必须回收自定义的ThreadLocal变量,尤其在线程池场景下,线程经常会被复用, 如果不清理自定义的 ThreadLocal变量,可能会影响后续业务逻辑和造成内存泄露等问题。 尽量在代理中使用try-finally块进行回收。
-
【强制】在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且在加锁方法与try代 码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在 finally中无法解锁。 说明一:如果在 lock 方法与 try代码块之间的方法调用抛出异常,那么无法解锁,造成其它线程无法成功 获取锁。
说明二:如果lock 方法在try代码块之内,可能由于其它方法抛出异常,导致在 finally代码块中,unlock 对未加锁的对象解锁,它会调用 AQS的tryRelease 方法(取决于具体实现类),抛出 IllegalMonitorStateException 异常。
说明三:在Lock 对象的lock 方法实现中可能抛出 unchecked 异常,产生的后果与说明二相同。 正例:
Lock lock = new XxxLock();
// …
lock.lock();
try {
doSomething();
doOthers(); }
finally {
lock.unlock(); } -
【推荐】资金相关的金融敏感信息,使用悲观锁策略。
-
【推荐】避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed 导致的性能下降。
-
【推荐】通过双重检查锁推荐解决方案中将目标属性声明为 volatile型
-
【参考】ThreadLocal对象使用static修饰,ThreadLocal 无法解决共享对象的更新问题。 说明:这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享此静态变量, 也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义的)都可 以操控这个变量。
(八) 控制语句
-
【强制】在一个switch块内,每个case要么通过continue/break/return 等来终止,要么 注释说明程序将继续执行到哪一个case为止;在一个switch 块内,都必须包含一个default
语句并且放在最后,即使它什么代码也没有。 -
【强制】在 if/else/for/while/do语句中必须使用大括号。 说明:即使只有一行代码
-
【强制】在高并发场景中,避免使用”等于”判断作为中断或退出的条件。 说明:如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件 来代替。
-
【推荐】表达异常的分支时,少用 if-else 方式,这种方式可以改写成:
可以使用卫语句, 多个if 替代if-else
8.将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。
final boolean existed = (file.open(fileName, “w”) != null) && (…) || (…);
if (existed) {
- 【推荐】避免采用取反逻辑运算符。 说明:取反逻辑不利于快速理解,并且取反逻辑写法必然存在对应的正向逻辑写法。 正例:使用if (x < 628) 来表达 x 小于628。 反例:使用if (!(x >= 628)) 来表达 x 小于 628
(九) 注释规约
3. 【强制】所有的类都必须添加创建者和创建日期。
11. 【参考】好的命名、代码结构是自解释的
- 【参考】特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描, 经常清理此类标记。线上故障有时候就是来源于这些标记处的代码。
1) 待办事宜(TODO):(标记人,标记时间,[预计处理时间])
2) 错误,不能工作(FIXME):(标记人,标记时间,[预计处理时间])
在注释中用 FIXME标记某代码是错误的,而且不能工作,需要及时纠正的情况。
(十) 其它
- 【强制】在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。 说明:不要在方法体内定义:Pattern pattern = Pattern.compile(“规则”);
2 【强制】避免用Apache Beanutils进行属性的copy。 说明:Apache BeanUtils性能较差,可以使用其他方案比如 Spring BeanUtils, Cglib BeanCopier,注意 均是浅拷贝
-
【强制】注意 Math.random() 这个方法返回是 double类型
直接使用 Random对象的 nextInt或者 nextLong方法 -
【推荐】及时清理不再使用的代码段或配置信息。
正例:对于暂时被注释掉,后续可能恢复使用的代码片断,在注释代码上方,统一规定使用三个斜杠(///) 来说明注释掉代码的理由。如:
/// 业务方通知活动暂停
// Business business = new Business();
// business.active(); System.out.println(“it’s finished”); }
二、异常日志
(一) 错误码
(二) 异常处理
7. 【强制】不要在finally块中使用return。 说明:try块中的 return 语句执行成功后,并不马上返回,而是继续执行 finally块中的语句,如果此处存 在 return 语句,则在此直接返回,无情丢弃掉try块中的返回点。
(三) 日志规约
-
【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 (SLF4J、JCL–Jakarta Commons Logging)中的 API,使用门面模式的日志框架,有利于维护和 各个类的日志处理方式统一。
-
【强制】在日志输出时,字符串变量之间的拼接使用占位符的方式。
正例:logger.debug(“Processing trade with id: {} and symbol: {}”, id, symbol); -
【强制】生产环境禁止直接使用System.out 或System.err 输出日志或使用 e.printStackTrace()打印异常堆栈。
-
【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过 关键字 throws往上抛出。
三、单元测试
2. 【强制】单元测试应该是全自动执行的,并且非交互式的。测试中不准使用 System.out来进行人肉验证,必须使用 assert来验证。
四、安全规约
五、MySQL数据库
(一) 建表规约
-
【强制】表达是与否概念的字段,必须使用is_xxx的方式命名,数据类型是unsigned tinyint (1表示是,0表示否)。 说明:任何字段如果为非负数,必须是 unsigned。
-
【强制】表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只 出现数字。
说明:MySQL 在Windows下不区分大小写,但在 Linux下默认是区分大小写。因此,数据库名、表名、 字段名,都不允许出现任何大写字母,避免节外生枝。 -
【强制】表名不使用复数名词。对应于 DO类名也是单数形式,符合 表达习惯。
-
【强制】禁用保留字,如desc、range、match、delayed等
-
【强制】主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。 说明:pk_ 即primary key;uk_ 即 unique key;idx_ 即index的简称
-
【强制】小数类型为decimal,禁止使用float和double。
-
【强制】varchar是可变长字符串,不预先分配存储空间,长度不要超过5000,如果存储长度 大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引效 率。
-
【强制】表必备三字段:id, gmt_create, gmt_modified
-
【推荐】表的命名最好是遵循“业务名称_表的作用”。
-
【推荐】单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表。 说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
-
【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索 速度。 正例:无符号值可以避免误存负数,且扩大了表示范围。
(二) 索引规约
-
【强制】业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。
-
【强制】在 varchar字段上建立索引时,必须指定索引长度,
-
【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决
-
【推荐】如果有order by的场景,请注意利用索引的有序性。order by 最后的字段是组合索 引的一部分,并且放在索引组合顺序的最后,避免出现file_sort的情况,影响查询性能
-
【推荐】SQL 性能优化的目标:至少要达到 range 级别,要求是 ref级别,如果可以是 consts 最好。
(三) SQL语句
-
【强制】不要使用count(列名)或count(常量)来替代count(*),
-
【强制】不得使用外键与级联,一切外键概念必须在应用层解决
-
【强制】对于数据库中表记录的查询和变更,只要涉及多个表,都需要在列名前加表的别名(或 表名)进行限定
-
【推荐】SQL语句中表的别名前加as,并且以t1、t2、t3、…的顺序依次命名。 说明:1)别名可以是表的简称,或者是根据表出现的顺序,以 t1、t2、t3 的方式命名。2)别名前加 as 使别名更容易识别
(四) ORM映射
2. 【强制】POJO类的布尔属性不能加is,而数据库字段必须加is_,要求在resultMap中进行 字段与属性之间的映射。
-
【强制】不要用resultClass当返回参数,即使所有类属性名与数据库字段一一对应,也需要 定义;反过来,每一个表也必然有一个与之对应。
-
【强制】不允许直接拿 HashMap与 Hashtable作为查询结果集的输出。
-
【强制】更新数据表记录时,必须同时更新记录对应的gmt_modified 字段值为当前时间。
-
【参考】@Transactional 事务不要滥用。事务会影响数据库的QPS,另外使用事务的地方需 要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。
六、工程结构
(一) 应用分层
(二) 二方库依赖
(三) 服务器
3. 【推荐】给JVM环境参数设置-XX:+HeapDumpOnOutOfMemoryError参数,让JVM碰到OOM 场景时输出dump信息。
- 【推荐】在线上生产环境,JVM的 Xms和 Xmx设置一样大小的内存容量,避免在 GC 后调整 堆大小带来的压力。
七、设计规约
3. 【强制】如果某个业务对象的状态超过3个,使用状态图来表达并且明确状态变化的各个触发 条件。
附2:专有名词解释
11. 一方库: 本工程内部子项目模块依赖的库(jar 包)。
12. 二方库: 公司内部发布到中央仓库,可供公司内部其它应用依赖的库(jar 包)。
13. 三方库: 公司之外的开源库(jar 包)。
附3:错误码列表
见书籍