• 分享一些你代码更好的小建议,流畅编码提搞效率


    看过很多思考不够深入的代码,因此写一下总结吧,让你代码更好的小建议。希望大家日常写代码多点思考,多点总结,加油!同时哪里有不对的,也望指出,感谢哈~

    1. 仅仅判断是否存在时,select count 比 select 具体的列,更好。

    我们经常遇到类似的业务场景,如,判断某个用户userId是否是会员。

    「(反例):」  一些小伙伴会这样实现,先查从用户信息表查出用户记录,然后再去判断是否是会员:

    1. <select id="selectUserByUserId" resultMap="BaseResultMap">
    2.      selct user_id , vip_flag from  user_info where user_id =#{userId};
    3.  select>
    4. boolean isVip (String userId){
    5.  UserInfo userInfo = userInfoDAp.selectUserByUserId(userId);
    6.  return UserInfo!=null && "Y".equals(userInfo.getVipFlag())
    7. }

    「(正例):」 针对这种业务场景,其实更好的实现,是直接select count一下,或者select limit 1如下:

    1. <select id="countVipUserByUserId" resultType="java.lang.Integer">
    2.      selct count(1from  user_info where user_id =#{userId} and vip_flag ='Y';
    3.  select>
    4.  
    5.  boolean isVip (String userId){
    6.   int vipNum = userInfoDAp.countVipUserByUserId(userId);
    7.   return vipNum>0
    8. }

    2. 复杂的if逻辑条件,可以调整顺序,让程序更高效

    假设业务需求是这样:如果用户是会员,并且第一次登陆时,需要发一条通知的短信。假如没有经过思考,代码很可能直接这样写了。

    1. if(isUserVip && isFirstLogin){
    2.     sendMsgNotify();
    3. }

    假设总共有5个请求进来,isUserVip通过的有3个请求,isFirstLogin通过的有1个请求。那么以上代码,isUserVip执行的次数为5次,isFirstLogin执行的次数也是3次,如下:

    如果调整一下isUserVip和isFirstLogin的顺序呢?

    1. if(isFirstLogin && isUserVip ){
    2.     sendMsg();
    3. }

    isFirstLogin执行的次数是5次,isUserVip执行的次数是1次,如下:

    如果你的isFirstLogin,判断逻辑只是select count 一下数据库表,isUserVip也是select count 一下数据库表的话,显然,把isFirstLogin放在前面更高效。

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

    「反例:」

    select * from user_info where user_id =#{userId};
    

    「正例:」

     select user_id , vip_flag from  user_info where user_id =#{userId};
    

    「理由:」

    • 节省资源、减少网络开销。

    • 可能用到覆盖索引,减少回表,提高查询效率。

    4. 优化你的程序,拒绝创建不必要的对象

    如果你的变量,后面的逻辑判断,一定会被赋值;或者说,只是一个字符串变量,直接初始化字符串常量就可以了,没有必要愣是要new String().

    反例:

    String s = new String ("捡田螺的小男孩");
    

    正例:

    1. String s=  "捡田螺的小男孩 ”;

    5. 初始化集合时,指定容量

    阿里的开发手册,也明确提到这个点:

    假设你的map要存储的元素个数是15个左右,最优写法如下

    1.  //initialCapacity = 15/0.75+1=21
    2.  Map map = new HashMap(21);
    3.  
    4.  又因为hashMap的容量跟2的幂有关,所以可以取32的容量
    5.  Map map = new HashMap(32);

    6.catch了异常,需要打印出具体的exception,方便更好定位问题

    「反例:」

    1. try{
    2.   // do something
    3. }catch(Exception e){
    4.   log.info("捡田螺的小男孩,你的程序有异常啦");
    5. }

    「正例:」

    1. try{
    2.   // do something
    3. }catch(Exception e){
    4.   log.info("捡田螺的小男孩,你的程序有异常啦:",e); //把exception打印出来
    5. }

    「理由:」

    • 反例中,并没有把exception出来,到时候排查问题就不好查了啦,到底是SQl写错的异常还是IO异常,还是其他呢?所以应该把exception打印到日志中哦~

    7. 打印日志的时候,对象没有覆盖Object的toString的方法,直接把类名打印出来了。

    我们在打印日志的时候,经常想看下一个请求参数对象request是什么。于是很容易有类似以下这些代码:

    1. publick Response dealWithRequest(Request request){
    2.    log.info("请求参数是:".request.toString)
    3. }

    打印结果如下:

    请求参数是:local.Request@49476842
    

    这是因为对象的toString方法,默认的实现是“类名@散列码的无符号十六进制”。所以你看吧,这样子打印日志就没啥意思啦,你都不知道打印的是什么内容。

    所以一般对象(尤其作为传参的对象),「都覆盖重写toString()方法」

    1. class Request {
    2.     private String age;
    3.     private String name;
    4.     @Override
    5.     public String toString() {
    6.         return "Request{" +
    7.                 "age='" + age + '\'' +
    8.                 ", name='" + name + '\'' +
    9.                 '}';
    10.     }
    11. }
    12. publick Response dealWithRequest(Request request){
    13.    log.info("请求参数是:".request.toString)
    14. }

    打印结果如下:

    请求参数是:Request{age='26', name='捡田螺的小男孩'}
    

    8. 一个方法,拒绝过长的参数列表。

    假设有这么一个公有方法,形参有四个。。。

    1. public void getUserInfo(String name,String age,String sex,String mobile){
    2.   // do something ...
    3. }

    如果现在需要多传一个version参数进来,并且你的公有方法是类似dubbo这种对外提供的接口的话,那么你的接口是不是需要兼容老版本啦?

    1. public void getUserInfo(String name,String age,String sex,String mobile){
    2.   // do something ...
    3. }
    4. /**
    5.  * 新接口调这里
    6.  */
    7. public void getNewUserInfo(String name,String age,String sex,String mobile,String version){
    8.   // do something ...
    9. }

    所以呢,一般一个方法的参数,一般不宜过长。过长的参数列表,不仅看起来不优雅,并且接口升级时,可能还要考虑新老版本兼容。如果参数实在是多怎么办呢?可以用个DTO对象包装一下这些参数呢~如下:

    1. public void getUserInfo(UserInfoParamDTO userInfoParamDTO){
    2.   // do something ...
    3. }
    4. class UserInfoParamDTO{
    5.   private String name;
    6.   private String age; 
    7.   private String sex;
    8.   private String mobile;
    9. }

    用个DTO对象包装一下,即使后面有参数变动,也可以不用动对外接口了,好处杠杠的。

    9. 使用缓冲流,减少IO操作

    「反例:」

    1. /**
    2.  *  
    3.  *  @desc: 复制一张图片文件
    4.  */
    5. public class MainTest {
    6.     public static void main(String[] args) throws FileNotFoundException {
    7.         long begin = System.currentTimeMillis();
    8.         try (FileInputStream input = new FileInputStream("C:/456.png");
    9.              FileOutputStream output = new FileOutputStream("C:/789.png")) {
    10.             byte[] bytes = new byte[1024];
    11.             int i;
    12.             while ((i = input.read(bytes)) != -1) {
    13.                 output.write(bytes,0,i);
    14.             }
    15.         } catch (IOException e) {
    16.             log.error("复制文件发生异常",e);
    17.         }
    18.         log.info("常规流读写,总共耗时ms:"+(System.currentTimeMillis() - begin));
    19.     }
    20. }

    运行结果:

    常规流读写,总共耗时ms:52
    

    使用FileInputStreamFileOutputStream实现文件读写功能,是没有什么问题的。但是呢,可以使用缓冲流BufferedReaderBufferedWriterBufferedInputStreamBufferedOutputStream等,减少IO次数,提高读写效率。

    如果是不带缓冲的流,读取到一个字节或者字符的,就会直接输出数据了。而带缓冲的流,读取到一个字节或者字符时,先不输出,而是等达到缓冲区的最大容量,才一次性输出。

    「正例:」

    1. /**
    2.  *  
    3.  *  @desc: 复制一张图片文件
    4.  */
    5. public class MainTest {
    6.     public static void main(String[] args) throws FileNotFoundException {
    7.         long begin = System.currentTimeMillis();
    8.         try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("C:/456.png"));
    9.         BufferedOutputStream  bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("C:/789.png"))) {
    10.             byte[] bytes = new byte[1024];
    11.             int i;
    12.             while ((i = input.read(bytes)) != -1) {
    13.                 output.write(bytes,0,i);
    14.             }
    15.         } catch (IOException e) {
    16.             log.error("复制文件发生异常",e);
    17.         }
    18.         log.info("总共耗时ms"+(System.currentTimeMillis() - begin));
    19.     }
    20. }

    运行结果:

    缓冲流读写,总共耗时ms:12
    

    10. 优化你的程序逻辑,比如前面已经查到的数据,在后面的方法也用到的话,是可以把往下传参的,减少方法调用/查表

    「反例:」

    1. public Response dealRequest(Request request){
    2.     
    3.     UserInfo userInfo = userInfoDao.selectUserByUserId(request.getUserId);
    4.      if(Objects.isNull(request)){
    5.        return ;
    6.      }
    7.    
    8.     insertUserVip(request.getUserId);
    9.    
    10. }
    11. private int insertUserVip(String userId){
    12.       //又查了一次 
    13.       UserInfo userInfo = userInfoDao.selectUserByUserId(request.getUserId);
    14.       //插入用户vip流水
    15.       insertUserVipFlow(userInfo);
    16.       ....
    17. }

    很显然,以上程序代码,已经查到 userInfo,然后又把userId传下去,又查多了一次。。。实际上,可以把userInfo传下去的,这样可以省去一次查表操作,程序更高效。

    「正例:」

    1. public Response dealRequest(Request request){
    2.     
    3.     UserInfo userInfo = userInfoDao.selectUserByUserId(request.getUserId);
    4.     if(Objects.isNull(request)){
    5.        return ;
    6.      }
    7.    
    8.     insertUserVip(userInfo);
    9. }
    10. private int insertUserVip(UserInfo userInfo){
    11.       //插入用户vip流水
    12.       insertUserVipFlow(userInfo);
    13.       ....
    14. }

  • 相关阅读:
    git 操作备注
    QT串口助手-ZUA课设
    配置Super-VLAN下的DHCP服务器示例
    python 实验3
    RISC-V函数调用约定 ABI
    实验一、综合实验【Process on】
    开关电源32个检测项目、检测方法与检测设备(下)
    运维工程师现在用什么系统
    5G+北斗高精度定位系统适用于哪些行业领域?
    VisualStudio删除干净电脑所有的有关文件
  • 原文地址:https://blog.csdn.net/weixin_72753070/article/details/126031079