最近作完12月份版本需求,有一些思考不够深刻的代码,所以写一下总结,但愿你们平常写代码多点思考,多点总结,加油!同时哪里有不对的,也望指出。程序员
假设业务需求是这样:会员,第一次登录时,须要发一条感谢短信。若是没有通过思考,代码直接这样写了sql
if(isUserVip && isFirstLogin){ sendMsg(); }
假设总共有5个请求,isUserVip经过的有3个请求,isFirstLogin经过的有1个请求。
那么以上代码,isUserVip执行的次数为5次,isFirstLogin执行的次数也是3次,以下:数据库
若是调整一下isUserVip和isFirstLogin的顺序呢?后端
if(isFirstLogin && isUserVip ){ sendMsg(); }
isFirstLogin执行的次数是5次,isUserVip执行的次数是1次,以下:缓存
酱紫你的程序是否更高效呢?网络
举个粟子吧,判断用户会员是否处于有效期,一般有如下相似代码:mybatis
//判断用户会员是否在有效期 public boolean isUserVIPValid() { Date now = new Date(); Calendar gmtCal = Calendar.getInstance(); gmtCal.set(2019, Calendar.JANUARY, 1, 0, 0, 0); Date beginTime = gmtCal.getTime(); gmtCal.set(2020, Calendar.JANUARY, 1, 0, 0, 0); Date endTime= gmtCal.getTime(); return now.compareTo(beginTime) >= 0 && now.compareTo(endTime) <= 0; }
可是呢,每次调用isUserVIPValid方法,都会建立Calendar和Date对象。其实吧,除了New Date,其余对象都是不变的,咱们能够抽出全局变量,避免建立了没必要要的对象,从而提升程序效率,以下:并发
public class Test { private static final Date BEGIN_TIME; private static final Date END_TIME; static { Calendar gmtCal = Calendar.getInstance(); gmtCal.set(2019, Calendar.JANUARY, 1, 0, 0, 0); BEGIN_TIME = gmtCal.getTime(); gmtCal.set(2020, Calendar.JANUARY, 1, 0, 0, 0); END_TIME = gmtCal.getTime(); } //判断用户会员是否在有效期 public boolean isUserVIPValid() { Date now = new Date(); return now.compareTo(BEGIN_TIME) >= 0 && now.compareTo(END_TIME) <= 0; } }
你们都知道,查库是比较耗时的操做,尤为数据量大的时候。因此,查询DB时,咱们取所需就好,没有必要大包大揽。异步
假设业务场景是这样:查询某个用户是不是会员。曾经看过实现代码是这样。。。函数
List<Long> userIds = sqlMap.queryList("select userId from user where vip=1"); boolean isVip = userIds.contains(userId);
为何先把全部会有查出来,再判断是否包含这个useId,来肯定useId是不是会员呢?直接把userId传进sql,它不香吗?以下:
Long userId = sqlMap.queryObject("select userId from user where userId='userId' and vip='1' ") boolean isVip = userId!=null;
实际上,咱们除了把查询条件都传过去,避免数据库查多余的数据回来,还能够经过select 具体字段代替select *
,从而使程序更高效。
假设业务流程这样:须要在用户登录时,添加个短信通知它的粉丝。
很容易想到的实现流程以下:
假设提供sendMsgNotify服务的系统挂了,或者调用sendMsgNotify失败了,那么用户登录就失败了。。。
一个通知功能致使了登录主流程不可用,明显的捡了芝麻丢西瓜。那么有没有鱼鱼熊掌兼得的方法呢?有的,给发短信接口捕获异常处理,或者另开线程异步处理,以下:
所以,咱们添加通知类等不是非主要,可降级的接口时,应该静下心来考虑是否会影响主要流程,思考怎么处理最好。
NullPointException在Java世界早已司空见惯,咱们在写代码时,能够三思然后写,尽可能避免低级的空指针问题。
好比有如下业务场景,判断用户是不是会员,常常可见以下代码:
boolean isVip = user.getUserFlag().equals("1");
若是让这个行代码上生产环境,待君蓦然回首,可能那空指针bug,就在灯火阑珊处。显然,这样可能会产生空指针异常,由于user.getUserFlag()多是null。
怎样避免空指针问题呢?把常量1放到左边就能够啦,以下:
boolean isVip = "1".equals(user.getUserFlag());
关键业务代码不管身处何地,都应该有足够的日志保驾护航。
好比:你实现转帐业务,转个几百万,而后转失败了,接着客户投诉,而后你尚未打印到日志,想一想那种水深火热的困境下,你却毫无办法。。。
那么,你的转帐业务都须要那些日志信息呢?至少,方法调用前,入参须要打印须要吧,接口调用后,须要捕获一下异常吧,同时打印异常相关日志吧,以下:
public void transfer(TransferDTO transferDTO){ log.info("invoke tranfer begin"); //打印入参 log.info("invoke tranfer,paramters:{}",transferDTO); try { res= transferService.transfer(transferDTO); }catch(Exception e){ log.error("transfer fail,cifno:{},account:{}",transferDTO.getCifno(), transferDTO.getaccount()) log.error("transfer fail,exception:{}",e); } log.info("invoke tranfer end"); }
除了打印足够的日志,咱们还须要注意一点是,日志级别别混淆使用,别本该打印info的日志,你却打印成error级别,告警半夜三更催你起来排查问题就很差了。
咱们在维护老代码的时候,常常会见到一坨坨的代码,有些函数几百行甚至上千行,阅读起来比较吃力。
假设如今有如下代码
public class Test { private String name; private Vector<Order> orders = new Vector<Order>(); public void printOwing() { //print banner System.out.println("****************"); System.out.println("*****customer Owes *****"); System.out.println("****************"); //calculate totalAmount Enumeration env = orders.elements(); double totalAmount = 0.0; while (env.hasMoreElements()) { Order order = (Order) env.nextElement(); totalAmount += order.getAmout(); } //print details System.out.println("name:" + name); System.out.println("amount:" + totalAmount); } }
划分为功能单一的小函数后:
public class Test { private String name; private Vector<Order> orders = new Vector<Order>(); public void printOwing() { //print banner printBanner(); //calculate totalAmount double totalAmount = getTotalAmount(); //print details printDetail(totalAmount); } void printBanner(){ System.out.println("****************"); System.out.println("*****customer Owes *****"); System.out.println("****************"); } double getTotalAmount(){ Enumeration env = orders.elements(); double totalAmount = 0.0; while (env.hasMoreElements()) { Order order = (Order) env.nextElement(); totalAmount += order.getAmout(); } return totalAmount; } void printDetail(double totalAmount){ System.out.println("name:" + name); System.out.println("amount:" + totalAmount); } }
一个过于冗长的函数或者一段须要注释才能让人理解用途的代码,能够考虑把它切分红一个功能明确的函数单元,并定义清晰简短的函数名,这样会让代码变得更加优雅。
假如产品提了个红包需求,圣诞节的时候,红包皮肤为圣诞节相关的,春节的时候,红包皮肤等。
若是在代码写死控制,可有相似如下代码:
if(duringChristmas){ img = redPacketChristmasSkin; }else if(duringSpringFestival){ img = redSpringFestivalSkin; } ......
若是到了元宵节的时候,运营小姐姐忽然又有想法,红包皮肤换成灯笼相关的,这时候,是否是要去修改代码了,从新发布了?
从一开始,实现一张红包皮肤的配置表,将红包皮肤作成配置化呢?更换红包皮肤,只需修改一下表数据就行了。
若是看到代码存在没使用的import 类,没被使用到的局部变量等,就删掉吧,以下这些:
这些没被引用的局部变量,若是没被使用到,就删掉吧,它又不是陈年的女儿红,留着会愈加醇香。它仍是会一块儿被编译的,就是说它仍是耗着资源的呢。
查询数据量比较大的表时,咱们须要确认三点:
通常状况下,数据量超过10万的表,就要考虑给表加索引了。哪些状况下,索引会失效呢?like通配符、索引列运算等会致使索引失效。有兴趣的朋友能够看一下我这篇文章。
后端程序员必备:索引失效的十大杂症
若是返回null,调用方在忘记检测的时候,可能会抛出空指针异常。返回一个空集合呢,就省去该问题了。
mybatis查询的时候,若是返回一个集合,结果为空时也会返回一个空集合,而不是null。
正例
public static List<UserResult> getUserResultList(){ return Collections.EMPTY_LIST; }
阿里开发手册推荐了这一点
假设你的map要存储的元素个数是15个左右,最优写法以下
//initialCapacity = 15/0.75+1=21 Map map = new HashMap(21);
假设你的订单表有10万数据要更新状态,不能一次性查询全部未更新的订单,要分批。
反例:
List<Order> list = sqlMap.queryList("select * from Order where status='0'"); for(Order order:list){ order.setStatus(1); sqlMap.update(order); }
正例:
Integer count = sqlMap.queryCount(select count(1) from Order where status ='0'); while(true){ int size=sqlMap.batchUpdate(params); if(size<500){ break; } }
幂等性是什么?
一次和屡次请求某一个资源对于资源自己应该具备一样的结果。就是说,其任意屡次执行对资源自己所产生的影响均与一次执行的影响相同。
为何须要幂等性?
假设有业务场景:
用户点击下载按钮,系统开始下载文件,用户再次点击下载,会提示文件正在下载中。
有一部分人会这样实现:
Integer count = sqlMap.selectCount("select count(1) from excel where state=1"); if(count<=0){ Excel.setStatus(1); updateExcelStatus(); downLoadExcel(); }else{ "文件正在下载中" }
咱们能够看一下,两个请求过来可能会有什么问题?
执行流程:
显然,这样有问题,同时两个文件在下载了。正确的实现方式呢?
if(updateExcelStatus(1){ downLoadExcel(); }else{ "文件正在下载中" }
工具类的方法都是静态方法,经过类来直接调用便可。可是有些调用方可能会先实例化,再用对象去调用,而这就很差了。怎么避免这种状况,让你的工具类到达可控状态呢,添加私有构造器
public class StringUtis{ private StringUtis(){} ///私有构造类,防止意外实例出现 public static bool validataString(String str){ } }
假设你的接口须要查询不少次数据库,获取到各中数据,而后再根据这些数据进行各类排序等等操做,这一系列猛如虎的操做下来,接口性能确定很差。典型应用场景好比:直播列表这些。
那么,怎么优化呢?剖析你排序的各部分数据,实时变的数据,继续查DB,不变的数据,如用户年龄这些,搞个定时任务,把它们从DB拉取到缓存,直接走缓存。
所以,这个点的思考就是,在恰当地时机,适当的使用缓存。