代碼質量小結
- 根據Sonar修改代碼的小結
- 主要結合 Sonar的規則和目前項目中的代碼來分析
目錄
- 代碼質量小結
- 目錄
- Bug與漏洞
- 性能提升
- 代碼規範
- 1.集合或者映射的非空判斷
- 2.不建議只聲明字段爲’public static’.
- 3. 類似命名爲”getX()”並且返回值是布爾類型的方法,建議改名爲”isX()”
- 4. 傳進方法的參數,不應該被重新賦值,最好是設置爲final
- 5. Switch語句末尾分支使用default
- 6.重寫equals() 一定要重寫 hashCode()
- 7. 使用資源後應該及時關閉它
- 8. 簡化邏輯,精簡代碼
- 9. 數組獲取”hashCode”和轉爲字符串的推薦用法
- 10. 推薦使用BigDecimal.valueOf(double)替代BigDecimal(double)
- 11. 通過定義私有構造函數強化某類不可實例化的能力(工具類)
- 12. 使用可變參數
- 13. 比較字符串是否相等時,字符串字面值應該放在左邊
- 14. 讀和取方法應該成對使用synchronized
- 小結
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);
}
優點:可讀性,效率更高。
基本類型與裝箱基本類型的主要區別:
- 基本類型只有值,裝箱基本類型具有與它們的值不用的同一性。
- 裝箱基本類型有非功能值null(類類型)。
- 基本類型通常比裝箱基本類型更節省時間和空間。
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》