代码质量小结
- 根据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》