编程规约
常量定义
- 不允许任何预先未定义的常量出现在代码中。
- 不要使用一个常量类维护所有的常量,要按常量功能进行归类,分开维护。
OOP规约
- 所有的POJO类属性必须使用包装数据类型。
- RPC方法的返回值和参数必须使用包装数据类型。
POJO类是最简单最普通的JAVA对象,内在含义是有一些private的参数作为对象的属性,然后针对每一个参数定义get和set方法访问的接口。
【没有从任何类继承、也没有实现任何接口。更没有被其他框架侵入的java对象】
JavaBean是遵循特殊约定的POJO,其所有属性为private,且提供无参数的构造器,这个类是可序列化的,实现serializable接口。
POJO(Plain Ordinary Java Object)与JavaBean的区别:
POJO是比JavaBean更纯净的简单类或接口。其严格遵守简单对象的概念,而JavaBean中往往会封装一些简单逻辑。
POJO主要用于数据临时传递,只能装载数据,作为数据存储载体,不具有业务逻辑处理能力。
JavaBean中可以有其他业务处理的方法。
- 定义数据对象DO类时,属性类型要与数据库字段类型相匹配。
- 禁止通过使用构造方法BigDecimal(double)的方式把double值转化为BigDecimal对象。推荐使用入参为String的构造方法或者BigDecimal的valueOf方法。否则会存在精度损失。
- 定义POJO类时,不要设定任何默认值。
- POJO类必须写toString方法,以便排查问题。
- 禁止在 POJO 类中,同时存在对应属性 xxx 的 isXxx()和 getXxx()方法。
日期时间
- 日期格式化时,传入pattern中表示年份统一使用小写的y。小写的y表示当天所在的年,大写的Y表示当周所在的年。
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
- 不允许在程序的任何地方使用 1)java.sql.Date 2)java.sql.Time 3)java.sql.Timestamp
集合处理
- 在使用java.util.stream.Collectors类的**toMap()**方法转为Map集合时,一定要使用含有参数类型为BinaryOperator,参数名为mergeFunction的方法,否则会抛出IllegalStateException异常。
- 在使用java.util.stream.Collectors类的**toMap()**方法转为Map集合时,当value为nulll时会抛出NPE异常。
- 不要在foreach循环里进行元素的remove/add操作
- 集合初始化时,指定集合初始值的大小。如new HashMap(int initialCapacity)
其中 initialCapacity = (需要存储的元素个数/负载因子)+ 1 , 负载因子 = 0.75
如果无法确定初始值,则默认16
并发处理
- 获取单例对象需要保证线程安全,其中的方法也要保证线程安全。
- 线程池不允许使用Executors创建,而是通过ThreadPoolExecutor创建,这样可以规避资源耗尽的风险。
- 必须回收自定义的ThreadLocal对象,否则可能会导致内存泄漏。
- 在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且在加锁方法与try代码块之间没有任何可抛出异常的方法调用,避免加锁成功后,在finally无法解锁。
- 在使用尝试机制来获取锁时,进入业务代码块之前,必须先判断当前线程是否持有锁。锁的释放规则与锁的阻塞等待方式一致。
- 并发修改同一记录时,为避免更新丢失需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,并用version作为更新依据。
控制语句
- 当switch括号内的变量类型为String并且此变量为外部参数时,该变量必须先进行null判断,否则可能会导致NPE。
- 三目运算符 condition? 表达式 1 : 表达式 2 中,高度注意表达式 1 和 2 在类型对齐
时,可能抛出因自动拆箱导致的 NPE 异常。
说明:以下两种场景会触发类型对齐的拆箱操作:
1) 表达式 1 或表达式 2 的值只要有一个是原始类型。
2) 表达式 1 或表达式 2 的值的类型不一致,会强制拆箱升级成表示范围更大的那个类型。
反例:
Integer a = 1;
Integer b = 2;
Integer c = null;
Boolean flag = false;
// ab 的结果是 int 类型,那么 c 会强制拆箱成 int 类型,抛出 NPE 异常
Integer result=(flag? ab : c);
- 高并发场景中避免使用等于判断作为中断或退出的条件。
- 表达异常的分支时,少用if-else 方式,也少用if-else if - else方式。最好直接用卫语句。
p.s. 卫语句就是将多个ifelse嵌套的语句转换成多个if语句,这样能将我们从多层嵌套的异常处理中解放出来,而关心真正的业务代码。 - 避免采用取反逻辑运算符,不易于理解。
- 接口入参保护。
某业务系统,提供一个用户批量查询的接口,API 文档上有说最多查多少个,但接口实现上没做任何保护,导致调用方传了一个 1000 的用户 id 数组过来后,查询信息后,内存爆了。
7.需要进行参数校验的场景:
调用频次低的方法。
执行时间开销大。
需要极高的稳定性和可用性的方法。
对外提供的方法。
敏感权限入口。
注释规约
- 所有的枚举字段都必须要有注释,说明每个数据项的用途。
- 对于暂时被注释掉,后续可能恢复使用的代码,在注释上方,统一规定用三个斜杠说明注释掉代码的理由。
异常日志
错误码
- 错误码不体现版本号和错误等级信息。
- 全部正常,但不得不填充错误码是返回五个零: 00000
- 错误码为5位的字符串类型。分为两个部分:错误来源 + 四位数字编号。
- 错误码之外的业务独特信息由errer_message承载,不要让错误码本身涵盖过多的业务属性。
异常处理
- RuntimeException异常不应该通过catch的方式处理,如NPE、IndexOutOfBoundsException等。
- 异常不要用来做流程控制、条件控制。
- 对于非稳定代码的catch尽可能区分异常类型,再做对应的异常处理。
- 捕获异常是为了处理它,不要捕获了异常什么都不做而抛弃它。如果不想处理请将异常抛给调用者。最外层的业务使用者必须处理异常,并转化为用户可以理解的内容。
- 事务场景中,抛出异常被caache后,如需回滚,一定要手动回滚事务。
- finally必须对资源对象、流对象进行关闭,有异常也要做try-catch。
如果JDK7 及以上,可以使用try-with-resources方式。
try (Connection conn = new Connection()) {
conn.sendData();
}
catch (Exception e) {
e.printStackTrace();
}
- 在调用RPC、二方包或动态生成类的相关方法时,捕获异常必须用Throwable类进行拦截。
- 防止NPE是程序员的基本素养。
日志规约
- 对于trace/debug/info级别的日志,必须进行日志级别的开关判断。
参数可能会进行字符串拼接计算,以及无谓方法的调用开销。
if(logger.isDebugEnabled){
logger.dubug("Current ID is: {} and name is: {}", id, getName())
}
- 日志打印时禁止直接用JSON工具将对象转换成String,如果对象中的get方法被重写,存在抛出异常的情况,则可能因为打印日志而影响正常业务流程的执行。
- 记录日志时请思考:这些日志真的有人看吗?这条日志能做什么?能不能给排查问题带来好处?
单元测试
- 单元测试中不准用SOUT人肉验证,必须使用assert验证。
- 单元测试是可以重复执行的,不能受到外界环境的影响。
- 编写单元测试代码遵守BCDE原则,以保证被测试模块的交付质量。
⚫ B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
⚫ C:Correct,正确的输入,并得到预期的结果。
⚫ D:Design,与设计文档相结合,来编写单元测试。
⚫ E:Error,强制错误信息输入(如:非法数据、异常流程、业务允许外等),并得到预期的结果。
MySQL数据库
建表规约
- 表达是与否概念的字段,必须使用is_xxx的方式命名,数据类型是unsigned tinyint
(1 表示是,0 表示否)。 - 禁用保留字。
- 小数类型为decimal,禁用floaat和double,会有精度损失。
- varchar是可变长度字符串,不预先分配存储空间,长度不要超过5000,如果存储长度大于此值,定义字段类型为text,独立出一张表,用主键对应,避免影响其他字段的索引效率。
- 表必备三字段:id、gmt_creat、gmt_modeified。即id、记录创建时间、记录修改时间。
- 单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表。
索引规约
- 业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。
- 超过三个表禁止join。
- 利用延迟关联或者子查询优化超多分页场景。
MySQL并不是跳过offset行,而是取offset+N行,然后返回前放弃前offset行,返回N行。当offset特别大,效率就会非常低下。【主键索引覆盖+关联优化】
SQL语句
- 不要使用count(列名)或count(常量)来替代count(*),后者是SQL92定义的标准统计行数的语法。
p.s. MySQL数据库有MyISAM和InnoDB两种执行引擎:
- 前者不支持事务且为表级锁,它对count(*)的优化是单独记录表的总行数。
- 后者为行级锁,对于count(*)和count(1)它会选择最小的非聚簇索引来扫表。优化的前提是不含where和聚集条件。
- 使用ISNULL()来判断是否为NULL值
- 代码中分页查询逻辑时,若count为0应直接返回,避免执行后面的分页语句。
- 不得使用外键和级联,一切外键概念必须在应用层解决。
- 禁用存储过程,难以调试和拓展,没有移植性。
- 数据订正时,要先select,避免出现误删除。
- 对于数据库中表记录的查询和变更,只要涉及多个表,都需要在列名前加表的别名(t1,t2,…)或表明进行限定。
- 更新数据表记录时必须记录更新时间。