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》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章