java开发规范
第一节:命名风格
1.方法名,参数名,成员变量都统一使用lowerCamelCase风格,必须遵从驼峰形式
2.类名必须使用upperCamelCase风格,但以下情形例外:DAO/BO/PO/VO/UID
3.常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长
4.代码中命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束
5.代码的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式
6.实体类中布尔类型变量都不要加is前缀,否则部分框架解析会引起序列化错误
7.包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词
8.抽象类命名使用Abstract或Base开头:异常类命名使用Exception结尾;测试类命名以它要测试的类的名称开始,以Test结尾
9.类型与中括号紧接相连表示数组
10.杜绝完全不规范的缩写,避免望文不知义
11.枚举类名带上ENUM后缀,枚举成员名称需要全大写,单词间用下划线隔开
第二节: 常量定义
12.不允许任何魔法值(既未经预先定义的常量) 直接出现在代码中
13.在long或者Long赋值时,数值后使用大写的L,不能是小写的l,小写容易跟数字1混淆,造成误解
14.不要使用一个常量类维护所有常量,要按照常量功能进行归类,分开维护
15.如果变量值仅在一个固定范围内变化用enum类型来定义
第三节 代码格式
-
如果是大括号内为空,则简洁写成{}即可,大括号中间无需换行和空格;如果非空代码块则,按如下规则去写:
1) 左大括号前不换行 2) 左大括号后换行 3) 右大括号前换行 4) 右大括号后还有else 等代码则不换行;表示终止的右大括号后必须换行
-
左小括号和字符之间不出现空格;同样,右小括号和字符之间也不出现空格;而大括号前需要空格
-
if/for/while/switch/do 等保留字与括号之间都必须加空格
-
任何二目,三目运算符的左右两边都需要加一个空格
-
使用4个空格缩进,进制使用tab字符
-
注释的双斜线与注释内容之间有且仅有一个空格
-
在进行类型强制转换时,右括号与强制转换之间不需要任何空格隔开
-
单行字符限制不超过120个,超出需要换行,换行时遵循如下原则
1.第二行相对第一行缩进4个空格,从第三行开始,不再进行缩进,参考示例 2.运算符与下文一起换行 3.方法调用的点符号与下文一起换行 4.方法调用中的多个参数需要换行时,在逗号后进行 5.在括号前不需要换行
-
方法参数在定义和传入时,多个参数逗号后边必须加空格
-
单个方法的总行数不超过80行
-
没必要增加若干空格来使变量的赋值等号与上一行对应位置的等号对齐
-
不同逻辑,不同语义,不同业务的代码之间插入一个空行分隔开来,以提高可读性
第四节 注释规范
28.类,雷属性,类方法的注释必须使用javadoc规范,使用/**内容格式,不得使用// xxx方式
- 所有抽象方法(包括接口中的方法)必须要用javadoc注释,除了返回值,参数,异常说明外,还必须指出该方法做什么事情,实现了什么功能
- 所有的类都必须添加创建者日期
- 方法内部单行注释,在被注释语句上方另起一行,使用/注释. 方法内部多行注释使用/**/注释,注意与代码对齐
- 所有的枚举类型字段必须要有注释,说明每个数据项的用途
- 代码修改的同事,注释也要进行相应的修改,尤其是参数,返回值,异常,核心逻辑等修改
第五节 控制语句
-
每行最多包含一条语句
-
在if/else/for/while语句中必须使用大括号
-
在一个switch块内,每个case要么通过continue/break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch快内,都必须包含一个default语句并且放在最后,即使它什么代码都没有
-
当switch括号内的变量类型为String并且此变量我外部参数时候,必须先进行null判断
-
在高并发场景中,避免使用"等于"判断作为中断或退出条件
说明:如果并发没有控制好,容易产生等值判断被"击穿",使用大于或小于的区间判断条件来替代
-
表达异常的分支时,少用if-else方式,这种方式可以改写成
if(condition){ ... return obj; } // 接着写else的业务逻辑代码
-
除常用方法(如getxxx/isXxx)等外, 不要在条件判断中执行其他复杂语句,将复杂逻辑判断的结果赋值给一个有意义的布尔值变量明,以提高可读性
//伪代码 final boolean existed =xxx; if(existed){ .... }
-
不要在其他表达式(尤其是条件表达式)中,插入赋值语句
-
循环体重的语句要考量性能,一下操作计量移植到循环外处理,如定义对象,变量,获取数据库链接,进行不必要的try-catch操作(这个try-catch移动到循环体外)
-
避免采用取反逻辑运算符
-
接口入参保护,这种场景常见的是用做批量操作的接口
-
关于方法参数校验的补充说明:
需要进行参数校验的场景
-
调用频次的方法
-
执行时间开销很大的方法
-
需要极高稳定性和可用性的方法
-
对外提供的开放接口,不管是RPC/API/HTTP接口
-
敏感权限入口
不要进行参数校验的场景
1.极有可能被循环调用的方法,但在方法说明里必须注明外部参数检查要求
- 底层调用频率比较高的方法
- 被声明成private只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参数已经做过检查或肯定不会有问题,此时可以不校验参数
-
其他
- Math.random() 这个方法返回都是double类型,注意取值的范围0<=x<1,若干倍取整数,Random对象的nextInt或netLong方法
- 获取当前毫秒数System.currentTimeMills() 而不是 newDate().getTime();
下面整理不分先后,按照图片顺序
-
所有整型包装类对象之间值的比较,全部使用equals方法
-
浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用equals来判断
-
使用BigDecimal类处理需要计算比较的金额对象
-
为了防止精度丢失,禁止使用构造方法BigDecimal(double)的方式把double值转化为BigDecimal对象
// 错误 BigDecimal g =new BigDecimal(0.1f) // 正确 BigDecimal g2 = new BigDecimal("0.1") BigDecimal g3 = new BigDecimal.valueOf(0.1)
-
序列化类新增属性时,请不要修改serialVersionUID字段,避免反序列失败;如果完全不兼容升级,避饭序列化混乱,那么请修改serialVersionUID值
-
用静态工厂方法替代构造方法
-
使用索引访问用String的split方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛出IndexOutOfBoundsException的风险
-
避免通过一个类的对象引用访问此类的静态变量或静态方法,无需增加编译成本,直接用类名来访问即可
-
所有的覆写方法,必须加@Override注释
-
相同参数类型,相同业务含义,才可以使用Java的可变参数,避免使用Object
-
不能使用过时的类或方法
-
Object的equals方法容易抛出空指针异常,应该使用常量或确定有值的对象来调用equals
-
日期格式化时,传入pattern中表示年份统一使用小写的y
MM时月份 HH是24小时 hh12小时 mm是分钟
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
-
避免出现重复的代码(Don't Repeat Yourself) , 即DRY原则
-
将字符串形式的日期进行Date对象转换或校验时,必须把完整的年月日时间信息传入,防止特殊日期格式导致校验异常
//正例 Date date = DateUtils.parseDateStrictly("20240229125500",yyyyMMddHHmmss) //反例 Date date = DateUtils.parseDateStrictly("0229125500","MMddHHmmss")
-
setter方法中,参数名称与成员变量名称一致,this.成员名=参数名, 在getter/setter方法中,不要增加业务逻辑,增加排查问题的难度
-
循环体内,字符串的连接方式,使用StringBuilder的append方法进行扩展
-
使用final声明类,成员变量,方法,以及本地变量
// 下面情况使用final关键字 1.不允许被继承的类,如String类 2. 不允许被修改引用域对象 3. 不允许被覆写的方法,如PoJO的setter方法 4. 不允许运行过程中重新赋值的局部变量 5. 避免上下文重复使用一个变量,使用final可以强制重新定义一个变量,方便更好的重构
-
慎用Object的clone方法拷贝对象
集合处理
-
关于hashCode和equals的处理,必须遵循一定规则
只要覆写equals就必须覆写hashCode 因为 set存储的是不重复对象,依据hashcode和equals,所以set存储的对象必须覆写这两个方法 如果自定义对象作为Map的键,那必须覆写hashcode和equals
-
ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException
subList返回是ArrayList的内部类SubList,并不是ArrayList,而是ArrayList的一个视图,对于
SubList子列表的所有操作最终会反映到原列表上
-
使用Map的方法keySet()/value()/entrySet()返回集合对象时候,不可以对其进行元素操作,否则会抛出UnsupportedOperationException
- Collections类返回的对象,如: emptyList()/singletonList()都是immutablelist,不可对其进行添加或者删除元素的操作
-
在subList场景中,高度注意对元集合元素的增加或删除,均会导致子列表的遍历,增加/删除都会产生
ConcurrentModificationException异常
-
使用集合转数组的方法,必须使用的toArray(T[]array) , 传入的类型完全一致,长度为0的空数组
-
集合声明时需要指定泛型定义
-
集合初始化时,指定集合初始化值大小
-
高度注意Map类集合k/v能不能存储null值的情况,如下表
hashtable k v 不为null,线程安全 concurrenthashMap k/v 不为null 锁分段技术 treeMap k 不为null v 可以为null 线程不安全 hashMap k/v 允许为null 线程不安全
-
使用entrySet遍历Map类集合KV,而不是keySet遍历
-
优先使用列表而非数组
-
在使用Collection接口任何实现类的addAll()方法时,都要对输入的集合参数进行NEP判断
-
使用工具列Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,他的add/remove/clear会抛出异常
-
不要在foreach循环里进行元素的remove/add操作, remove元素请使用iterator方法,如果并发操作,需要对iterator对象加锁
-
在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否持有锁,锁的释放规则与锁的阻塞等待方式相同
-
获取单例对象需要保证线程安全,其中的方法也要保证线程安全
-
线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
-
现成池不允许使用Executors去创建,而是通过ThreadPoolExecurtor的方式,这样的处理方式让大家明确线程池的运行规则,规避资源耗尽的风险
-
SimpleDateFromat是线程不安全的类,一般不要定义为static变量,如果定义必须加锁
-
必须回收自定义的ThreadLocal变量,尤其是在线程池场景下,现成经常会被复用,如果不清理自定义的ThreadLocal变量,可能影响后续业务逻辑和造成内存
-
Java类库中定义的可以通过预检查方式规避的runtimeException异常不应该ton过catch的方式来处理
-
异常不要用来做流程处理,条件控制
-
catch时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错,对于非稳定的代码catch尽可能区分异常类型,做对应异常处理
-
有try块放到了事务代码中,catch异常后,如果需要回滚事务,一定要注意手动回滚事务
-
须通过finally块对资源对象,流对象进行关闭,有异常也要做try-catch
-
不要在finally块中使用return
-
方法返回值可以为null,不强制返回空集合/空对象,必须添加注释
-
防止出现NPE null point Exception
1.返回类型为基本类型,return 包装数据类型 2. 数据库查询结果可能为null 3. 集合的元素即使isNotEmpty,取出的元素也可能为null 4.远程调用返回对象时候,一律要进行 5.对于Session中获取的对象,建议进行NPE检查,避免空指针 6. 级联调obj.getA().getB().getC(); 一连串调用,容易产生NPE
-
避免重复打印,日志配置中指定additivity=false
-
单条日志必须同时包含数据/描述,不可拆分后分为两句输出
-
对trace/debug/info级的日志输出,尤其是日志内容包含计算逻辑的,必须进行日志级别的开关控制
-
通过传入throwable对象记录异常信息
-
日志打印时进制直接用json工具将对象转换成String
-
类在设置时候符合单一职责原则
-
系统设计时,根据依赖倒置原则,尽量依赖抽象与接口(优先使用接口),有利于扩展与维护
-
返回码定义:
-
系统编号8位,+子系统2位_错误码4位(数字)
GEMS00004PD2718
-
-
常见异常规范:
- 数据库异常:9000
- 网络通讯异常:9001
- MQ等消息队列异常:9002
- Redis等缓存中间件异常:9003
- 文件流操作异常:9004
- 工作流异常:9005
- 其他未知异常:9006
-
返回码与返回描述应一一对应,禁止出现一个返回码对应多个返回描述的情形
-
返回码禁止修改或丢弃原始返回码进行转译
数据库方面的
-
复合索引的列数量不建议超过阈值
-
数据库对象命名不建议大小写字母混合
-
不建议使用set类型(集合的修改需要重新定义列)
-
BlOB和TEXT类型的字段不建议设置为Not null
-
不建议使用ENUM类型
-
索引个数建议不超过阈值
-
表中包含有太多的列
-
数据库名称必须使用固定的后缀结尾
-
谨慎使用视图
-
使用delete/drop/truancate等操作时候注意备份
-
不建议对条件字段使用负向查询
-
insert语句必须指定column
-
使用Join链接表查询建议不超过阈值
-
请使用<> 代替!=
-
检查DDL语句中是否使用了中文全角引号
-
join字段字符集校对规则不一致
-
where条件内in语句中的参数个数不能超过阈值 100
-
索引命名规范 (唯一索引名为uk1_表名 uk2_表明 普通为l1_表名)
-
禁止使用全模糊搜索或左模糊搜索 (导致全表扫描)
-
检查DDL创建的新索引对应字段是否存在过多索引
-
建表DDL必须包含更新时间字段,且默认值为current_timestamp on current_timestamp
-
别名不要与列或表名字相同
-
建议列与表使用同一个字符集
-
避免使用标量子查询
-
单条insert语句,建议批量插入不超过阈值100
-
禁用get_format函数
-
禁止使用外键(外键在高并发场景下 性能较差,容易造成死锁,同时不利于维护)
-
创建序列必须指定cache大小
-
检查DDL是否创建冗余的索引
-
表必须有主键
-
禁止使用生成列作为分区键
-
数据库对象禁止使用保留字
-
数据库对象命名只能使用英文,下划线或数字,首字母必须是英文
-
表明/列名/索引名的长度不能大于指定字节
对象命名64 标识符64字节
-
多表关联必须有关联条件
-
禁止使用insert ignore语句进行插入
应该使用 replace into , insert into
-
检查大表执行计划是否走索引
-
禁止将blob类型的列加入索引
-
单个索引字段值的总长度不超过64kb
-
不允许创建全文索引
-
自增列字段使用bigint,禁止使用int类型,防止存储溢出,禁止使用自增列作为分区键
-
数据库名校验: 应用标识(4位)+子应用明(最多4位可选)+db 例如 cdssdb
-
禁止使用触发器(触发器难以开发和维护,不能高效移植,且在复杂的逻辑以及高并发下,容易出现死锁影响业务)
-
禁止使用存储过程(存储过程一定程度上会使程序难以调试或拓展,各种数据库的存储过程语法相差很大,移植带来很大困难,并且会极大增加bug概率)
-
小数类型使用decimal类型存储,禁止使用double,float
-
检查SQL语句是否有效
-
不建议使用select *
-
建表语句必须创建时间字段,默认为 current_timestamp
-
检查DDl操作的表是否超过指定数据量
-
列建议添加注释
-
主键中的列过多
-
请为group by 显示添加order by条件
-
应避免在where条件中使用函数或者其他运算符
-
不建议对常量进行group by
-
不建议order by的条件为表达式
-
不建议使用没有通配符的like查询
-
不建议使用order by rand()
-
禁止使用没有where条件的sql语句或者使用where 1=1等变相没有条件的sql
-
条件字段存咋技术局和字符的隐式转换(隐式转换导致无法命中索引风险,在高并发时候,不走索引,性能严重下降)
-
union查询时候选择列字段类型是否一致
-
修改标的默认字符集不会改标各个字段的字符集
-
发现常见SQL注入函数