开发规范是所有程序员开发过程中必须掌握的技能,早期的软件开发过程可能不重视开发规范导致后期维护成本极高,现在国内的大厂都会制定自己的开发规范,完善的开发规范不仅可以提高团队效率,还可以避免很多意外的bug问题。下面我找了几篇关于代码规范重要性的文章,大家可以参考下。
本系列文章将整合 阿里巴巴《Java开发手册》 和 谷歌《Java编程规范》 ,总结Java开发过程的编码规范,并通过具体编码案例给出编码规范的原因,如果总结内容存在问题还望指出。
Java开发规范之OOP规约篇共上中下三篇,具体内容参考 阿里巴巴《Java开发手册》的目录,同时补充 谷歌《Java编程规范》的内容,阿里巴巴规约内容比较丰富,谷歌规约很多实际内容都没有。
Java开发规范之OOP规约篇(上)
3.Java可变参数必须是相同参数和业务含义且避免使用Object类型
OOP规约指的是面向对象编程规范,英文全称是 Object Oriented Programming。面向对象程序设计是Java开发语言的一种计算机编程架构,它的基本原则是计算机程序是由单个能够起到子程序作用的单元或对象组合而成。
OOP 达到了软件工程的三个主要目标:重用性、灵活性和扩展性。为了实现整体运算,每个对象都能够接收信息、处理数据和向其它对象发送信息。相对而言,开发中还有一种面向过程编程(POP,Procedure Oriented Programming)的思想,它是一种以过程为中心的编程思想。该思想的开发过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
作为一种使用类和对象来设计程序的方法或模式,OOP提供一些概念简化了软件开发和维护:
- 对象,任何具有状态和行为的实体都称为对象,例如:老师,学生,学校等。
- 类,对象的集合称为类,它是一个逻辑实体。
- 继承,当一个对象获取父对象的所有属性和行为时,称为继承。它提供代码可重用性,它用于实现运行时多态性。
- 多态性,当一个任务通过不同的方式执行时,称为多态性,例如说话,人说人话,猫说话可以是:“喵喵”,而狗说话可能是“旺旺”等,说话时表示和声音也不太一样。
- 抽象,隐藏内部细节和显示功能称为抽象。例如:电话,但我们不知道内部是如何处理通话/通信的。
- 封装,将代码和数据绑定(或包装)在一起成为单个单元称为封装。例如:胶囊,它包裹着不同的药物。
- 组合,组合是聚合的特例。组合是一种更具限制性的聚合形式。例如,房子里有房间。没有房子,这里的房间不可能存在。
本篇将针对上述概念对OOP规约问题进行总结,由于篇幅有限将分为上中下三篇。
1.直接使用类名访问静态变量或静态方法
Alibaba规约(强制)
避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。
Google规约
使用类名调用静态的类成员,而不是具体某个对象或表达式。
说明:静态成员的初始化和类的初始化一起完成,初始化后被放入方法区中,而且只会被初始化一次,不会因为创建几次对象而被多次初始化,因此为节省内存空间建议直接通过类名调用,第一次通过类名调用静态成员将触发类的初始化,具体过程可以参考类的加载过程。
区别:两个大厂表达的含义是一样的。
正例:
// 获取静态变量的日期格式
SimpleDateFormat simpleDateFormat = DateFromatList.DATE_YYYY_MM_DD_HH_MM_SS;
// 访问类 Person 的静态方法获取 count 值
Integer count = Person.getCount();
反例:
DateFromatList list = new DateFromatList();
SimpleDateFormat dateFormat = list.DATE_YYYY_MM_DD_HH_MM_SS;
Person person = new Person();
Integer number = person.getCount();
2.覆写方法必须加@Override注解
Alibaba规约(强制)
所有的覆写方法,必须加 @Override 注解。
Google规约
只要是合法的,就把 @Override 注解给用上。
说明:子类重载父类的方法一般需要在方法前加上 @Override 注解,但是如果不添加编译器也能识别且能正常运行,但是实际调用具体使用哪个方法可能存在问题。另一方面,getObject()与 get0bject()的问题。一个是字母的 O,一个是数字的 0,加@Override 可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。
区别:无区别。
正例:
反例:
3.Java可变参数必须是相同参数和业务含义且避免使用Object类型
Alibaba规约(强制)
相同参数类型,相同业务含义,才可以使用Java的可变参数,避免使用Object。可变参数必须放置在参数列表的最后。(提倡同学们尽量不用可变参数编程)
Google规约
未明确定义类似规范
说明:Java 可变参数是1.5版本的新特性,也就是说用户若是想定义一个方法,但是在此之前并不知道以后要用的时候想传几百个参数进去,可以在方法的参数列表中写参数类型或度者数组名,然后在方法内部直接用操作数组的方式操作,具体案例可参考如下。
public class Demo {
public static void main(String[] args) {
System.out.println(getSum(1, 2, 3));
}
// 可变参数方法,array 即可变参数,调用时用逗号分隔可输入多个参数
public static int getSum(int... array) {
int sum = 0;
for (int i : array) {
sum += i;
}
return sum;
}
}
正例:
// ids可变参数为多个相同业务逻辑的 id 参数
public List<User> listUsers(String type, Long... ids) {
// 省略部分代码
}
反例:
// 避免使用 Object 类型的可变参数
public List<User> listStudents(String type, Object... objects) {
// 省略部分代码
}
4.禁止修改调用或者二方库依赖的接口方法签名
Alibaba规约(强制)
外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加@Deprecated注解,并清晰地说明采用的新接口或者新服务是什么。
Google规约
未明确定义类似规范
说明:规约中提到修改调用接口签名可能导致调用方法出现问题,因此最好不要擅自修改。@Deprecated 注解添加后表示此方法或类不再建议使用,调用时也会出现删除线,但并不代表不能用,只是说不推荐使用,因为还有更好的方法可以调用。
5.禁止使用过时的类或方法
Alibaba规约(强制)
不能使用过时的类或方法。
Google规约
未明确定义类似规范
说明:过时方法就是调用接口出现删除线,开发过程应该避免此类问题。java.net.URLDecoder 中的方法decode(String encodeStr) 这个方法已经过时,应该使用双参数decode(String source, String encode)。接口提供方既然明确是过时接口,那么有义务同时提供新的接口;作为调用方来说,有义务去考证过时方法的新实现是什么。
反例:
补充:Spring的过时类,Spring5.0之前,拦截器配置都是直接继承WebMvcConfigurerAdapter的,从5.0之后,这个类已废弃,而5.0之后WebMvcConfigurer有了defalut方法,允许子类仅覆盖那些需要的方法,所以这个适配器不需要了,直接继承WebMvcConfigurer接口就可以。
6.使用常量或确定有值的对象来调用equals方法
Alibaba规约(强制)
Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。
Google规约
未明确定义类似规范
说明:新手经常犯的错误就是直接调用equals方法进行判断,为避免空指针异常必须执行该规则。推荐使用java.util.Objects#equals(JDK7引入的工具类)。
正例:
Person person = new Person();
if ("李四".equals(person.getName())){
// 省去部分代码
}
// 推荐使用 Objects.equals(Object a, Object b) 方法
String str1 = new String("123");
String str2 = new String("123");
boolean result = Objects.equals(str1, str2);
反例:
Person person = new Person();
if (person.getName().equals("李四")){
// 省去部分代码
}
7.整型包装类对象之间值的比较必须使用equals方法
Alibaba规约(强制)
所有整型包装类对象之间值的比较,全部使用equals方法比较。
Google规约
未明确定义类似规范
说明:对于Integer var = ? 在-128至127范围内的赋值,Integer对象是在 IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。
补充:Integer 和 Long 类型的自动装箱的结果如下:
Integer x = 127;
Integer y = 127;
Integer m = 99999;
Integer n = 99999;
System.out.println("x == y: " + (x == y)); // true
System.out.println("m == n: " + (m == n)); // false
System.out.println("x.equals(y): " + x.equals(y)); // true
System.out.println("m.equals(n): " + m.equals(n)); // true
Long i = -128l;
Long j = -128l;
Long z = 9999l;
Long c = 9999l;
System.out.println("i == j: " + (i == j)); // true
System.out.println("z == c: " + (z == c)); // false
System.out.println("i.equals(j): " + i.equals(j)); // true
System.out.println("z.equals(c): " + z.equals(c)); // true
8.浮点数之间的等值判断避免精度问题误判
Alibaba规约(强制)
浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用equals来判断。
Google规约
未明确定义类似规范
说明:浮点数采用“尾数+阶码”的编码方式,类似于科学计数法的“有效数字+指数”的表示方式。二进制无法精确表示大部分的十进制小数。
正例:
// 指定一个误差范围,两个浮点数的差值在此范围之内,则认为是相等的。
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");
}
// 使用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");
}
反例:
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
}