Java代码质量小结

代码质量小结

  • 根据Sonar修改代码的小结
  • 主要结合 Sonar的规则和目前项目中的代码来分析

目录

Bug与漏洞

1. 给基本数据类型赋值前应该先做强制类型转换

为了避免溢出的情况

Sonar:Math operands should be cast before assignment

// 不规范                                      结果值:
float twoThirds = 2/3;                       // 0.0
long millisInYear = 1000*3600*24*365;        // 1471228928  
long bigNum = Integer.MAX_VALUE + 2;         // -2147483647
long bigNegNum = Integer.MIN_VALUE - 1;      // 2147483647
// 推荐写法                                     期望值:
float twoThirds = 2f/3;                      // 0.6666667
long millisInYear = 1000L*3600*24*365;       // 31536000000
long bigNum = Integer.MAX_VALUE + 2L;        // 2147483649
long bigNegNum = Integer.MIN_VALUE - 1L;     // -2147483649
// 或者
float twoThirds = (float)2/3; 
long millisInYear = (long)1000*3600*24*365; 
long bigNum = (long)Integer.MAX_VALUE + 2;
long bigNegNum = (long)Integer.MIN_VALUE - 1;

2. 使用float和double的注意事项

Sonar:Floating point numbers should not be tested for equality

可参考《Effective Java》第48条。
由于float和double是不精确的,所以不建议直接拿来比,尤其不适合用于货币计算。可以使用BigDecimal来比较

                // 真实值:
float f = 0.1;  // 0.100000001490116119384765625
double d = 0.1; // 0.1000000000000000055511151231257827021181583404541015625

3. if语句的判断条件不应该是一个确定值

Sonar:Conditions should not unconditionally evaluate to “TRUE” or to “FALSE”
IDE中会有提示,多注意下。

if (!hasNonMemberId) {
   return " and memberMap.projectCategoryPOID in " + this.getCommonWhere(memberIds);
}

// 检查是否只有无成员Id
if (memberIds.length == 1 && hasNonMemberId) {
    return " and memberMap.projectCategoryPOID is null ";
}
if (mCurrentData == null || 
    (mCurrentData != null && mCurrentData.isAccountIsOpened())) {
    // .......
}

// 优化后
if (mCurrentData == null || mCurrentData.isAccountIsOpened()) {
    // .......
}

性能提升

1.数组的复制建议使用System.arraycopy()

执行效率更高。

public static native void arraycopy(Object src, int srcPos, Object dst, int dstPos, int length);
    Long[] ids = temp.toArray(new Long[0]);
    memberIds = new long[ids.length];

    // 可替换的部分 
    for (int i = 0; i < ids.length; i ++) {
         memberIds[i] = ids[i];
    }

    // 替换后
    System.arraycopy(ids, 0, memberIds, 0, ids.length);

2.字符串转为基本数据类型的推荐用法

使用Integer.parseInt(String) 替代Integer.valueOf(String)。

Sonar:Parsing should be used to convert “Strings” to primitives

// Noncompliant Code Example
String myNum = "12.2";
float f = new Float(myNum).floatValue();  // 增加了开销

// Compliant Solution
String myNum = "12.2";
float f = Float.parseFloat(myNum);
public BottomBoardLoader getSubtitleLoader(BottomBoardBean bean) {
        assertTypeAndId(bean);

        // 旧写法,这里的bean.getId() 是String类型
        final int id = Integer.valueOf(bean.getId());

        // 推荐写法, 类似还有Long.parseLong(String)
        final int id = Integer.parseInt(bean.getId());
    // 返回的是装箱基本类型,进行一次装箱和一次拆箱,导致高开销
    public static Integer valueOf(String string) throws NumberFormatException {
        return valueOf(parseInt(string));
    }

    // 直接返回基本类型
    public static int parseInt(String string) throws NumberFormatException {
        return parseInt(string, 10);
    }

优点:可读性,效率更高。

基本类型与装箱基本类型的主要区别:

  1. 基本类型只有值,装箱基本类型具有与它们的值不用的同一性。
  2. 装箱基本类型有非功能值null(类类型)。
  3. 基本类型通常比装箱基本类型更节省时间和空间。

3. 单个字符索引,用lastIndexOf(int c)替换lastIndexOf(String string)

Sonar:Use Index Of Char
效率更高

    private static String getFileName(String url) {
        // 如果只有一个字符,建议使用 url.lastIndexOf('/')
        int index = url.lastIndexOf("/");
        String fileName = url;
        if (index > 0) {
            fileName = url.substring(index + 1);
        }
        return fileName;
    }

4. 基本数据类型转化为字符串的推荐用法

Sonar:Primitives should not be boxed just for “String” conversion
推荐的写法能节省开销,提高效率

    // 不规范的写法
    int myInt = 4;
    String myIntString = new Integer(myInt).toString(); // 多创建了一个Integer对象
    myIntString = Integer.valueOf(myInt).toString(); 
    myIntString = 4 + ""; 

    // 推荐的写法
    int myInt = 4;
    String myIntString = Integer.toString(myInt);
    // 项目中存在的地方:

    case Types.BOOLEAN:
        boolean b = rs.getBoolean(colIndex);
        if (!rs.wasNull()) {
        // 旧写法
        value = Boolean.valueOf(b).toString();
        // 推荐写法
        value = Boolean.toString(b);
        }

        // ......

     private String randomPasswordByPhone(String phone) {
        // 旧写法
        String currentTimeStr =  System.currentTimeMillis() + "";
        // 推荐写法
        String currentTimeStr =  Long.toString(System.currentTimeMillis());

代码规范

1.集合或者映射的非空判断

建议统一使用下面的工具类(项目中已有)

public class CollectionUtil {

    public static boolean isEmpty(Collection<?> collection){
        return collection == null || collection.isEmpty();
    }

    public static boolean isEmpty(Map map){
        return map == null || map.isEmpty();
    }
}

目前项目存在的写法

    // 原本的写法,类似地方有417处
    if (transTypeList != null && transTypeList.size() > 0) {}
    // 修改后
    if (!CollectionUtil.isEmpty(transTypeList)) {}

特定情况下(并发编程),可采用防御式编程

    if (mOutAccountList.size() <= 0) {}

2.不建议只声明字段为’public static’.

Sonar:通常没有一个好的理由去声明一个字段为’public static’ 却不加上’final’ ,因为任何类都能去修改这个共享的字段,甚至赋值为null。
建议: 添加final或改为private并提供get方法

    // 例如这种,应该添加final
    public static String EXTRA_KEY_TIME_ID = "extra_time_id";
    public static String EXTRA_KEY_CUSTOM_START_TIME = "extra_start_time";
    // 修改后
    public static final String EXTRA_KEY_TIME_ID = "extra_time_id";
    public static final String EXTRA_KEY_CUSTOM_START_TIME = "extra_start_time";

如我们项目中的BaseApplication的Context的对象,我们可以改为私有,提供get方法,而不是直接使用ApplicationContext.context来获取。

3. 类似命名为”getX()”并且返回值是布尔类型的方法,建议改名为”isX()”

增加可读性

    // 是否快速记账,之前的命名,别扭
    public static boolean getIsQuickAddTrans() {}
    // 修改之后
    public static boolean isQuickAddTrans() {}

4. 传进方法的参数,不应该被重新赋值,最好是设置为final

Sonar:Method parameters, caught exceptions and foreach variables should not be reassigned

当方法体过长,可能在后面调用时,不知道参数已经被改变,导致程序异常。

5. Switch语句末尾分支使用default

建议大于3种情况(或者后续会拓展)才使用switch,否则使用if-else,执行效率更高

// 推荐用法
switch (param) {
  case 0:
    doSomething(); // 注意不要在分支中写太多行代码,最好封装成方法
    break;
  case 1:
    doSomethingElse();
    break;
  default:
    error(); // 最好有相关处理
    break;
}

6.重写equals() 一定要重写 hashCode()

可以使用IDE自动生成。如果不这样做,可能导致该类无法结合所有基于散列的集合一起正常运作,包括HashMap,HashSet和Hashtable。

否则违反的Object.hashCode的约定:相等的对象必须具有相等的hashcode

7. 使用资源后应该及时关闭它

(输入输出流,操作数据库的游标等)

    public void put(String key, String value) {
        File file = mCache.newFile(key);
        BufferedWriter out = null;
        try {
            // sonar提示下面这行没有关闭资源,需不需要关闭?
            out = new BufferedWriter(new FileWriter(file), 1024); 

            out.write(value);
        } catch (IOException e) {
            DebugUtil.exception(TAG, e);
        } finally {
            if (out != null) {
                try {
                    out.flush();
                    out.close();
                } catch (IOException e) {
                    DebugUtil.exception(TAG, e);
                }
            }
            mCache.put(file);
        }
    }

答案:外层的流被关闭后,会自动关闭传递过来的流。

8. 简化逻辑,精简代码

删除无用的私有成员变量,私用方法,局部变量等。没用到的尽量删去,用到再重新写。提高逻辑严谨,去掉冗余代码

     Fragment fragment = getCurrFragment();
     if (fragment != null && fragment instanceof BaseAddTransObserverFragment) {
         ((BaseAddTransObserverFragment)fragment).doSaveTransactionTemplate();
     }

    // 简化后,即使 fragment = null 的时候,if的条件 也是 false
    if (fragment instanceof BaseAddTransObserverFragment) {
         ((BaseAddTransObserverFragment)fragment).doSaveTransactionTemplate();
     }
   private void closeCursor(Cursor c) {
        if (c != null) {
            if (!c.isClosed()) {
                c.close();
                c = null; // 多余,形参的生命周期结束
            }
        } else {
            c = null; // 多余,逻辑上已经为null
        }
    }

9. 数组获取”hashCode”和转为字符串的推荐用法

// Noncompliant Code Example

public static void main( String[] args ) {
    String argStr = args.toString(); // Noncompliant
    int argHash = args.hashCode(); // Noncompliant

// Compliant Solution

public static void main( String[] args ) {
    String argStr = Arrays.toString(args);
    int argHash = Arrays.hashCode(args);

10. 推荐使用BigDecimal.valueOf(double)替代BigDecimal(double)

使数据更加精确

    public static void main(String[] args) {
        BigDecimal d1 = new BigDecimal(0.1); // 不推荐
        BigDecimal d2 = BigDecimal.valueOf(0.1); // 推荐用法

        if (d1.equals(d2)) {
            System.out.print("==");
        } else {
            System.out.print("!=");  // 执行的结果,不相等
        }

        // 打印出来的值
        // d1:0.1000000000000000055511151231257827021181583404541015625
        // d2:0.1
    }
    public BigDecimal(double val) {
        this(val,MathContext.UNLIMITED); // 得到的是近似值
    }

    public static BigDecimal valueOf(double val) {
        return new BigDecimal(Double.toString(val)); 
    }
    public BigDecimal(String val) {
        this(val.toCharArray(), 0, val.length()); // 精确值
    }

11. 通过定义私有构造函数强化某类不可实例化的能力(工具类)

目前项目代码中还没有new一个工具类的情况。
但有时候你可能需要编写只包含静态方法和静态域的类,不希望被实例化。

// Noncompliant Code Example

class StringUtils { 

  public static String concatenate(String s1, String s2) {
    return s1 + s2;
  }

}

// Compliant Solution

class StringUtils { 

  private StringUtils() {
    throw new IllegalAccessError("Utility class");
  }

  public static String concatenate(String s1, String s2) {
    return s1 + s2;
  }
}

12. 使用可变参数

Sonar中建议使用可变参数,提高灵活性。但是《Effective Java》中第42条:慎用可变参数。请视情况而定。

    // 这种情况建议使用可变参数,参数个数不会引发异常
    static int sum(int... args) {
        int sum = 0;
        for (int arg : args) {
            sum += arg;
        }
        return sum;
    }
    // 这种情况不建议使用可变参数,需要1个以上的参数,同时编码不美观,必须对参数检查
    static int min(int... args) {
        if (args.length == 0) {
            throw new IllegalArgumentException("args");
        }
        int min = args[0];
        for (int arg : args) {
            if (arg < min) {
                min = arg;
            }
        }
        return min;
    }

13. 比较字符串是否相等时,字符串字面值应该放在左边

Sonar:Strings literals should be placed on the left side when checking for equality
这样做可以预防空指针异常

// Noncompliant Code Example

String myString = null;

// Noncompliant  引发空指针异常
System.out.println("Equal? " + myString.equals("foo")); 

// Noncompliant  这样写有点冗余
System.out.println("Equal? " + (myString != null && myString.equals("foo")));  


// Compliant Solution

System.out.println("Equal?" + "foo".equals(myString));  // 规范写法

14. 读和取方法应该成对使用synchronized

Sonar:Getters and setters should be synchronized in pairs

小结

  • 考虑时间复杂度与空间复杂度
  • 编写的方法注意检查参数的有效性,预防空指针
  • 提高逻辑严谨性
  • 多参考《Effetive Java》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章