个人日常编码规范(一)

代码优化:  

  • 奇偶判断
  • 不要在常量与变量中出现易混淆字符
  • 不要让常量变成了变量
  • 三元表达式的类型务必一致
  • 建议显式的声明UID
  • switch使用注意
  • 用整数类型处理货币
  • 注意边界问题
  • 基本类型转换时尽量避免隐式的类型转换
  • 警惕包装类型的NULL值
  • 警惕包装类型的比较
  • 建议工具类不可实例化
  • 警惕对象的潜拷贝
  • 建议覆写实体类的toString方法
  • 合理使用String、StringBuffer、StringBuilder
  • 注意字符串的位置
  • 明确场景下为集合指定初始容量
  • 使用Arrays.asList方法时要注意List对象不可改变
  • 注意子列表只是元列表的一个视图
  • 提倡异常封装
  • 建议采用异常链传递异常
  • 不要在finally中处理返回值
  • 不要在循环条件中计算
  • 推荐使用guava扩展工具包
  • 代码注释应该正确、清晰、简洁
  • 避免重复代码
  • 避免过长方法
  • 定义的变量名称,方法名称,类名称等应该具有一定的意义
  • 避免过大的类
  • 避免过长的参数列表

设计优化:

  • 接口职责要保持单一
  • 增强类的可替换性
  • 模块之间的依赖通过抽象发生
  • 一个类对另一个类的依赖应该建立在最小的接口上
  • 尽量降低类与类之间的耦合

 

一、代码优化:  

(1)奇偶判断

说明:

不要使用 i % 2 == 1 来判断是否是奇数,因为i为负奇数时不成立,请使用 i % 2 != 0 来判断是否是奇数,或使用高效式 (i & 1) != 0来判断。

(2)不要在常量与变量中出现易混淆字符

示例:

long i= 1l; // 不推荐

long i= 1L; // 推荐

说明:

如果字母和数字必须混合使用,字母”l”务必大写,字母”o”则增加注释

(3)不要让常量变成了变量

实例:

public static final int RAND_CONST = new Random().nextInt(); // 不推荐

说明:

务必让常量的值在运行期保持不变,不要使用常量会变的这个功能来实现序列号算法、随机种子生成

(4)三元表达式的类型务必一致

示例:

public static void test() {

    int i = 80;

    String str = String.valueOf(i < 100 90 100); // 推荐

    String str1 = String.valueOf(i < 100 90 100.0); // 不推荐

    System.out.println("两者是否相等:" + str.equals(str1));

}

说明:

1.若两个操作数不可转换,则不作转换,返回值是Object类型;

2.若两个操作数是明确类型的表达式(比如变量),则按照正常的二进制数字转换,int转为long,long转为float等;

3.若两个操作数中有一个是数字S,另外一个是表达式,且其类型标志位T,那么,若数字S在T的范围内,则转换为T类型;若S超出了T的范围,则T转换为S;

4.若两个操作数都是直接量数字,则返回值类型范围较大者。

(5)建议显式的声明UID

示例:

import java.io.Serializable;

public class Person implements Serializable {

  

    private static final long serialVersionUID = 1L;

 

    private String name;

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

}

说明:

1.SerializableUID,也叫做流标识符(Stream Unique Identifier),即类的版本定义的,它可以显示声明也可以隐式声明,显式声明如上例对于serialVersionUID 的定义,隐式声明则是不声明,编译器在遍译的时候帮我生成。生成的依据是通过包名、类名、继承关系、非私有的方法和属性,以及参数、返回值等诸多因子算出来的,极度复杂,基本上计算出来的这个值是唯一的。

2.JVM在反序列化时,会比较数据流中的serialVersionUID与类的serialVersionUID是否相同,如果相同,则认为类没有改变;如果不相同,抛出异常InviladClassException。

3.显式声明UID可以在序列化对象变化的时候我们反序列化实现版本兼容

(6)switch使用注意

示例:

switch (date) {

    case YEAR:

        // do something

        break;

    case MONTH:

        // do something

        break;

    default:

        // do something

}

说明:

1.switch语句注意空指针异常

2.default不能少

3.case语句上记得加上break

 

(7)用整数类型处理货币

示例:

public static BigDecimal divide(Long amount, Long divisor) {

   return new BigDecimal(amount).divide(new BigDecimal(divisor), 10, RoundingMode.HALF_DOWN);

}

说明:

1.BigDecimal是专门为弥补浮点数无法精确计算的缺憾而设计的类,并且它本身也提供了加减乘除的常用数学算法

2.把参与运算的值扩大100倍,并转为整型,然后在展现时再缩小100倍,这样处理的好处是计算简单,准确

(8)注意边界问题

示例:

public class Test {

    // 光速是30万公里/秒,常量

    public static final int LIGHT_SPEED = 30 10000 1000;

    public static void main(String[] args) {

        System.out.println("题目:太阳光照射到地球需要8分钟,计算太阳到地球的距离.");

        // int dis2 = LIGHT_SPEED * 60 * 8; // 溢出

        long dis2 = 1L * LIGHT_SPEED * 60 8// 推荐

        System.out.println("太阳与地球之间的距离是:" + dis2 + " 米");

    }

}

说明:

(9)基本类型转换时尽量避免隐式的类型转换

示例:

public class Test {

    // 光速是30万公里/秒,常量

    public static final int LIGHT_SPEED = 30 10000 1000;

    public static void main(String[] args) {

        System.out.println("题目:太阳光照射到地球需要8分钟,计算太阳到地球的距离.");

        // 可能要超出整数范围,使用long型

        long dis2 = LIGHT_SPEED * 60 8// 不推荐,未考虑类型转换

        long dis3 = 1L * LIGHT_SPEED * 60 8// 推荐

        System.out.println("太阳与地球之间的距离是:" + dis2 + " 米");

        System.out.println("太阳与地球之间的距离是:" + dis3 + " 米");

    }

}

说明:

因为Java是先运算然后进行类型转换的,涉及到基本类型转换的时候使用主动的方式减少不必要的BUG

(10)警惕包装类型的NULL值

示例:

public static int testMethod(List<Integer> list) {

    int count = 0;

    for (Integer i : list) {

        count += (i != null) ? i : 0// 推荐

        // count += i; // 不推荐

    }

    return count;

}

说明:

基本类型和包装类型都是可以通过自动装箱(Autoboxing)和自动拆箱(AutoUnboxing)自由转换,但是NULL值并不能转换为基本类型,所以包装类型参与运算时,要做NULL值校验

(11)警惕包装类型的比较

示例:

public static void main(String[] args) {

    System.out.println("测试包装类型相等------>");

    Integer i1 = new Integer(100);

    Integer j1 = new Integer(100);

    isEqual(i1, j1);

   

    System.out.println("测试包装类型相等------>");

    Integer i11 = new Integer(100);

    Integer j11 = 100;

    isEqual(i11, j11); 

   

    System.out.println("测试包装类型相等------>");

    Integer i12 = 100;

    Integer j12 = 100;

    isEqual(i12, j12);

   

    System.out.println("测试包装类型相等------>");

    Integer i13 = new Integer(100);

    int j13 = 100;

    isEqual(i13, j13); 

   

    System.out.println("测试包装类型相等------>");

    Integer i14 = 100;

    int j14 = 100;

    isEqual(i14, j14);

}

public static void isEqual(Integer i, Integer j) {

    System.out.println(i == j); // 不推荐

    System.out.println(i.equals(j)); // 推荐

    System.out.println(i.compareTo(j)==0); // 推荐

}

说明:

1.在java中"=="是用来判断两个操作数是否有相等关系的,如果是基本类型则判断值是否相等,如果是对象则判断是否是一个对象的两个引用,也就是地址是否相等

2.在Java中,">" 和 "<" 用来判断两个数字类型的大小关系,注意只能是数字类型的判断,对于Integer包装类型,是根据其intValue()方法的返回值(也就是其相应的基本类型)进行比较的

(12)建议工具类不可实例化

示例:

class UtilsClazz{

    private UtilsClazz(){

        throw new Error("Don't instantiate "+getClass());

    }

}

说明:

保证了所有的访问都是通过类名来进行的,并且通过反射也不能修改构造函数

(13)警惕对象的潜拷贝

示例:

public class Person implements Cloneable {

    public static void main(String[] args) {

        // 定义父亲

        Person f = new Person("父亲");

        // 定义大儿子

        Person s1 = new Person("大儿子", f);

        // 小儿子的信息时通过大儿子拷贝过来的

        Person s2 = s1.clone();

        s2.setName("小儿子");

        // 大儿子认干爹

        s1.getFather().setName("干爹");

        System.out.println(s1.getName() + " 的父亲是 " + s1.getFather().getName());

        System.out.println(s2.getName() + " 的父亲是 " + s2.getFather().getName());

    }

     

    // 姓名

    private String name;

    // 父亲

    private Person father;

    public Person(String _name) {

        name = _name;

    }

    public Person(String _name, Person _parent) {

        name = _name;

        father = _parent;

    }

    @Override

    public Person clone() {

        Person p = null;

        try {

            p = (Person) super.clone(); // 潜拷贝

            if (null != p.getFather()) {

                p.setFather((Person)(p.getFather().clone())); // 将引用对象也clone一下,间接弥补了潜拷贝的缺陷

            }

        catch (CloneNotSupportedException e) {

            e.printStackTrace();

        }

        return p;

    }

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

    public Person getFather() {

        return father;

    }

    public void setFather(Person father) {

        this.father = father;

    }

}

说明:

1.基本类型:如果变量是基本类型,则拷贝其值。比如int、float等。

2.对象:如果变量是一个实例对象,则拷贝其地址引用,也就是说此时拷贝出的对象与原有对象共享该实例变量,不受访问权限的控制。

3.String字符串:这个比较特殊,拷贝的也是一个地址,是个引用,但是在修改时,它会从字符串池(String pool)中重新生成新的字符串,原有的字符串对象保持不变,在此处我们可以认为String是一个基本类型。

(14)建议覆写实体类的toString方法

示例:

/**

 * 使用apache包实现toString方法

 */

public class BaseModel implements Serializable {

    private static final long serialVersionUID = 1L;

    @Override

    public String toString() {

        return org.apache.commons.lang.builder.ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE);

    }

}

  

/**

 * 使用反射实现toString方法

 */

public class BaseDto implements Serializable {

 

    private static final long serialVersionUID = 1L;

 

    @Override

    public String toString() {

        Field[] field = this.getClass().getDeclaredFields();

        if (null != field && field.length > 0) {

            List<Field> fields = Arrays.asList(field);

            if (null != fields && fields.size() > 0) {

                String result = fields.stream()

                        .filter(f -> null != f && !"serialVersionUID".equalsIgnoreCase(f.getName()))

                        .map(f -> this.getFiledVal(f, this)).collect(Collectors.joining(","));

                if (null != result && result.length() > 0) {

                    result =

                            Optional.ofNullable(this.getClass().getSimpleName()).orElse("") + " [" + result + "]";

                    return result;

                }

            }

        }

        return super.toString();

    }

 

    private String getFiledVal(Field field, Object obj) {

        String key = "";

        String value = "";

        try {

            if (null != field) {

                field.setAccessible(true);

                key = Optional.ofNullable(field.getName()).orElse("");

                Object valObj = field.get(obj);

                if (null != valObj) {

                    value = Optional.ofNullable(valObj.toString()).orElse("");

                }

            }

        catch (Exception e) {

            e.printStackTrace();

        }

        return key + "=" + value;

    }

}

说明:

可以在需要的时候输出调试信息

(15)合理使用String、StringBuffer、StringBuilder

说明:

1.String:字符串不经常变化的场景,例如常量的声明、少量变量的计算。

2.StringBuffer:频繁的字符串运算,并且运行在多线程环境中,例如HTTP参数解析和封装。

3.StringBuilder:频繁的字符串运算,并且运行在单线程环境中,例如JSON封装。

(16)注意字符串的位置

示例:

public class Test{

    public static void main(String[] args) {

        String str1 = 1 2 "apples";

        String str2 = "apples" 1 2;

        System.out.println(str1); // 输出3apples

        System.out.println(str2); // 输出apples12

    }

}

说明:

 在“+” 表达式中,String字符串具有最高优先级。

(17)明确场景下为集合指定初始容量

示例:

public static void main(String[] args) {

        StudentVO student = null;

        long begin1 = System.currentTimeMillis();

        List<StudentVO> list1 = new ArrayList<>();

        for(int i = 0 ; i < 1000000; i++){

            student = new StudentVO(i,"chenssy_"+i,i);

            list1.add(student);

        }

        long end1 = System.currentTimeMillis();

        System.out.println("list1 time:" + (end1 - begin1));

         

        long begin2 = System.currentTimeMillis();

        List<StudentVO> list2 = new ArrayList<>(1000000);

        for(int i = 0 ; i < 1000000; i++){

            student = new StudentVO(i,"chenssy_"+i,i);

            list2.add(student);

        }

        long end2 = System.currentTimeMillis();

        System.out.println("list2 time:" + (end2 - begin2));

}

说明:

避免集合扩容造成的额外性能开销

(18)使用Arrays.asList方法时要注意List对象不可改变

示例:

public class MainFacade {

    public static void main(String[] args) {

        List<Integer> list = Arrays.asList(1,2,3);

        list.add(5); // 抛出UnsupportedOperationException异常

        System.out.print(list.toString());

    }

}

说明:

Arrays.asList得到的ArrayList是一个继承自AbstractList同时实现了RandomAccess和Serializable接口的静态私有内部类,它的add、set、remove方法都是父类AbstractList提供,AbstractList中并没相关的具体实现而是直接抛出UnsupportedOperationException异常。

注意子列表只是元列表的一个视图

示例:

public static void main(String[] args) {

    // 定义一个包含两个字符串的列表

    List<String> c = new ArrayList<String>();

    c.add("A");

    c.add("B");

    // 构造一个包含c列表的字符串列表

    List<String> c1 = new ArrayList<String>(c);

    // subList生成与c相同的列表

    List<String> c2 = c.subList(0, c.size());

    // c2增加一个元素

    c2.add("C");

    System.out.println("c==c1? " + c.equals(c1));

    System.out.println("c==c2? " + c.equals(c2));

}

说明:

subList产生的列表只是一个视图,所有的修改动作直接作用于原列表。

 

(19)提倡异常封装

示例:

/**

 * 运行时异常基类

 */

public class BaseRuntimeException extends RuntimeException {

 

    private static final long serialVersionUID = 1L;

 

    private int code;

 

    public int getCode() {

        return code;

    }

 

    public BaseRuntimeException(int code, String message) {

        super(message);

        this.code = code;

    }

 

    public BaseRuntimeException(String message) {

        super(message);

        this.code = SuperErrorCodeConstants.UNKNOW_ERROR_CODE;

    }

 

    public BaseRuntimeException(String message, Throwable cause) {

        super(message, cause);

        this.code = SuperErrorCodeConstants.UNKNOW_ERROR_CODE;

    }

}

说明:

1.提高系统友好性

2.提高系统可维护性

(20)建议采用异常链传递异常

示例:

try{

    //doSomething

}catch(Exception e){

    throw new BusinessException(e);

}

说明:

异常需要封装和传递,底层不要吞噬异常(特别是不能根据异常做业务处理),在上层通过切面统一处理异常

(21)不要在finally中处理返回值

示例:

/**

 * 不会修改返回值,返回1

 */

public static int doStuff() {

    int a = 1;

    try {

        return a;

    catch (Exception e) {

        throw new RuntimeException(e);

    finally {

        // 重新修改一下返回值

        a = -1;

    }

    return 0;

}

  

/**

 * 异常被屏蔽

 */

public static void doSomeThing(){

    try{

        //正常抛出异常

        throw new RuntimeException();

    }finally{

        //告诉JVM:该方法正常返回

        return;

    }

}

说明:

在finally中处理return返回值会产生一些隐蔽性的错误

(22)不要在循环条件中计算

示例:

// 不推荐

public void test1(vector vector) {

    for (int i= 0; i < vector.size (); i++) {

        // do something

    }

}

  

// 推荐

public void test2(vector vector) {

     int size = vector.size ()

    for (int i= 0; i < size; i++) {

        // do something

    }

}

推荐使用guava扩展工具包

(23)代码注释应该正确、清晰、简洁

说明:

1.冗余注释、故事式注释、过时注释、大块无用的注释代码、流水账注释要去除

2.javaDoc注释不要引入过多的HTML标签影响可读性

3.未完成的代码要加上TODO注释

4.根据具体场景要加上警示性注释

(24)避免重复代码

说明:

如果在一个以上的地方看到相同的程序结构,那么设法将它们合而为一

(25)避免过长方法

说明:

如果一个方法内的大部分操作需要调用其他类的操作,应该将这个方法移至其他类中

(26)定义的变量名称,方法名称,类名称等应该具有一定的意义

说明:

不要定义类似a1,a2,abc…这类没有意义或意义不明显的变量。

(27)避免过大的类

说明:

应该按职能的不同将其细化为多个小类或子类。

如果每遇到某种变化,你都必须在许多不同的类内做许多小修改,应该把所有需要修改的代码放进同一个类中,使得“外界变化”与“需要修改的类”趋于一一对应

如果常常可以在很多地方看到相同的三四项数据(如:两类中相同的字段、许多方法签名中相同的参数),应该将这些数据项提炼到一个独立对象中

(28)避免过长的参数列表

说明:

太长的参数列表难以理解,而且容易造成前后不一致,不易使用,应该将对象传递给方法。

 

二、设计优化:

(1)接口职责要保持单一

说明:

这里的职责指的是一个接口(类)要承担的业务含义

(2)增强类的可替换性

说明:

只要父类可以出现的地方子类型就可以出现,并且将父类类型替换为子类型不会产生任何错误与异常,使用者不需要知道是父类型还是子类型

(3)模块之间的依赖通过抽象发生

说明:

实现类之间不要直接发生依赖关系

(4)一个类对另一个类的依赖应该建立在最小的接口上

说明:

例如,类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类C必须去实现他们不需要的方法。将臃肿的接口I拆分为独立的几个接口,类A和类C分别与它需要的接口简历依赖关系

(5)尽量降低类与类之间的耦合

说明:

陌生的类最好不要作为局部变量的形式出现在类的内部

 

 

下一篇:个人日常编码规范二

 

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值