代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
反例:_name / $name / name_ / name$
严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
正例:ali / taobao / aliyun / youku (国际通用的名称,可视同英文)
反例:pingJia(评价) DaZhe (打折) DianYing(电影)
代码和注释中避免使用任何语言的种族歧视性词语。
反例:RIBENGUIZI / slave
类名使用UpperCamelCase风格,但以下情形除外:DO/BO/DTO/VO/AO/PO/UID等
正例:ForceCode / UserDO /
反例:forcecode / userdo
方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase风格。
反例:getAge / setAge / localValue
常量命名全部大写,单词间用下划线隔开,力求予以表达完整清楚,不要嫌名字长。
正例:MAX_STOCK_COUNT / CACHE_EXPRIES_TIME
抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类
命名以它要测试的类的名称开始,以 Test 结尾
类型与中括号紧挨相连来表示数组。
正例:int[] arrayDemo
POJO 类中的任何布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列
化错误。
说明:在本文MySQl规约中的建表约定第一条,表达是与否的变量采用is_xxx的命名方 式,所以需要在
反例:Boolean isDeleted这个属性,它的方法也是isDeleted(),框架在反向解析的时候,误以为对应属性名称是deleted,导致属性获取不到,进而抛出异常。
包名统一使用小写,点分隔符之间有且仅有个自然语义的英语单词。包名统一使用
单数形式,但是类名如果有复数含义,类名可以使用复数形式
正例:com.qcby.user
避免在子父类的成员变量之间、或者不同代码块的局部变量之间采用完全相同的命名,
使可理解性降低
说明:子类、父类之间变量名相同,即使是public类型的变量也能通过编译,另外,局部变量在同一方法内的不同代码块中同名也是合法的,这些情况我们都要避免,对于非setter/getter的参数名称也要避免与成员变量名称相同。
杜绝完全不规范的缩写,避免望文不知义。
对于service和DAO类,基于SOA的理念,暴露出来的服务一定是接口,内部的实现类用lmpl的后缀与接口区别。
正例:cacheServicelmpl实现CacheService接口
各层命名规约:
Service/DAO层方法命名规约
获取单个对象的方法用get作为前缀。
获取多个对象的方法用list作为前缀,复数结尾,如:listObjects。
获取统计值的方法要用count作为前缀。
插入的方法用save/insert作为前缀。
删除的方法用remove/delete做前缀。
修改的方法用update前缀
不允许使用魔法值(未经预先定义的常量)直接出现在代码当中
if (age < 18) { console.log(“未成年”); }
//此时18就是魔法值,将其变为下面形式,
const MINOR_AGE = 18;
if (age < MINOR_AGE) { console.log(“未成年”); }
//魔法值会降低代码的可读性与可维护性,因为它们含义不清晰,可能会导致错误或者混淆。
//所以要避免魔法值,使用常量代替魔法值
在 long 或者 Long 赋值时,数值后使用大写字母 L,不能是小写字母 l,小写容易跟
数字混淆,造成误解。
正例:Long a = 2L
反例:Long a = 2l
如果是大括号内为空,则简洁地写成{}即可,大括号中间无需换行和空格;如果是非
空代码块则:
左大括号前不换行。
左大括号后换行。
右大括号前换行
右大括号后还有 else 等代码则不换行;表示终止的右大括号后必需换行。
左小括号和右边相邻字符之间不出现空格;右小括号和左边相邻字符之间也不出现空
格;而左大括号前需要加空格。
反例:if (空格 a == b 空格)
if/for/while/switch/do 等保留字与括号之间都必需加空格。
任何二目、三目运算符的左右两边都需要加一个空格。
说明:包括赋值运算符号=、逻辑运算符&&、加减乘除符号等
采用 4 个空格缩进,禁止使用 Tab 字符。
说明:如果使用 Tab 缩进,必需设置 1 个 Tab 为 4 个空格。IDEA 设置 Tab 为 4 个空格时,请勿勾选 Use tab character;而在 Eclipse 中,必需勾选 insert spaces for tabs。
public static void main(String[] args) {
// 缩进 4 个空格
String say = "hello";
// 运算符的左右必需有一个空格
int flag = 0;
// 关键词 if 与括号之间必需有一个空格,括号内的 f 与左括号,0 与右括号不要空格
if (flag == 0) {
System.out.println(say);
}
// 左大括号前加空格且不换行;左大括号后换行
if (flag == 1) {
System.out.println("world");
// 右大括号前换行,右大括号后有 else,不用换行
} else {
System.out.println("ok");
// 在右大括号后直接结束,则必须换行
}
}
注释的双斜线与注释内容之间有且仅有一个空格。
正例:// 注意这是示例注释,双斜线与注释内容之间有一个空格
在进行类型强制转换时,右括号与强制转换值之间不需要任何空格隔开
正例:double first = 3.2d;
int second = (int)first + 2;
单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则:
第二行相对第一行缩进 4 个空格,从第三行开始,不再继续缩进,参考示例。
运算符与下文一起换行。
方法调用的点符号与下文一起换行。
方法调用中的多个参数需要换行时,在逗号后进行。
在括号前不要换行,见反例
正例:
StringBuilder sb = new StringBuilder();
// 超过 120 个字符的情况下,换行缩进 4 个空格,并且方法前的点号一起换行
sb.append("yang").append("hao")...
.append("chen")...
.append("chen")...
.append("chen");
反例:
StringBuilder sb = new StringBuilder();
// 超过 120 个字符的情况下,不要在括号前换行
sb.append("you").append("are")...append
("lucky");
// 参数很多的方法调用可能超过 120 个字符,逗号后才是换行处
method(args1, args2, args3, ...
, argsX);
方法参数在定义和传入时,多个参数逗号后面必需加空格。
正例:method(args1, args2, args3);
IDE 的 text file encoding 设置为 UTF-8; IDE 中文件的换行符使用 Unix 格式,不要
使用 Windows 格式.
避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。
所有的覆写方法,必须加@Override注解。
说明:getObject()与get0bject()的问题。一个是字母的O,一个是数字的0,加@Override 可以准确判断是否覆盖成功。另外,如果在抽象类中对方法名进行修改,其实现类会马上编译报错。(方法签名是指方法的名称和一个参数列表(参数的个数、类型和顺序))
相同参数类型,相同业务含义,才可以使用Java的可变参数,避免使用Object.。
说明∶可变参数必须放置在参数列表的最后。(建议开发者尽量不用可变参数编程)
正例: public List
外部正在调用或者二方库(依赖于公司内部的其他项目发布的jar包)依赖的接口,不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加@Deprecated注解,并清晰地说明采用的新接口或者新服务是什么。
不能使用过时的类或方法。
说明: java.net.URLDecoder 中的方法decode(String encodeStr)这个方法已经过时,应该使用双参数decode(String source, String encode)。接口提供方既然明确是过时接口,那么有义务同时提供新的接口;作为调用方来说,有义务去考证过时方法的新实现是什么。
Object的equals方法容易抛空指针异常应使用常量或确定有值的对象来调用equals。
正例: "test".equals(object);
反例:Object.equals("test");
说明︰推荐使用JDK7引入的工具类java.util.Objectst#equals(Object a, Object b)
所有整型包装类对象之间值的比较,全部使用equals方法比较。
说明:对于Integer var = ?在-128至127之间的赋值,Integer对象是在 IntegerCache.cache产生,会复用已有对象,这个区间内的 Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。
任何货币金额,均以最小货币单位且整型类型来进行存储。
浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用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)的结果为false
}
//正例
(1)指定一个范围误差,两个浮点数的差值在此范围内,则认为相等
float a = 1.0F - 0.9F;
float b = 0.9F - 0.8F;
float diff = 1e-6F;
if(Math.abs(a-b)
如上所示BigDecimal的等值比较应使用compareTo()方法,而不是equals()方法。
说明:equals()方法会比较值和精度(1.0与1.00返回结果为false ),而compareTo()则会忽略精度。
定义数据对象DO类时,属性类型要与数据库字段类型相匹配。
正例︰数据库字段的 bigint必须与类属性的Long类型相对应。
反例︰某个案例的数据库表id字段定义类型 bigint unsigned,实际类对象属性为Integer,随着id越来越大,超过Integer的表示范围而溢出成为负数。
禁止使用构造方法BigDecimal(double)的方式把double值转化为BigDecimal对象。
说明∶BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。如:BigDecimal g = new BigDecimal(0.1F);实际的存储值为∶0.10000000149
正例:优先推荐入参为String 的构造方法,或使用BigDecimal的valueOf方法,此方法内部其实执行了Double的toString ,而 Double的toString 按double的实际能表达的精度对尾数进行了截断。
BigDecimal recommend1 = new BigDecimal("0.1");
BigDecimal recommend2 = BigDecimal.valueOf(0.1);
关于基本数据类型与包装数据类型的使用标准如下:
所有的POJO类属性必须使用包装数据类型。
RPC方法的返回值和参数必须使用包装数据类型。
所有的局部变量使用基本数据类型。
正例:数据库查询结果可能是null,因为自动拆箱,用基本数据类型接受有NPE风险
反例:某业务的交易报表上显示成交总额跌涨情况,即正负x%,x为基本数据类型,调用RPC服务,调用不成功时,返回的是默认值,页面显示为0%,这显然是不合理的,应该显示成中划线-。所以包装数据类型的null值,能够表示额外的信息,如远程调用失败,异常退出。
定义DO/DTO/VO等POJO类时,不要设定任何属性默认值。
反例:POJO类中的createTime默认值是new Date(),但当这个属性在数据提取时并没有置入具体值,在更新其它字段时又附带更新了此字段,导致创建时间被修改成当前时间
DO、DTO、VO等都是POJO类。POJO(Plain Ordinary Java Object)是指只有setter/getter/toString的简单类,包括DO/DTO/BO/VO等。
DO(Data Object):此对象与数据库表结构一一对应,通过DAO层向上传输数据源对象。
DTO(Data Transfer Object):前端给后端传递的数据。
VO(View Object):后端给前端传递的数据。
构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在init方法当中。
POJO类必须写toString方法。使用IDE中的工具:source>generate toString时,如果继承了另一个POJO类,注意在前面加一个super.toString。
说明:在方法抛出异常时,可以直接调用POJO的toString()方法打印其属性值,便于排查问题
禁止在POJO类中,同时存在对应属性xxx的isXxx()和getXxx()方法。
说明:框架在调用属性xxx的提取方法时,并不能确定到哪个方法一定是优先被调用到的。
日期格式化时,传入pattern中表示年份统一使用小写的y。
说明∶日期格式化时,yyyy表示当天所在的年,而大写的YYYY代表是week in which year ( JDK7之后引入的概念),意思是当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,返回的YYYY就是下一年。
正例:表示日期和时间的格式如下所示︰
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
在日期格式中分清楚大写的M和小写m,大写的H和小写的h分别指代的意义。
说明:日期格式中的这两对字母表意如下:
表示月份的是大写的M;
表示分钟是小写的m;
24小时制的大写的H;
12小时制的则是小写的h;
获取当前毫秒数:System.currentTimeMIllis();而不是new Date().getTime()。
说明:如果想获取更加精确的纳秒级的时间值,使用System.nanoTime的方式。在JDK8中,针对统计时间等场景,推荐使用Instant类。
不允许在程序任何地方中使用
java.sql.Date
java.sql.Time
java.sql.Timestamp
说明:第1个不记录时间,getHours()抛出异常;第2个不记录日期,getYear()抛出异常;第3个在构造方法super((time/1000)*1000),在Timestamp属性fastTime和nanos分别储存和纳秒信息。
反例:java.util.Date.after(Date)进行时间比较时,当入参是java.sql.Timestamp时,会触发JDKBUG(JDK已修复),可能导致比较时的意外结果。
不要在程序当中写死一年为365天,避免在公历闰年时出现日期转换错误或程序逻辑错误。
正例
//获取今年的天数
int daysOfThisYear =LocalData.now().lengthOfYear();
//获取指定某年的天数
LocalDate.of(2011,1,1).lengthOfYear();
反例
//第一种情况:在闰年366天时,出现数组越界异常
int [] dayArray =new int [365]
//第二种情况:一年有效期的会员制,今年1月26日注册,硬编码365返回的却是1月25
Calendar calendar =Calendar.getlnstance();
calendar.set(2020,1,26);
calendar.add(Calendar.DATE,365);
关于hashCode和equals 的处理,遵循如下规则∶
只要覆写equals,就必须覆写hashCode。
因为Set存储的是不重复的对象,依据 hashCode和equals进行判断,所以Set存储的对象必须覆写这两种方法。
如果自定义对象作为Map 的键,那么必须覆写hashCode和equals。
说明:String因为覆写了hashCode和equals方法,所以可以愉快地将String对象作为key来使用。
判断所有集合内部的元素是否为空,使用isEmpty()方法,而不是 size()==0的方式。
说明︰在某些集合中,前者的时间复杂度为O(1),而且可读性更好。
正例:
Map map = new HashMap<>(16);
if(map.isEmpty()){
System.out.println("no element in this map.");
}
在使用java.util.stream.Collectors类的 toMap()方法转为Map集合时,一定要使用含有参数类型为BinaryOperator ,参数名为mergeFunction的方法,否则当出现相同key值时会抛lllegalStateException 异常。
说明∶参数 mergeFunction的作用是当出现key重复时,自定义对value的处理策略。
正例:
(七)并发处理
(八)控制语句
(九)注释规约
(十)前后端规约
(十一)其他