天天看点

工作快四年,分享代码优化的15个小建议

1.空指针异常,不要过于相信查询到的数据,使用对象之前一定要做空指针处理,当然要符合实际情况,当是必要对象直接退出(return或throw),当是非必要数据,判断有值状态处理即可。

NullPointerException 在我们日常开发中非常常见,我们代码开发过程中,一定要对空指针保持灵敏的嗅觉。

主要有这几类空指针问题:

  1. 包装类型的空指针问题
  2. 级联调用的空指针问题
  3. Equals方法左边的空指针问题
  4. ConcurrentHashMap 类似容器不支持 k-v为 null。
  5. 集合,数组直接获取元素
  6. 对象直接获取属性
反例
public static String getStr(){
       String str[] = {"123",null,"456",""};
       int index = (int)(Math.random()*3);
       System.out.println("index="+index+",str="+str[index]);
       return str[index];
   }
    public static void main(String args[]){
        String str = getStr();
        if(str.equals("456"))
            System.out.println("str is 456");
        else
           System.out.println("str is not 456");
   }
           
正例
public static void main(String args[]){
      String str = getStr();
      if("456".equals(str))
          System.out.println("str is 456");
      else
         System.out.println("str is not 456");
 }
           

2. 捕获到的异常,不要直接用Exception统一捕获,也不要使用e.printStackTrace输出,不要忽略它,至少打点日志。

反例
public static void main(String args[]){
     try{
     }catch(Exception e){
         e.printStackTrace();  //直接控制台输出
     }
 }
           
正例
public static void main(String args[]){
     try{
       //…抛出 IOException 的代码调用
      //…抛出 SQLException 的代码调用
     }catch(IOException e){
         log.error("IOException is {}",e); //异常分类输出,出错容易定位
     }catch (SQLException e){
         log.error("SQLException is {}",e);
     }
 }
           

3. 采用Lambda表达式替换内部匿名类,使代码更优雅

JDK8出现了新特性-Lambda表达式。Lambda表达式不仅比匿名内部类更加优雅,并且在大多数虚拟机中,都是采用invokeDynamic指令实现,相对于匿名内部类,效率也更高。

反例
public void sortUserInfoList(List<UserInfo> userInfoList){
   userInfoList.sort(new Comparator<UserInfo>() {
       @Override
       public int compare(UserInfo user1, UserInfo user2) {
           return user1.getUserId().compareTo(user2.getUserId());
       }});
   }
           
正例
public void sortUserInfoList(List<UserInfo> userInfoList){
     userInfoList.sort((user1, user2) -> 
          return  user1.getUserId().compareTo( user2.getUserId());
     }); 
   }
           

4.如果变量的初值一定会被覆盖,就没有必要给变量赋初值。

反例
List<UserInfo> userList = new ArrayList<>();
 if (isAll) {
     userList = userInfoDaO.queryAll();
 } else {
     userList = userInfoDaO.queryActive();
 }
           
正例
List<UserInfo> userList =null ;
if (isAll) {
    userList = userInfoDAO.queryAll();
} else {
    userList = userInfoDAO.queryActive();
}
           

5.注意Arrays.asList的几个坑

  • 基本类型不能作为 Arrays.asList方法的参数,否则会被当做一个参数
public class ArrayAsListTest {
    public static void main(String[] args) {
        int[] array = {1, 2, 3};
        List list = Arrays.asList(array);
        System.out.println(list.size());
    }
}
//运行结果
1
           
  • Arrays.asList 返回的 List 不支持增删操作。
public class ArrayAsListTest {
    public static void main(String[] args) {
        String[] array = {"1", "2", "3"};
        List list = Arrays.asList(array);
        list.add("5");
        System.out.println(list.size());
    }
}
// 运行结果
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.AbstractList.add(AbstractList.java:148)
	at java.util.AbstractList.add(AbstractList.java:108)
	at object.ArrayAsListTest.main(ArrayAsListTest.java:11)
           

Arrays.asList 返回的 List 并不是我们期望的 java.util.ArrayList,而是 Arrays 的内部类ArrayList。内部类的ArrayList没有实现add方法,而是父类的add方法的实现,是会抛出异常的呢。

  • 使用Arrays.asLis的时候,对原始数组的修改会影响到我们获得的那个List
public class ArrayAsListTest {
    public static void main(String[] args) {
        String[] arr = {"1", "2", "3"};
        List list = Arrays.asList(arr);
        arr[1] = "4";
        System.out.println("原始数组"+Arrays.toString(arr));
        System.out.println("list数组" + list);
    }
}
//运行结果
原始数组[1, 4, 3]
list数组[1, 4, 3]
           

6. 尽量减少对变量的重复计算

一般我们写代码的时候,会以以下的方式实现遍历:

反例
for (int i = 0; i < list.size; i++){

}
           
正例

如果list数据量比较小那还好。如果list比较大时,可以优化成这样:

for (int i = 0, int length = list.size; i < length; i++){

}
           

对方法的调用,即使是只有一个语句,也是有有消耗的,比如创建栈帧。如果list比较大时,多次调用list.size也是会有资源消耗的。

7. 尽量不在循环里远程调用、或者数据库操作,优先考虑批量进行。

远程操作或者数据库操作都是比较耗网络、IO资源的,所以尽量不在循环里远程调用、不在循环里操作数据库,能批量一次性查回来尽量不要循环多次去查。但是呢,也不能一次性操作太多数据,可以设置一个阙值,比如一次性操作500的量。

反例
for(int i=0;i<n;i++){
  remoteSingleQuery(param)
}
循环单次操作
           
正例
remoteBatchOperation(param);
远程批量操作
           

8. 调用第三方接口,需要考虑异常处理,安全性,超时重试这几个点。

日常开发中,经常需要调用第三方服务,或者分布式远程服务的的话,需要考虑:

  • 异常处理(比如,你调别人的接口,如果异常了,怎么处理,是重试还是当做失败)
  • 超时(没法预估对方接口一般多久返回,一般设置个超时断开时间,以保护你的接口)
  • 重试次数(你的接口调失败,需不需要重试,需要站在业务上角度思考这个问题)

简单一个例子,你一个http请求调别人的服务,需要考虑设置connect-time,和retry次数。

9.接口需要考虑幂等性

接口是需要考虑幂等性的,尤其抢红包、转账这些重要接口。最直观的业务场景,就是用户连着点两次,你的接口有没有hold住。

一般幂等技术方案有这几种:

  • 查询操作
  • 唯一索引
  • token机制,防止重复提交
  • 数据库的delete/update操作
  • 乐观锁/悲观锁
  • Redis、zookeeper 分布式锁(以前抢红包需求,用了Redis分布式锁)
  • 状态机幂等

10. 修改对外老接口的时候,思考接口的兼容性。

很多bug都是因为修改了对外老接口,但是却不做兼容导致的。关键这个问题多数是比较严重的,可能直接导致系统发版失败的。新手程序员很容易就犯这个错误了哦~

所以,如果你的需求是在原来接口上修改,,尤其这个接口是对外提供服务的话,一定要考虑接口兼容。举个例子吧,比如dubbo接口,原本是只接收A,B参数,现在你加了一个参数C,就可以考虑这样处理。

//老接口
void oldService(A,B);{
  //兼容新接口,传个null代替C
  newService(A,B,null);
}
//新接口,暂时不能删掉老接口,需要做兼容。
void newService(A,B,C);
           

11. 代码采取措施避免运行时错误(如数组边界溢出,被零除等)

日常开发中,我们需要采取措施规避数组边界溢出,被零整除,空指针等运行时错误。

类似代码比较常见:

反例
String name = list.get(1).getName(); //list可能越界,因为不一定有2个元素哈
           

所以, 应该采取措施,预防一下数组边界溢出

正例
if(CollectionsUtil.isNotEmpty(list)&& list.size()>1){
  String name = list.get(1).getName(); 
}
           

12. 如果数据库一次查询的数量过多,建议分页处理。

如果你的Sql一次性查出来的数据量比较多,建议分页处理。

反例
select user_id,name,age from user_info ;
           
正例
select user_id,name,age from user_info limit #{offset},#{pageSize};
           

13. 如果确定只查询一条记录,建议使用limit 1

如果查询出来的数据只有一条,当查询出一条记录后,查询仍会继续

反例
select id,name from user_info where id='1';
           
正例

使用limit1 查询出一条数据后直接退出查询,效率更高

select id,name from user_info where id='1' limit 1;
           

14. 写查询Sql的时候,只查你需要用到的字段,还有通用的字段,拒绝反手的select *

反例
select * from user_info where user_id =#{userId};
           
正例
selct user_id , vip_flag from  user_info where user_id =#{userId};
           

理由:

  • 节省资源、减少网络开销。
  • 可能用到覆盖索引,减少回表,提高查询效率。

15. 使用spring事务功能时,注意这几个事务未生效的坑

日常业务开发中,我们经常跟事务打交道,事务失效主要有以下几个场景:

  • 底层数据库引擎不支持事务
  • 在非public修饰的方法使用
  • rollbackFor属性设置错误
  • 本类方法直接调用
  • 异常被try…catch吃了,导致事务失效。
反例
public class TransactionTest{
  public void A(){
    //插入一条数据
    //调用方法B (本地的类调用,事务失效了)
    B();
  }
  @Transactional
  public void B(){
    //插入数据
  }
}