1.1命名风格
代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号开始。
常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
抽象类命名使用Abstract或Base开头:异常类命名使用Exception结尾:测试类命名以它要测试的类名开始:以Test结尾。
POJO类中布尔类型的变量都不要加is前缀,否则部分框架解析会引起序列化错误。
包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数模式,但是类名如果有复数含义,则类名可以使用复数模式。
避免在子父类的成员变量之间或者不同代码块的局部变量之间财通完全相同的命名方式,使可读性降低。(子类、父类成员变量名相同,即使属性使public也是能通过变异。)
public class ConfusingName {
public int alibaba;
//非setter/getter的参数名称
//不允许与本类成员变量同名
public void get(String alibaba) {
if(true) {
final int taobao = 15;
}
for (int i = 0; i < 10; i++) {
//在同一方法内,不允许与其他代码块中的
//taobao命名相同
final int taobao = 15;
}
}
}
class Son extends ConfusingName{
//不允许与父类的成员变量名称相同
public int alibaba;
}
在常量与便两个的命名时,表示类型的名词放在词尾,以提升辨识度。
如果模块、接口、类、方法使用了设计模式,应在命名时体现出具体模式。
接口类中的方法和属性不要加任何修饰符号(pubilic也不要加),尽量不要在接口里定义变量,如果一定要定义变量,必须是与接口方法相关的,并且是整个应用的基础变量。
枚举类名建议带上Enum后缀,枚举成员名称需要全大写。
各层命名规约:
Service/DAO层方法命名规约如下。
获取单个对象的方法用get作为前缀。
获取多个对象的方法用list作为前缀,复数结尾,如listObjects。
领域模型命名规约如下。
数据对象:xxxDO,xxx为数据表名。
数据传输对象:xxxDTO,xxx为业务领域相关的名称。
展示对象:xxxVO,xxx一般为网页名称。
POJO是DO/DTO/BO/VO的统称,禁止命名成xxxPOJO。
1.2 常量定义
在long或者Long初始赋值时,数值后使用大写L,不能时小写的l。
如果变量值仅在一个范围内变化,则用enum类型类定义。
1.3 代码格式
左大括号前需要加空格。
采用4个空格缩进,禁止使用tab控制符。
单行字符数不超过120个,超出则需要换行,第二行相对第一行缩进4个空格,从第三行开始,不再持续缩进。
1.4 OOP规约
浮点数之间的等职判断,基本数据类型不能用 == 进行比较,包装数据类型不能用equals方法进行判断。
反例
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
if(a == b) {
// 预期进入此代码块,执行其他业务逻辑
// 但是 a==b 的结果为false
}
Float x = Float.valueOf(a);
Float y = Float.valueOf(b);
if(x.equals(y)){
//预期进入此代码块,执行其他业务逻辑
// 但是x.equals(y)的结果为fasle
}
正例
指定一个误差范围,两个浮点数的插值在此范围之内,则认为是相等的.
使用BigDecimal来定义值,再进行浮点数的运算操作.
定义数据对象DO类时,属性类型要与数据库字段类型相匹配.
禁止使用构造方法BigDecimal(double)的方式把double值转化为BigDecimal对象
正例
BigDecimal good1 = new BigDecimal("0.1")
`BigDecimal good2 = BigDecimal.valueOf(0.1);
所有的POJO类属性必须使用包装数据类型,所有的局部变量使用基本数据类型.
在循环体内,字符串的连接方式使用StringBuilder的append方法进行扩展
下面例子中反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法Strting对象,造成内存资源浪费.
String str = "start";
for (int i = 0; i < 100; i++){
str = str + "hello";
}
1.5 集合处理
关于hashCode和equals的处理,遵循如下规则:
只要重写equals,就必须重写hashCode.
因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两种方法.
如果自定义对象作为Map的键,那么必须重写hashCode
说明:String重写了hashCode和equals方法,所以我们可以非常愉快地将String对象作为key来使用.
使用集合转数组地方法,必须使用集合的toArray(T[] array),传入地是类型完全一样地数组,大小就是list.size().
不要在foereach循环里进行元素的remove/add操作.remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁.
正例
List list = new ArrayList();
Iterator iterator = list.iterator();
while(iterator.hasNext()){
String item = iterator.next();
if(删除元素的条件){
iterator.remove();
}
}
反例(不能达到预想结果)
list.add("1");
list.add("2");
for (String item : list){
if ("1".equals(item)) {
list.remove(item);
}
}
使用entrySet遍历Map类集合K/V,而不是用keySet方式遍历。(keySet其实遍历两次)。
集合类
Key
Value
Super
说明
Hashtable
不允许为null
不允许为null
Dictionary
线程安全
ConcurrentHashMap
不允许为null
不允许为null
AbstractMap
锁分段技术
TreeMap
不允许为null
允许为null
AbstractMap
线程不安全
HashMap
允许为null
允许为null
AbstractMap
线程不安全
利用Set元素唯一的特性,可以快速对一个集合进行去重操作,避免使用List的Contains方法进行遍历、对比、去重操作。
1.7 控制语句
在一个switch块内,都必须包含一个default语句并且放在最后,即使它什么代码也没有。
当switch括号内的变量类型为String并且此变量为外部参数时,必须先进行null判断。
以下例子只会抛出空指针异常。
public class SwitchDemo {
public static void main(String[] args) {
method(null);
}
public static void method(String param) {
switch (param) {
// 肯定不是进入这里
case "sth":
System.out.println("sth");
break;
// 也不是进入这里
case "null":
System.out.println("it's null");
break;
//也不是进入这里
default:
System.out.println("default");
}
}
}
在高并发场景中,避免使用“等于”判断作为中断或退出的条件,(应该使用大于小于区间判断)
if()...else if()...else...不能超过三层。除了策略模式,状态模式外,还可以卫语句。(卫语句就是把复杂的条件表达式拆分成多个条件表达示,调条件为真时,立刻从方法体中返回给调用方。卫语句的好处时条件表达式之间相互独立,不会互扰)。
避免采用取反逻辑运算符。
1.8 注释规约
类、类属性、类方法的注释必须使用javadoc规范,使用/*内容/格式,不得使用//xxx方式。
2.1 异常处理
有try快放到了事务代码中,catch异常后,如果需要回滚事务,一定要注意rollback事务。
不要再finally块中使用return.
2.2 日志规约
应用中不可直接使用日志系统(Log4j,Logback中的api,而应依赖使用日志框架SLF4J中的api)。
日志如初时,字符串变量之间的拼接使用占位符的方式。
log.debug("Processing trade with id: {} and symbol: {}, id , symbol)
避免重复打印日志,否则会浪费磁盘空间。务必再日志配置文件中设置additivity=false
4 安全规约
用户输入的SQL参数严格使用参数绑定或者METADATA字段值限定。
表单、AJAX提交必须执行CSRF安全验证
URL外部重定向传入的目标地址必须执行白名单过滤。
再使用平台资源时,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放机制,如数量限制、疲劳度控制、验证码校验,避免被滥刷导致用户受扰或平台资损。
5.1 建表规约
表达是与否概念的字段,必须使用is_xxx的方式命名,数据类型是unsigned tinyint(1表示是,0表示否)。需要再设置从is_xxx到Xxx的映射关系。
表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。
小鼠类型为decimal,禁止使用float和double。
表必备三字段:id,create_time,update_time。
当单表行数超过500万行或者单表容量超过2GB时,才推荐进行分库分表。
5.2 索引规约
业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
当多表关联查询时,保证被关联的字段需要有索引。
在varchar字段上建立索引,必须指定索引长度。
如果有order by的场景,请注意利用索引的有序性。order by最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现file sorr的情况,影响查询性能。
正例: where a = ? and b= ? order by c;索引:a_b_c
反例:索引中有范围查找,那么索引有序性无法利用,如WHERE a>10 order by b;索引弄个a_b无法排序。
能够建立索引分类为主键索引、唯一索引、普通索引。
SQL性能优化的目标:至少要达到range级别,要求是ref级别,最好是const。
consts单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
ref指的是使用普通的索引。
range对索引进行范围检索。
建组合索引的时候,区芬顿最高的在最左边。
如果 where a = ? and b = ? , a列几乎接近于唯一值,那么只需要单建 idx_a索引即可。(如存在非等号和等号混合判断条件,在建索引时,请把等号条件的列前置。如where c>? and d=?,那么即使c的区分度更高。也必须把d放在索引的最前列。)
5.3 SQL语句
不要使用count(列名)或count(常量)来替代count()。(count() 会统计值为null的行,而count(列名)不会统计此列为null值的行。)
count(distinct column)计算该列除NULL外的不重复行数。注意,count(distinct column,column2),如果其中一列权威NULL,那么即使另一列有不同的值,也返回为0。
当某一列的值全为NULL时,count(column)的返回结果为0,但sum(column)的返回结果为NULL,因此使用sum()时需注意NPE问题。
不得使用外键与级联,一切外键概念必须在应用层解决。
禁止使用存储过程。
数据订正(特别是删除、修改记录操作)时,要先select,避免出现误删除,确认无误才能执行更新语句。
in操作能避免则避免,若实在避免不了,需要仔细评估in后面的集合元素数量,控制在1000个之内。
TRUNCATE无事务且不触发trigger,有可能造成事故。
5.4 ORM映射
不要用resultClass作为返回参数。
sql.xml配置参数使用:#{},#param#,不要使用${},此种方式容易出现SQL注入。
不允许直接拿HashMap与Hashtable作为查询结果集的输出。
@Transactional事务不要滥用。
6.1 应用分层
1:
image.png
开放接口层:可直接封装Service方法暴露成RPC接口;通过web封装成HTTP接口;进行网关安全控制、流量控制等。
Service层:通用业务处理层,它有如下特征。
对第三方平台封装的层,预处理返回结果及转化异常信息;
对Service层通用能力的下沉。如缓存方案、中间件通用处理。
与DAO层交互,对多个DAO组合复用。
2:在DAO层,产生的异常类型有很多,无法是用细粒度的异常进行catch,使用catch(Excception)方式,并throw new DAOException(e)。不需要打印日志。在Service层出现异常时,必须将出错日志记录到磁盘。
6.3 服务器
高并发服务器建议调小TCP协议的time wait 超时时间。
调大服务器所支持的最大文件句柄数(Flie Descriptor,简称为fd)。
7 设计规约
谨慎使用继承的方式进行扩展,优先使用聚合或组合的方式来实现。