开发手册
一、编程规范
(一)命名规范
类型 | 规范 |
---|---|
命名 | 不能以_$开头或结尾,正确英文拼写和语法,避免不规范缩写 |
类名 | UpperCamelCase风格(领域模式除外,DO、BO、DTO、VO) |
方法 参数 变量名 | lowerCamelCase风格 |
常量 | 全大写,以_隔开,语义表达完整,不要嫌长 |
抽象类名 | 以Abstract或Base开头 |
异常类名 | 以Exception结尾 |
测试类名 | 以Test结尾 |
常量类 | 以Consts结尾 |
数组 | 数组定义如下:String[] args |
布尔变量 | 不能加is |
包名 | 小写,一个自然语义单词,单数形式 |
设计模式 | 在类名中体现出具体模式 |
接口 | 字段和方法不加任何修饰符,以形容能力的接口名称,取形容词为接口名(通常-able形式) |
接口实现类 | 以Impl结尾 |
枚举类 | 以Enum结尾,枚举成员全大写,以_隔开(枚举就是特殊的常量类,且构造方法被默认强制私有) |
各层命名规范
Service/DAO层方法命名规范 |
1 获取单个对象的方法用 get 做前缀 2 获取多个对象的方法用 list 做前缀 3 获取统计值的方法用 count 做前缀 4 插入的方法用 save(推荐)或 insert 做前缀 5 删除的方法用 remove(推荐)或 delete 做前缀 6 修改的方法用 update 做前缀 |
---|---|
领域模型命名规范 |
1 数据对象:xxxDO,xxx为数据表名 2 数据传输对象:xxxDTO,xxx为业务领域相关的名称 3 展示对象:xxxVO,xxx一般为网页名称 4 POJO是DO/DTO/BO/VO的统称,禁止命名为xxxPOJO |
(二)常量定义
类型 | 规范 |
---|---|
常量 | 不允许魔法值(未定义常量)直接出现在代码中 |
长整型 | 初始化用L(大写)结尾,避免l(小写)与数字1混淆 |
常量类 | 按功能进行分类,分开维护 |
复用层次 | 1 跨应用共享常量 2 应用内共享常量 3 子工程内共享常量 4 包内共享常量 5 类内共享常量 |
Enum类 | 如果变量在范围内变化用Enum,如果还带有名称之外的延伸属性,必须用Enum类,如MONDAY(1) |
(三)格式规约
类型 | 规范 |
---|---|
大括号的使用 | 1 空代码块 简洁地写成{},不需要换行 2 非空代码块:左大括号前不换行,后换行; 右大括号前换行,后换行(还有else则不换行,表示终止必须换行) |
括号的使用 | 左括号与后一个字符之间不出现空格,右括号与前一个字符之间也不能出现空格 |
关键字 | if/for/while/switch/do 等保留字与左右括号之间都必须加空格 |
运算符 | 任何运算符左右都必须加一个空格 |
缩进 | 缩进采用4个空格,禁止使用tab字符 |
例: | |
单行字符 | 单行字符数限制不能超过120个,超出需要换行,遵循原则如下: 1 第二行相对第一行缩进4字符,第三行开始,相对第二行不再缩进; 2 运算符与下文一起换行 3 方法调用.符号与下文一起换行 4 多个参数超行,逗号后进行换行 5 在括号前不要换行 |
方法参数 | 方法参数在定义和传入时,多个参数逗号后边必须加空格 |
编码和换行符 | IDE的text file encoding设置为utf-8,IDE文件换行符使用Unix格式,而非windows格式 |
字符对齐 | 没有必要增加若干空格来使某一行的字符与上一行的相对字符对齐 |
方法体 | 方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义之间插入一个空行(没有必要插入多行)。相同业务逻辑和语义之间bu需要插入空行 |
(四)OOP规约
类型 | 规范 |
---|---|
静态成员访问 | 直接使用类名访问,避免通过对象引用访问,无谓增加编译器解析成本。 |
覆写方法 | 所有覆写方法,必须添加@Override注解(准确判断是否覆盖成功) |
可变参数编程 | 相同参数类型,相同业务含义,才可使用Java的可变参数,避免使用Object,可变参数必须放置在参数列表的最后 |
对外暴露的接口签名 | 原则上不允许修改方法签名,避免对接口调用方产生影响,接口过时必须添加@Deprecated注解,并清晰第说明采用的新接口或者新服务是什么 |
过时的类或方法 | 不能调用过时的类或方法,作为调用方,有义务去考证过时方法的新实现是什么 |
equals方法 | 应使用常量或确保有值的对象来调用equals,避免空指针异常 |
包装类比较 | 所有的相同类型的包装类之间值的比较,全部使用equals方法比较, 原因:Integer在-128 至127之间的赋值,Integer对象在IntegerCache.cache产生,会复用已有对象,而在这个区间之外,都会在堆上产生。 例子:Integer a = 111;Integer b = 111; a == b为true; Integer a = 1111;Integer b = 1111;a == b为false |
基本数据类型 包装数据类型使用标准 |
1 所有的POJO类属性必须使用包装数据类型 2 RPC方法的返回值和参数必须使用包装数据类型 3所有的局部变量【推荐】使用基本数据类型。 说明:POJO类无初值提醒使用者显式赋值,任何NPE问题,入库检查,都由使用者保证。 |
POJO类 | 定义DO/DTO/VO等POJO类时,不要设定任何属性默认值 |
serialVersionUID | 序列化类新增属性时,请不要修改serialVersionUID字段,避免反序列化失败; 如果完全不兼容升级,避免反序列化混乱,那么请修改serialVersionUID值 |
构造方法 | 构造方法里面禁止写入任何业务逻辑,如果有初始化逻辑,请放在init方法中 |
toString方法 | POJO类必须写toString方法,使用IDE中的工具,source>generate toString时,如果继承了另一个POJO类,注意在前面加一下super.toString()(即同时输出父类的信息,在发放抛出异常时,通过toString()方法打印其属性值,便于排查问题)。 也可使用apache common工具类库的ReflectionToStringBuilder.toString(this)通过反射打印出类中的属性,包括父类的属性。 |
String的split方法 | 使用索引访问String的split方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛IndexOutOfBoundsException的风险;例:"a,b,c,,"; |
同名方法 | 当一个类中有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,便于阅读。 |
类内方法定义顺序 | 公有或保护方法 > 私有方法 > getter/setter方法。 |
getter/setter方法 | setter方法中,参数名称与类成员名称一致,this.成员名=参数名,在getter/setter方法中,尽量不要增加业务逻辑,增加排查问题的难度。 |
字符串联接 | 在循环体内,字符串的联接方式,使用StringBuilder的append方法进行扩展。反编译出的字节码文件显示每次循环都会new一个StringBuilder对象,进行append操作,并调用toString方法返回String对象,造成内存资源浪费。 |
final关键字 | final可提高程序响应效率,声明成final的情况: 1 不需要重新赋值的变量,包括类属性、局部变量。 2 对象参数前加final,表示不允许修改引用的指向。 3 类方法确定不允许被重写。 |
clone方法 | 慎用Object的clone方法来拷贝对象,默认是浅拷贝,共享对象属性引用,若想实现深拷贝需重写clone方法实现属性对象的拷贝。 |
类成员与方法访问控制从严 | 1 不允许外部直接new创建对象,构造方法必须是private 2 工具类不允许有public 或 default构造方法 3 类非static成员变量并且与子类共享,必须是protected 4 类非static成员变量并且仅在本类使用,必须是private 5 类static成语言变量并且仅在本类使用,必须是privat 6 若是static成员变量,必须考虑是否为final 7 类成员方法只供类内部调用,必须是private 8 类成员方法只对继承类公开,那么限制为protected |
(五)集合处理
类型 | 规范 |
---|---|
hashCode和equals | 1 只要重写equals,必须重写hashCode[equals 结果相同,hashCode必须一致;hashCode相同,equals不一定相同] 2 Set存储的是不可重复的对象,依据hashCOde和equals进行判断,所以Set存储的对象必须重写方法 3 如果自定义对象作为Map的键,那么必须重写hashCode和equals |
ArrayList的subList | subList结果不可强转为ArrayList,否则会抛出ClassCastException异常:java.util.RandomAccessSubList cannot be cast to java.util.ArrayList; 说明:subList返回的是ArrayList的内部类SubList,并不是ArrayList,而是ArrayList的一个视图,对于SubList子列表的所有操作最终会反映到原列表上。 |
subList场景 | 在subList场景中,高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、删除均产生ConcurrentModificationException异常。 |
集合转数组 | 必须使用集合的toArray(T[] a),传入的是类型完全一样的数组,大小就是list.size(); 说明:调用toArray代餐方法,a.length小于集合size时,重新分配内存空间,返回新数组地址(长度为集合size),a.length大于集合size时,设置[list.size()]的数组元素为null,其他数组元素保持原值,因此最好a长度与集合元素个数一致。 |
Arrays.asList() | 使用工具类Arrays.asList()把数组转换成集合时,无法调用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportedOperationException异常。 说明:asList的返回对象是一个Arrays内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。 |
泛型通配符<? extends T> | 泛型上限,用来接受返回的数据,此写法的泛型集合不能使用add方法。 |
foreache循环 | 集合foreach底层是迭代器模式,在foreach循环中进行元素的remove/add操作可能会抛出异常,remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。 |
Comparator | JDK7版本以上,Comparator要满足自反性,传递性,对称性,不然Arrays.sort,Collections.sort会报IllegalArgumentException异常。 |
集合初始化 | 集合初始化时,尽量指定集合初始值大小。 |
键值对集合遍历 | 使用entrySet遍历Map类集合KV,而不是keySet方式进行遍历。 说明:keySet其实是遍历了2次,一次是转为Iterator对象,另一次是从hashMap中取出key所对应的value,而entrySet只遍历一次就把k,v都放到entry中,效率更高,如果是JDK8,使用Map.foreach方法。 |
Map类存储null值 |
说明:不允许null值时,存储null值会抛出NPE异常。 |
集合的有序性和稳定性 | 合理利用好集合的有序性(sort)和稳定性(order),皮面集合的无序性(unsort)何不稳定性(unorder)带来的负面影响。 说明:稳定性指集合每次遍历的元素次序是一定的。有序性是指遍历的结果是按某种比较规则依次排序的。如:Array是order/unsort;HashMap是unorder/unsort;TreeSet是order/sort; |
Set特性 | 利用Set元素唯一的特性,可以快速对一个集合进行去重操作,避免使用List的contains方法进行遍历、对比、去重操作。 |
并发处理
类型 | 规范 |
---|---|
获取单例对象 | 需要保证线程安全,其中非方法也要保证线程安全 说明:资源驱动类、工具类、单例工厂类都需要注意。 |
创建线程或线程池 | 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。 |
线程资源 | 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。避免造成系统创建大量同类线程而导致消耗完内存或“过度切换”问题。 |
线程池创建 | 不允许使用Executors创建,而是通过ThreadPoolExecutor的方式,明确线程池的运行规则,规避资源耗尽的风险。 说明:FixedThreadPool 和SingleThreadPool 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量请求,从而导致OOM。 CachedThreadPool 和ScheduledThreadPool允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。 |
SimpleDateFormat | 是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类,亦推荐使用ThreadLocal。 说明:如果是JDK8,可使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFirmatter。 |
高并发 | 高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。 |
并发修改 | 并发修改同一记录时,避免更新丢失,要么在应用层加锁,要么在缓存加锁,要么在数据层使用乐观锁,使用version作为更新依据。 说明:如果每次访问冲突概率小于20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不能小于3次。 |
定时任务 | 多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其他任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题。 |
CountDownLatch | 使用CountDownLatch进行异步转同步操作,每个线程退出前必须调用countDown方法,线程执行代码注意catch异常,确保countDown方法可以执行,避免主线程无法执行至countDown方法,直到超时才返回结果。 说明:注意,子线程抛出异常堆栈,不能在主线程try-catch到。 |
Rondom | 避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed导致性能下降。 说明:Random实例包括java.util.Random和Math.random()实例。 在JDK7之后,可以直接使用API ThreadLocalRandom,在JDK7之前,可以做到每个线程一个实例。 |
双重检查 | 通过双重检查锁(double-checked locking)(在并发场景)实现延迟初始化的优化问题隐患,推荐问题解决方案中较为简单一种(JDK5之后),将目标属性声明为volatile型。 |
volatile | volatile解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题.如果是i++操作,通过AtomicInteger.addAndGet(1);如果是JDK8,推荐使用LongAdder对象,比AtomicLong性能更好(减少乐观锁的重试次数)。 |
HashMap | HashMap在容量不够进行resize时由于高并发可能出现死链,导致CPU飙升,在开发过程中注意规避此风险。 |
ThreadLocal | ThreadLocal无法解决共享对象的更新问题,ThreadLocal对象建议使用static修饰,这个变量是针对一个线程内所有操作共有的,所以设置为静态变量,所有此类实例共享此静态变量,也就是说类第一次被使用时装载,只分配一块存储空间,所有此类的对象(同一线程内)都可以操作这个变量。 |
控制语句
类型 | 规范 |
---|---|
switch | 在switch块中,每个case要么通过break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch块内,都必须包含一个default语句并且放在最后,即使什么代码也没有。 |
if/else/for/while/do | 在if/else/for/while/do语句中必须使用大括号,即使只有一行代码 |
else | 推荐尽量少用else,if-else 可改写为 if() { return} //接着写else业务逻辑 说明:如果非得使用if..else if..else,勿超过3层,超过请使用卫语句,或者状态模式来实现。 |
条件判断语句 | 不要在条件判断中执行复杂语句,将复杂语句的结果赋值给一个有意义的布尔变量名,以提高可读性。 |
循环体 | 循环体重的语句要考量性能,以下操作尽量移至循环体外处理,定义对象、变量、获取数据库连接,进行不必要的try-catch操作(这个try-catch是否可以移至循环体外) |
接口入参保护 | 对接口接受参数进行检验,避免错误不合理的参数,这种场景常见的是用于做批量操作的接口。 |
参数校验 | 场景:1 调用频次低的方法。 2 执行时间开销大,参数校验时间几乎可以忽略不计,但因为参数错误导致中间执行回退或者错误,得不偿失。 3 需要极高稳定性和可用性的方法。 4 对外提供的开发接口,不管是RPC/API/HTTP接口。 敏感权限入口。 |
不需要参数校验 | 场景:1 既有可能被循环调用的方法,不建议对参数进行校验。但在方法说明里面必须注明外部参数检查。 2 底层方法调用频度都比较高,一般不校验。 3 被声明成private只会被自己代码调用的方法时,能够确认调用方法的传入参数做过检查或肯定不会有问题,此时可以不校验参数。 |
注释规约
类型 | 规范 |
---|---|
类、类属性、类方法 | 注释必须使用Javadoc规范,/* /,不得使用//xxx |
抽象方法(接口方法) | 必须要用Javadoc注释,除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。 对子类的实现要求,或者调用注意事项,请一并说明。 |
类 | 所有的类都必须添加创建者信息。 |
方法内部注释 | 方法内部单行注释在被注释语句上方另起一行,使用//注释。方法内部多行注释 使用/ /注释,注意与代码对齐。 |
枚举字段 | 所有枚举类型字段必须要有注释,说明每个数据项的用途。 |
英文注释 | 英文水平不好,不如用中文注释把问题说清楚,专有名词与关键字保持英文原文。 |
代码修改 | 代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改。 |
注释掉的代码 | 尽量配合说明,而不是简单的注释掉 说明:代码被注释有两种可能,1后续恢复此段代码逻辑 2永久不用 前者如果不备注信息,难以知晓注释动机,后者建议直接删掉(代码仓库保存历史代码) |
注释要求 | 一、准确反应设计思想和代码逻辑;二、描述业务含义,使阅读者能迅速了解代码背后信息。可以帮助自己后期理解当时思路;也可给继任者快速接替自己工作。 |
有效注释 | 好的命名、代码结构是自解释的,注释力求精简准确、表达到尾。避免滥用。 |
特殊注释标记 | 1 TODO(待办事宜):(标记人、标记时间、[预计处理时间]) 2 FIXME(错误,不能工作):(标记人、标记时间、[预计处理时间]) |
其他
类型 | 规范 |
---|---|
正则表达式 | 利用好其预编译功能,可以有效加快正则匹配速度 不要再方法体内定义: Pattern pattern = Pattern.compile(规则); |
velocity | velocity调用POJO类的属性时,建议直接使用属性名取值即可,模板引擎会自动按规范调用POJO的getxxx(),如果是boolean基本数据类型变量(boolean命名不需要加is前缀),会自动调用isxxx(),如果是Boolean包装类对象,优先调用getxxx()。 |
页面变量 | 如果后台输送给页面的变量必须加$!{var} 中间的感叹号,如果var==null或不存在,那么${var}会直接显示在页面上 |
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。