「本文已参与「新人创作礼」活动,一起开启掘金创作之路。」
1.命名风格
a.不能以下划线和美元符号作为开始和结束
b.禁止使用拼音,中文,还有拼音和英文混合的方式,国际通用的拼音视为英文
c.方法名,参数名,成员变量,局部变量都是统一使用小驼峰效法 d.常量名全部大写,每个单词用下划线隔开,不要嫌名字长
e.抽象类命名使用Abstract或Base开头 异常类命名使用Exception开头 测试类命名使用Test开头
f.数据类型和方括号来定义数组 g.在pojo类中的布尔型的变量 不要加上is开头,因为在部分框架中会解析错误 比如:isDeleted 反向解析的时候会变成 deleted h.包名统一使用小写
i.子父类的成员变量之间或者不同代码块的局部变量不要采用相同的命名,否则会降低理解性
j.禁止不规范的缩写 比如update写成up abstract写成 abs function写成fu等等
k.命名常量或者变量的时候,表示类型的放在后面 比如 nameList startTime workQueue
l.如果采用了设计模式,那么相关的命名后缀要有模式的名次 比如 public class OrderFactory;(工厂模式)
m.接口类中的方法和属性不要加任何的修饰符号,并加上有效的javadoc的注释 尽量不在接口里面定义常量,如果一定要定义,确定与接口方法相关,并且是整个应用的基础变量
n.对于Service和Dao类,基于SOA的理念,暴露出来的一定是接口 实现类用Impl后缀进行区别
o.枚举名带上Enum的后缀,成员名称需要全部大写,单词之间用下划线隔开
p.service和Dao的方法命名: 获取单个对象 get作为前缀 获取多个对象 list作为前缀 负数作为结尾 ListUser 获取统计值用 count作为前缀 插入方法用 save/insert作为前缀 删除方法用 remove/delete作为前缀 修改方法用 update做为前缀 数据对象:xxxDo或者xxx表示表名 数据传输对象:xxxDTO xxx作为业务的表名 展示对象:xxxVO POJO是DO/DTO/BO/VO的统称 但是禁止用POJO命名
2.常量定义
a.不允许魔法值,直接在声明在全局的地方,有需要的地方进行统一调用
b.Long或者long类型的数据,值必须是L大写后缀,小写容易混淆
c.不使用一个常量类维护所有的常量,根据功能划分去分开维护 缓存相关的:CacheConsts 系统相关的:SystemConfigConsts
d.如果变量值仅在一个固定范围内变化,则使用enum类型来定义
public enum SeasonEnum{ SPRING(1),SUMMER(2),AUTUMN(3),WINTER(4); private int seq; SeasonEnum(int seq){ this.seq = seq; } public int getSeq(){ return seq; } }
3.代码格式
a.大括号内为空,不需要换行 {},无须换行和空格
b.小括号中内容格式如下 if(a == b)
c.if for while switch do 等保留字与括号之间必须加空格 if () for ()
d.运算符左右两边都需要空格 e.采用四个空格锁紧 禁止使用tab
f.注释的斜线和内容有且只有一个空格
g.强制转换类型的右边括号要和后面的值相近。不能有空格
h.单行字符不超过200字符,超过要换行并相对于第一行锁进4,第三行开始不需要再锁进
i.定义和传参的时候,逗号后面要有空格依次和参数隔开
j.单个方法的行数不能超过80
4.oop规约(面向对象程序设计的规约)
a.覆写方法都必须使用@Override注解
b.只有相同参数,相同业务含义,才可以使用可变参数,否则要明确类型 Long type, String name, Integer age
c.若接口过时,要加上注解@Deprecated 并且说明新的结果或者新的服务是什么
d.不能使用过时的类或者方法 e.equals方法容易抛出空指针异常,所以要用常量或者确定有值的放在等号的左边
f.所有整型包装类对象之间的比较,全部使用equals 对于Integer var = 某个数字,如果在-127~128范围之内的赋值, Integer对象是在Cache产生的,会复用对象 如果超出范围,区间之外的数据会在堆上产生,不会复用已有对象
g.属性类型和数据库类型要相匹配 Bigint必须和Long 对应
h.禁止Bigdecimal(double)这样的处理,会出现丢失精度 优先推荐使用String的构造方法,或者使用Bigdecimal的valueOf方法
i.POJO类的属性都必须使用包装类型,PRC方法的返回值和参数也都必须使用包装类型 所有的局部变量 都使用基本类型
j.定义POJO类时候,不要设置任何属性的默认值。
k.序列化类新增属性时,不要修改serialversionUID字段。避免反序列化失败
l.禁止在pojo类中同时存在对应属性xxx的isxxx()和getxxx()方法 因为框架在调用属性xxx的时候,并不确定哪种方法一定是优先调用的
m.类内方法定义的顺序依次是:公有方法 保护方法 私有方法 getter setter
n.循环体内字符串的连接方式使用StringBuilder的append来扩展 因为如果用String,每次循环都会new一个Stringbuilder对象,然后进行append 最后通过toString方法返回String,会造成内存资源浪费
o.final用在: 不允许被继承的类 不允许修改引用的域对象 不允许被覆写的方法 不允许在运行过程中给局部变量重新赋值 避免上下文重复使用一个变量
5.日期时间
a.表示年份统一使用小写的y
b.大写M月份 小写m分钟 大写H24小时 小写h12小时
c.获取当前毫秒是 System.currentTimeMillis() 不是new Date().getTime();
d.允许使用 java.sql.Date/Time/Timestamp Date不记录时间,getHours会抛出异常 Time不记录日期,getTear()会抛出异常 Timestamp存储秒和纳秒信息
e.获取今年的天数 LocalDate.now.lengthOfYear();
6.集合处理
a.判断所有集合内部的元素是否为空,应使用isEmpty() 而不是用size()==0 isEmpty的时间复杂度 O(1) 可读性好
b.Collectors的类toMap方法转为Map集合的时候, 一定要注意value为null的时候会抛出空指针异常
c.ArrayList的subList结果不可强转成ArrayList,否则会抛出类型对应不上的错误
d.使用Map的keySet、values、entrySet返回集合对象的时候,不可以对其添加元素,否则会抛出异常
e.Collections类返回的对象,如果是emptyList或者是singletonList 则不能添加和删除元素 比如查询没有结果的emptyList,添加元素会抛出异常
f.使用集合转数组的方法,必须使用toArray。传入的类型要完全一样 List list = new ArrayList<>(); //转数组 String[] arr = list.toArray(new String[0]); 0:动态创建和size相同的数组,性能最好 大于0但是小于size 重新创建大小等于size的数组增加gc负担 等于size 在高并发情况下 数组创建完成之后size在变大的场景下,副作用同上 大于size 空间浪费并且在size处插入null值
g.当数组使用Arrays.asList转成集合的时候,add\remove\clear的方法调用会出现异常 原因是asList返回的对象是一个Arrays的内部类,并没有实现集合的修改方法,底层仍然是数组
h.不要在foreach循环中对元素进行remove\add的操作 remove的时候 要使用Iterator方式,如果是并发场景需要加锁 i.当使用范型来集合定义时,使用diamand的语法来完成省略 Map<String, String> userCache = new HashMap<>();
j.集合初始化的时候。制定集合的初始化值大小 HashMap初始化如果无法确定集合大小,那么制定默认值16 存储大小运算:需要存储的元素个数/负载因子 + 1 负载因子默认0.75
k.使用entrySet便利Map类的集合K/v ,二不是用keySet方式遍历 keySet其实遍历两次 一次是为了转成Iterator 一次是从hashmap中取出key对应的value entrySet则是直接取出 如果是1.8 那么使用Map.forEach()
7.并发处理
a.当创建线程或者线程池时,请指定有意义的线程名称,出错时方便回溯
b.线程资源必须通过线程池提供,不允许在应用中自行显式创建线程 使用线程池的好处是 减少在创建和销毁线程上消耗的时间和系统资源,解决资源不足的问题 不使用的话 有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题
c.线程池不允许使用Executors创建,而是通过ThreadPoolexecutor的方式创建 能让维护人员更加明确线程池的运行规则,规避资源耗尽的风险 例如:FixedThreadPool和SingleThreadPool允许的请求队列长度为Integer的max_value 可能会堆积大量的请求,导致oom CachedThreadPool允许创建的线程数量为Integer的max_value,可能会创建大量的线程 从而导致oom
d.SimpleDateFormat是线程不安全的类,一般不要定义为static的变量,如果定义为static, 那么就需要加锁,或者用DateUtils工具类
e.必须回收自定义的ThreadLocal变量,尤其是在线程池的场景下,线程经常会被复用 如果不清理自定义的Thread变量,可能会影响后续业务逻辑和内存泄漏的问题, 尽量在try-finally块中执行 objectThreadLocal.remove()
f.高并发场景中,同步调用应该考量锁的性能损耗,能用无锁数据结构就不要用锁的 能锁区块就不要锁整个方法体,能用对象锁,就不要用类锁
g.对于多个资源,数据库表,对象同时加锁时,需要保持一致的加锁顺序,否则会造成死锁 如果线程1需要对表a,b,c依次加锁后才可以进行更新操作,那么线程2也要按照这个顺序 否则可能会出现死锁
h.在使用尝试机制获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否持有锁, 锁的释放规则和锁的阻塞等待方式相同
i.在并发修改同一个记录时,为了避免更新丢失,需要加锁,要么在应用层加锁,要么在缓存层加锁, 要么在数据库层使用乐观锁,使用version作为更新依据 如果每次访问冲突概率小于20%,推荐使用乐观锁,否则使用悲观锁 乐观锁的重试次数不得小于3次
j.与资金相关的金融敏感信息,使用悲观锁策略 因为乐观锁在获得锁的同时已经完成更新操作,校验逻辑容易出现漏洞, 处理不当容易造成系统压力或数据异常 悲观锁-》“一锁二判三更新四释放”
8.控制语句
a.switch每个case要么通过continue/break/return等终止, 要么注释说明程序将继续执行到哪一个case, 一个switch块内必须包含一个default的语句并且放在后面,即使什么代码都没有
b.在if/else/for/while/do语句中,必须使用打括号,即使只有一行代码 也禁止不采用大括号的编码方式
c.在高并发场景中,避免使用“等于”判断作为中断或退出的条件 如果没有处理好并发控制,容易产生等值判断被“击穿”的情况, 使用大于或者小于的区间判断条件来代替 比如判读剩余数量等于0,可能因为并发处理导致负数,从而一直不等于,进而死循环
d.当某个方法的代码总行数超过10行时,return\throw等中断逻辑的右大括号后 都需要加一个空行
e.表达异常的分之,尽量少用if-else方式 写完if 在写else的情况 非要使用的话,那么不要超过3层(超过3层的话使用卫语句,策略模式,状态模式等)
f.不要在条件判断中放入复杂的语句,将负责逻辑的判断的值赋值给一个变量, 在逆行放入判断
g.不要在表达式中放入赋值,每个赋值都需要单独一行 h.避免使用取反逻辑运算符 比如 : if x<20 不要写成 if !(x>=20)