[编程规范篇]《阿⾥巴巴开发⼿册》泰⼭版解读
本⽂章主要记录⼀些⾃⼰⽇常容易犯错或者推荐遵守的编程习惯。
⽂章⽬录
⼀、编程规约
1. 命名风格
1.【强制】 包名统⼀使⽤⼩写,点分隔符之间有且仅有⼀个⾃然语义的英语单词。包名统⼀使⽤单数形式,但是类名如果有复数含义,类名可以使⽤复数形式。
正例:应⽤⼯具类包名为com.alibaba.ei.kunlun.aap.util、类名为MessageUtils(此规则参考spring的框架结构)
2.(推荐) 在常量与变量的命名时,表⽰类型的名词放在词尾,以提升辨识度。
正例:startTime / workQueue / nameList / TERMINATED_THREAD_COUNT
反例:startedAt / QueueOfWork / listName / COUNT_TERMINATED_THREAD
3.接⼝和实现类的命名有两套规则:
1)【强制】 对于Service和DAO类,基于SOA的理念,暴露出来的服务⼀定是接⼝,内部的实现类⽤Impl的后缀与接⼝区别。
正例:CacheServiceImpl实现CacheService接⼝。
2)(推荐) 如果是形容能⼒的接⼝名称,取对应的形容词为接⼝名(通常是–able的形容词)。 正例:AbstractTranslator实现Translatable接⼝。
4.{参考} 各层命名规约:
A) Service/DAO层⽅法命名规约
1) 获取单个对象的⽅法⽤get做前缀。
2) 获取多个对象的⽅法⽤list做前缀,复数结尾,如:listObjects。
3) 获取统计值的⽅法⽤count做前缀。
4) 插⼊的⽅法⽤save/insert做前缀。
5) 删除的⽅法⽤remove/delete做前缀。
6) 修改的⽅法⽤update做前缀。
B) 领域模型命名规约
1) 数据对象:xxxDO,xxx即为数据表名。
2) 数据传输对象:xxxDTO,xxx为业务领域相关的名称。
3) 展⽰对象:xxxVO,xxx⼀般为⽹页名称。
4) POJO是DO/DTO/BO/VO的统称,禁⽌命名成xxxPOJO。
2. 代码格式
1.【强制】 如果是⼤括号内为空,则简洁地写成 {} 即可,⼤括号中间⽆需换⾏和空格;如果是⾮空代码块则:
1) 左⼤括号前不换⾏。
2) 左⼤括号后换⾏。
3) 右⼤括号前换⾏。
4) 右⼤括号后还有else等代码则不换⾏;表⽰终⽌的右⼤括号后必须换⾏。
2.【强制】 在进⾏类型强制转换时,右括号与强制转换值之间不需要任何空格隔开。
正例:
long first =1000000000000L;
int second =(int)first +2;
3.【强制】 单⾏字符数限制不超过120个,超出需要换⾏,换⾏时遵循如下原则:
1)第⼆⾏相对第⼀⾏缩进4个空格,从第三⾏开始,不再继续缩进,参考⽰例。
2)运算符与下⽂⼀起换⾏。
3)⽅法调⽤的点符号与下⽂⼀起换⾏。
4)⽅法调⽤中的多个参数需要换⾏时,在逗号后进⾏。
5)在括号前不要换⾏,见反例。
正例:
StringBuilder sb =new StringBuilder();
// 超过120个字符的情况下,换⾏缩进4个空格,并且⽅法前的点号⼀起换⾏
sb.append("zi").append("xin")...
.append("huang")...
.append("huang")...
.append("huang");
反例:
StringBuilder sb =new StringBuilder();
// 超过120个字符的情况下,不要在括号前换⾏
sb.append("you").append("are")...append
("lucky");
// 参数很多的⽅法调⽤可能超过120个字符,逗号后才是换⾏处
method(args1, args2, args3,...
, argsX);
3. OOP规约
1.【强制】 外部正在调⽤或者⼆⽅库依赖的接⼝,不允许修改⽅法签名,避免对接⼝调⽤⽅产⽣影响。接⼝过时必须加@Deprecated注解,并清晰地说明采⽤的新接⼝或者新服务是什么。
2.【强制】 所有整型包装类对象之间值的⽐较,全部使⽤equals⽅法⽐较。
说明:对于Integer var = ? 在-128⾄127之间的赋值,Integer对象是在 IntegerCache.cache产⽣,会复⽤已有对象(享元设计模式),这个区间内的Integer值可以直接使⽤==进⾏判断,但是这个区间之外的所有数据,都会在堆上产⽣,并不会复⽤已有对象,这是⼀个⼤坑,推荐使⽤equals⽅法进⾏判断。
3.【强制】 任何货币⾦额,均以最⼩货币单位且整型类型来进⾏存储。
4.【强制】 浮点数之间的等值判断,基本数据类型 不能⽤== 来⽐较,包装数据类型不能⽤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)){
// 预期进⼊此代码快,执⾏其它业务逻辑
// 但事实上equals的结果为false
}
正例:
(1) 指定⼀个误差范围,两个浮点数的差值在此范围之内,则认为是相等的。
float a =1.0f-0.9f;
float b =0.9f-0.8f;
float diff =1e-6f;
if(Math.abs(a - b)< diff){
System.out.println("true");
}
(2) 使⽤ BigDecimal 来定义值,再进⾏浮点数的运算操作。
BigDecimal a =new BigDecimal("1.0");
BigDecimal b =new BigDecimal("0.9");
BigDecimal c =new BigDecimal("0.8");
BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);
if(x.equals(y)){
System.out.println("true");
}
5.【强制】 定义数据对象DO类时,属性类型要与数据库字段类型相匹配。
正例:数据库字段的 bigint 必须与类属性的 Long 类型相对应。
反例:某个案例的数据库表id字段定义类型bigint unsigned,实际类对象属性为Integer,随着id越来越⼤,超过Integer的表⽰范围⽽溢出成为负数。
6.【强制】 禁⽌使⽤构造⽅法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);
7.【强制】 构造⽅法⾥⾯禁⽌加⼊任何业务逻辑,如果有初始化逻辑,请放在init⽅法中。
8.【强制】 禁⽌在POJO类中,同时存在对应属性xxx的 isXxx() 和 getXxx() ⽅法。
说明:框架在调⽤属性xxx的提取⽅法时,并不能确定哪个⽅法⼀定是被优先调⽤到,神坑之⼀。
9.(推荐) 循环体内,字符串的连接⽅式,使⽤StringBuilder的 append() ⽅法进⾏扩展。
说明:下例中,反编译出的字节码⽂件显⽰每次循环都会new出⼀个StringBuilder对象,然后进⾏append操作,最后通过toString⽅法返回String对象,造成内存资源浪费。
反例:
String str ="start";
for(int i =0; i <100; i++){
str = str +"hello";
}
10.(推荐) 慎⽤Object的clone⽅法来拷贝对象。 说明:对象clone⽅法默认是 浅拷贝,若想实现 深拷贝 需覆写clone⽅法实现域对象的深度遍历式拷贝。
4. ⽇期时间
1.【强制】 不要在程序中写死⼀年为365天,避免在公历闰年时出现⽇期转换错误或程序逻辑错误。
正例:
// 获取今年的天数
int daysOfThisYear = w().lengthOfYear();
// 获取指定某年的天数
LocalDate.of(2011,1,1).lengthOfYear();
反例:
// 第⼀种情况:在闰年366天时,出现数组越界异常
int[] dayArray =new int[365];
// 第⼆种情况:⼀年有效期的会员制,今年1⽉26⽇注册,硬编码365返回的却是1⽉25⽇
Calendar calendar = Instance();
calendar.set(2020,1,26);
calendar.add(Calendar.DATE,365);
2.(推荐) 使⽤枚举值来指代⽉份。如果使⽤数字,注意Date,Calendar等⽇期相关类的⽉份month取值在0-11之间。
说明:参考JDK原⽣注释,Month value is 0-based. e.g., 0 for January.
正例:
Calendar.JANUARY,Calendar.FEBRUARY,Calendar.MARCH等来指代相应⽉份来进⾏传参或⽐较。
5. 集合处理
1.【强制】 关于hashCode和equals的处理,遵循如下规则:
1) 只要重写equals,就必须重写hashCode。
2) 因为Set存储的是不重复的对象,依据hashCode和equals进⾏判断,所以Set存储的对象必须重写这两个⽅法。
3) 如果⾃定义对象作为Map的键,那么必须覆写hashCode和equals。
说明:String因为重写了hashCode和equals⽅法,所以我们可以愉快地使⽤String对象作为key来使⽤。
2.【强制】 在使⽤java.util.stream.Collectors类的 toMap() ⽅法转为Map集合时,⼀定要使⽤含有参数类型为BinaryOperator,参数名为 mergeFunction 的⽅法,否则当出现相同key值时会抛出IllegalStateException异常。
说明:参数mergeFunction的作⽤是当出现key重复时,⾃定义对value的处理策略。
正例:
List<Pair<String, Double>> pairArrayList =new ArrayList<>(3);
pairArrayList.add(new Pair<>("version",6.19));
bigdecimal转换为integerpairArrayList.add(new Pair<>("version",10.24));
pairArrayList.add(new Pair<>("version",13.14));
Map<String, Double> map = pairArrayList.stream().collect(
// ⽣成的map集合中只有⼀个键值对:{version=13.14}
// 使⽤覆盖的策略解决key相同的问题
反例:
String[] departments =new String[]{"iERP","iERP","EIBU"};
// 抛出IllegalStateException异常
Map<Integer, String> map = Arrays.stream(departments)
.Map(String::hashCode, str -> str));
3.【强制】 在使⽤java.util.stream.Collectors类的toMap()⽅法转为Map集合时,⼀定要注意当value为null时会抛NPE异常。
说明:在java.util.HashMap的merge⽅法⾥会进⾏如下的判断:
if(value ==null|| remappingFunction ==null)
throw new NullPointerException();
反例:
List<Pair<String, Double>> pairArrayList =new ArrayList<>(2);
pairArrayList.add(new Pair<>("version1",4.22));
pairArrayList.add(new Pair<>("version2",null));
Map<String, Double> map = pairArrayList.stream().collect(
// 抛出NullPointerException异常
4.【强制】 使⽤集合转数组的⽅法,必须使⽤集合的 toArray(T[] array),传⼊的是类型完全⼀致、长度为0的空数组。
反例:直接使⽤toArray⽆参⽅法存在问题,此⽅法返回值只能是 Object[] 类,若强转其它类型数组将出现 ClassCastException 错误。正例:
List<String> list =new ArrayList<>(2);
list.add("guan");
list.add("bao");
String[] array = Array(new String[0]);
说明:使⽤toArray带参⽅法,数组空间⼤⼩的length,
1) 等于0,动态创建与size相同的数组,性能最好。
2) ⼤于0但⼩于size,重新创建⼤⼩等于size的数组,增加GC负担。
3) 等于size,在⾼并发情况下,数组创建完成之后,size正在变⼤的情况下,负⾯影响与2相同。
4) ⼤于size,空间浪费,且在size处插⼊null值,存在NPE隐患。
5.【强制】 在使⽤Collection接⼝任何实现类的 addAll() ⽅法时,都要对输⼊的集合参数进⾏NPE判断。
说明:在 ArrayList#addAll ⽅法的第⼀⾏代码即 Object[] a = c.toArray(); ,其中c为输⼊集合参数,如果为 null,则直接抛出异常。
6.【强制】 泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使⽤add⽅法,⽽<? super T>不能使⽤get⽅法,两者在接⼝调⽤赋值的场景中容易出错。
说明:扩展说⼀下PECS(Producer Extends Consumer Super)原则:
第⼀、频繁往外读取内容的,适合⽤<? extends T>。
第⼆、经常往⾥插⼊的,适合⽤<? super T>。
7.【强制】 不要在foreach循环⾥进⾏元素的 remove/add 操作。remove 元素请使⽤ Iterator ⽅式,如果并发操作,需要对 Iterator 对象加锁。
正例:
List<String> list =new ArrayList<>();
list.add("1");
list.add("2");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String item = ();
if(删除元素的条件){
}
}
反例:
for(String item : list){
if("1".equals(item)){
}
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论