记录一下
一些强制性的代码编写规范,方便自己方便他人。
原文来自
https://blog.youkuaiyun.com/qq_36178727/article/details/94047230
关于命名
1.代码中的命名不能以下划线或美元符号为开始和结尾
2.代码中的命名不能使用拼音和英文混用,严禁以纯中文命名
3.类名要使用UpperCamelCase风格以及驼峰式
4.方法名,参数名,成员变量,局部变量统一使用lowerCamelCase风格以及驼峰式
5.常量命名必须全部大写,单词间用下划线分隔,语义表达要完整清楚
6.抽象类命名要使用Abstract或Base开头
7.异常类命名使用Exception结尾
8.测试类命名以它要测试的类的名称开始,以Test结尾
9.中括号是数组类型的一部分,如String[] args
10.POJO类布尔类型的变量不能加is
11.包名统一使用小写,点分隔符之间有且仅有一个自然语义的英文单词,包统一使用单数形式,类名如果又复数含义,类名可以使用复数形式
12.杜绝不规范缩写命名,使用尽量完整的单词组合来表达其意
13.如果模块,接口,类,方法使用了设计模式,在命名时体现出具体模式
14.接口类中的方法和属性不要加任何修饰符,加上有效的Javadoc注释。
15.对于 Service 和 DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部的实现类用 Impl 的后缀与接口区别。
16.枚举类型建议带上Enum后缀,枚举成员名称需要全大写,单词间用下划线隔开
17.Service/DAO层获取单个对象用get做前缀,获取多个对象用list做前缀,获取统计值的方法用count做前缀,插入方法用save/insert做前缀,删除方法用remove/delete做前缀,修改用update做前缀。 数据对象xxxDO,xxx为数据表名 数据传输对象xxxDTO,xxx为业务领域相关的名称 展示对象xxxVO,xxx为网页名称 POJO是DO/DTO/BO/VO的统称,禁止命名为xxxPOJO
常量定义
1.不允许任何未经定义的常量直接出现在代码中
2.Long初始赋值时,使用大写的L
3.不要用一个常量类维护所有常量,按常量功能进行分类
4.跨应用共享常量放置在二方库中,通常是client.jar中的constant下
应用类共享常量放置在一方库中,modules中的constant
子工程内部共享常量:即在当前子工程的 constant 目录下。
包内共享常量:即在当前包下单独的 constant 目录下。
类内共享常量:直接在类内部 private static final 定义。
5.如果变量值仅在一个范围内变化,且带有名称之外的延伸属性,定义为枚举类。
代码格式
1.大括号的使用约定。如果是大括号内为空,则简洁地写成{}即可,不需要换行;如果是非空代码块则:
1) 左大括号前不换行。
2) 左大括号后换行。
3) 右大括号前换行。
4) 右大括号后还有 else 等代码则不换行;表示终止的右大括号后必须换行。
2.左小括号和字符之间不出现空格;同样,右小括号和字符之间也不出现空格。
3.if/for/while/switch/do 等保留字与括号之间都必须加空格。
4.任何二目、三目运算符的左右两边都需要加一个空格。
5.采用 4 个空格缩进,禁止使用 tab 字符。
6.注释的双斜线与注释内容之间有且仅有一个空格。
7.单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则:
1) 第二行相对第一行缩进 4 个空格,从第三行开始,不再继续缩进,参考示例。
2) 运算符与下文一起换行。
3) 方法调用的点符号与下文一起换行。
4) 方法调用时,多个参数,需要换行时,在逗号后进行。
5) 在括号前不要换行
8.方法参数在定义和传入时,多个参数逗号后边必须加空格。
9.IDE 的 text file encoding 设置为 UTF-8; IDE 中文件的换行符使用 Unix 格式, 不要使用 Windows 格式。
10.没有必要增加若干空格来使某一行的字符与上一行对应位置的字符对齐。
11.方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义之间插入一个空行。相同业务逻辑和语义之间不需要插入空行。
OOP规约
1.类的静态变量或静态方法直接用类名点来引用
2.所有的覆写方法,必须加@Override 注解。
3.相同参数类型,相同业务含义,才可以使用 Java 的可变参数,避免使用 Object。
4.外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生影响。
5.不能使用过时的类或方法。
6.Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。
7.所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。
8.所有的 POJO 类属性必须使用包装数据类型。
9.RPC 方法的返回值和参数必须使用包装数据类型。(分布式)
10.所有的局部变量使用基本数据类型。
11.定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值。
12.序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列失败;如果完全不兼容升级,避免反序列化混乱,那么请修改serialVersionUID 值。
13.构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。
14.POJO 类必须写 toString 方法。
15.当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起, 便于阅读,此条规则优先于公有方法或保护方法 > 私有方法 > getter/setter方法。
16.setter 方法中,参数名称与类成员变量名称一致,this.成员名 = 参数名。在getter/setter 方法中,不要增加业务逻辑,增加排查问题的难度。
17.循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展。
18.Final用来修饰不允许被继承的类,不允许修改引用的域对象,不允许被重写的方法,不允许运行过程中重新赋值的局部变量
19.慎用 Object 的 clone 方法来拷贝对象。
20.类成员与方法访问控制从严: 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private。工具类不允许有 public 或 default 构造方法。类非 static 成员变量并且与子类共享,必须是 protected。类非 static 成员变量并且仅在本类使用,必须是 private。
集合处理
1.只要重写 equals,就必须重写 hashCode。
2.ArrayList 的subList 结果不可强转成ArrayList
3.使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一样的数组,大小就是 list.size()。
4.使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法
5.泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方法,而<? super T>不能使用 get 方法,做为接口调用赋值时易出错。
6.不要在 foreach 循环里进行元素的remove/add 操作。remove 元素请使用 Iterator
并发处理
1.获取单例对象需要保证线程安全,其中的方法也要保证线程安全。说明:资源驱动类、工具类、单例工厂类都需要注意。
2.创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。正例:
public class TimerTaskThread extends Thread { public TimerTaskThread() {
super.setName(“TimerTaskThread”);
…
}
3.线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
4.线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2) CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
5.SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为
static,必须加锁,或者使用 DateUtils 工具类。
正例:注意线程安全,使用 DateUtils。亦推荐如下处理:
private static final ThreadLocal df = new ThreadLocal() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat(“yyyy-MM-dd”);
}
};
说明:如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe。
6.高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法。
7.对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。
说明:线程一需要对表 A、B、C 依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序也必须是 A、B、C,否则可能出现死锁。
8.并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用 version 作为更新依据。
说明:如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于 3 次。
9.多线程并行处理定时任务时,Timer 运行多个 TimeTask 时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。
控制语句
1.在一个 switch 块内,每个 case 要么通过 break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default 语句并且放在最后,即使它什么代码也没有。
2.在 if/else/for/while/do 语句中必须使用大括号。即使只有一行代码,避免采用单行的编码方式:if (condition) statements;
注释规约
1.类、类属性、类方法的注释必须使用 Javadoc 规范,使用/*内容/格式,不得使用// xxx 方式。
说明:在 IDE 编辑窗口中,Javadoc 方式会提示相关注释,生成 Javadoc 可以正确输出相应注释;在 IDE 中,工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高阅读效率。
2.所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。
说明:对子类的实现要求,或者调用注意事项,请一并说明。
3.所有的类都必须添加创建者和创建日期。
4.方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注意与代码对齐。
5.所有的枚举类型字段必须要有注释,说明每个数据项的用途。
异常日志
1.应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
2.日志文件推荐至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。
3.应用中的扩展日志(如打点、临时监控、访问日志等)命名方式: appName_logType_logName.log。logType:日志类型,推荐分类有stats/desc/monitor/visit 等;logName:日志描述。这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。
4.对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。
正例:(条件)
if (logger.isDebugEnabled()) {
logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);
}
正例:(占位符)
logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);
5.避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置additivity=false。
6.异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。
7.Java 类库中定义的一类 RuntimeException 可以通过预先检查进行规避,而不应该通过 catch 来处理,比如:IndexOutOfBoundsException,NullPointerException 等等。说明:无法通过预检查的异常除外,如在解析一个外部传来的字符串形式数字时,通过 catch NumberFormatException 来实现。
正例:if (obj != null) {…}
反例:try { obj.method() } catch (NullPointerException e) {…}
8.异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低。
9.对大段代码进行 try-catch,这是不负责任的表现。catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。
10.捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
11.有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回滚事务。
12.finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。说明:如果 JDK7 及以上,可以使用 try-with-resources 方式。
13.不能在 finally 块中使用 return,finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句。
14.捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。说明:如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。
单元测试
1.好的单元测试必须遵守 AIR 原则。
说明:单元测试在线上运行时,感觉像空气(AIR)一样并不存在,但在测试质量的保障上, 却是非常关键的。好的单元测试宏观上来说,具有自动化、独立性、可重复执行的特点。
A:Automatic(自动化)
I:Independent(独立性)
R:Repeatable(可重复)
2.单元测试应该是全自动执行的,并且非交互式的。测试框架通常是定期执行的,执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。单元测试中不准使用 System.out 来进行人肉验证,必须使用 assert 来验证。
3.保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。
反例:method2 需要依赖 method1 的执行,将执行结果做为 method2 的输入。
4.单元测试是可以重复执行的,不能受到外界环境的影响。
说明:单元测试通常会被放到持续集成中,每次有代码 check in 时单元测试都会被执行。如果单测对外部环境(网络、服务、中间件等)有依赖,容易导致持续集成机制的不可用。
正例:为了不受外界环境影响,要求设计代码时就把 SUT 的依赖改成注入,在测试时用 spring
这样的 DI 框架注入一个本地(内存)实现或者 Mock 实现。
5.对于单元测试,要保证测试粒度足够小,有助于精确定位问题。单测粒度至多是类级别,一般是方法级别。
说明:只有测试粒度小才能在出错时尽快定位到出错位置。单测不负责检查跨类或者跨系统的交互逻辑,那是集成测试的领域。
6.核心业务、核心应用、核心模块的增量代码确保单元测试通过。
说明:新增代码及时补充单元测试,如果新增代码影响了原有单元测试,请及时修正。
7.单元测试代码必须写在如下工程目录:src/test/java,不允许写在业务代码目录下。说明:源码构建时会跳过此目录,而单元测试框架默认是扫描此目录。
安全规约
1.隶属于用户个人的页面或者功能必须进行权限控制校验。
2.用户敏感数据禁止直接展示,必须对展示数据进行脱敏。
3.用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入, 禁止字符串拼接 SQL 访问数据库。
4.用户请求传入的任何参数必须做有效性验证。
5.禁止向 HTML 页面输出未经安全过滤或未正确转义的用户数据。
6.表单、AJAX 提交必须执行 CSRF 安全过滤。
7.在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放限制, 如数量限制、疲劳度控制、验证码校验,避免被滥刷、资损。
数据库规范
1.表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint( 1 表示是,0 表示否)。
2.表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。
3.表名不使用复数名词。
4.禁用保留字,如 desc、range、match、delayed 等
5.主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。
6.小数类型为 decimal,禁止使用 float 和 double。
7.如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
8.varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。
9.表必备三字段:id, gmt_create(主动创建时间), gmt_modified(被动更新时间)。
10.表的命名最好是加上“业务名称_表的作用”。
11.库名与应用名称尽量一致。
12.如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。
13.单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。
14.合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。
索引规约
1.业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
2.超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致;多表关联查询时, 保证被关联的字段需要有索引。
3.在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。
4.页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
5.如果有 order by 的场景,请注意利用索引的有序性。
6.利用覆盖索引来进行查询操作,避免回表。
7.利用延迟关联或者子查询优化超多分页场景。
8.SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts
9.建组合索引的时候,区分度最高的在最左边。
10.如果 where a=? and b=? ,a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即可。
Sql语句
1.不要使用 count(列名)或 count(常量)来替代 count(),count()是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
2.count(distinct col) 计算该列除 NULL 之外的不重复行数
3.当某一列的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为NULL,因此使用 sum()时需注意 NPE 问题。
4.使用 ISNULL()来判断是否为 NULL 值。
5.在代码中写分页查询逻辑时,若 count 为 0 应直接返回,避免执行后面的分页语句。
6.不得使用外键与级联,一切外键概念必须在应用层解决。
7.禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
8.数据订正时,删除和修改记录时,要先 select,避免出现误删除,确认无误才能执行更新语句。
ORM映射
1.在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。
2.POJO 类的布尔属性不能加 is,而数据库字段必须加 is_,要求在 resultMap 中进行字段与属性之间的映射。
3.不要用 resultClass 当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义;反过来,每一个表也必然有一个与之对应。
4.sql.xml 配置参数使用:#{},#param# 不要使用${} 此种方式容易出现 SQL 注入。
5.MyBATIS 自带的 queryForList(String statementName,int start,int size)不推荐使用。
6.不允许直接拿 HashMap 与 Hashtable 作为查询结果集的输出。
7.更新数据表记录时,必须同时更新记录对应的 gmt_modified 字段值为当前时间。
8.不要写一个大而全的数据更新接口。
9.@Transactional 事务不要滥用。
分层领域模型规约
• DO(Data Object):与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
• DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象。
• BO(Business Object):业务对象。由 Service 层输出的封装业务逻辑的对象。
• AO(Application Object):应用对象。在 Web 层与 Service 层之间抽象的复用对象模型, 极为贴近展示层,复用度不高。
• VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
• Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类来传输。