前言:
最近回头看了一下自己以前写过的代码,越来越觉得自己以前写的代码确实很LOW,还有需要可以改进的点,所以在这里总结一下。
逻辑条件顺序的调整,可以让程序更高效
假设现在有这样一个需求:会员,第一次登陆时,需要发送一条感谢短信。如果没有经过思考,代码可以直接这样写:
if(isUserVip && isFirstLogin)
{
sendMsg();
}
这么写是没有问题的,下面我们来分析一下,这个&&条件,如果第一个为假就不会向下执行了,所以当把isUserVip放第一位的话,假设有5个请求,只要是VIP,就会通过,后面也会进行判断,这无疑是增加了判断次数,所以我们打算把顺序换一下,这回呢?
if(isFirstLogin && isUserVip ){
sendMsg();
}
假设请求执行的次数是5次,那么isUserVip执行的次数是1次。
这不就更高效了嘛!!!!!
不必要的对象创建
我们来看看这个例子,判断用户会员是否处于有效期:
public boolean isUserVIPValid(){
Date now = new Date();
Calendar cal = Calendar.getInstace();
cal.set(2020,Calendar.JANUARY,1,0,0,0);
Date beginTime = cal.getTime();
cal.set(2021,Calendar.JANUARY,1,0,0,0);
Date endTime = cal.getTime();
return now.compareTo(beginTime) >=0 && now.compareTo(endTime) <=0;
}
我们看这段代码,每次调用的时候,都会创建Calendar和Date对象。除了new Date,其他对象都是不变的。所以我们可以抽出全局变量,避免创建了不必要的对象,从而提高程序效率。
public class isUserVip{
private static final Date BeginTime;
private static final Date endTime;
static{
Calendar cal = Calendar.getInstance();
cal.set(2020,Calendar.JANUARY,1,0,0,0);
beginTime = cal.getTime();
cal.set(2021,Calendar.JANUARY,1,0,0,0);
endTime = cal.getTime();
}
public boolean isUserVIPValid(){
Date now = new Date();
return now.compareTo(beginTime) >=0 && now.compareTo(endTime) <=0;
}
}
这样的代码更高效了么?
数据库查询,多查了没用的数据
数据库查询是比较耗时的操作,当数据量大的时候,更是低效率。所以尽量在查询的时候只查有用的数据。
比如举个例子:查询某个用户是否是会员。
List<Long> userIds = sqlMap.queryList("select userId from user where vip=1");
boolean isVip = userIds.contains(userId);
这个代码没有任何问题,但是把所有会员都查出来,再判断是否包含这个userId,这就很低效率了,直接查出来不好吗?
Long userId = sqlMap.queryObject("select userId from user where userId = 'userId' and vip = 1");
boolean isVip = userId != null;
实际上,我们除了把查询条件都传过去,避免数据库查多余的数据回来,还可以通过select 具体字段代替 select * , 从而使程序更高效。
不要让通知类代码影响到主流程
假设业务流程是这样的:需要用户登陆时,添加个短信通知它的粉丝。流程如下:
这个流程如果sendMsgNotify服务的系统挂了,或者调用sendMsgNotify失败了,那么用户登陆就失败了。
其实还有更好的方法实现,我们可以在开一个线程来处理这个方法。
因此我们添加通知类不是非主要,可降级的接口,所以主要流程需要我们好好的思考。
空指针的出现
NullPointException是经常出现的,所以我们在写代码的时候尽量避免低级的空指针异常。
假设如下业务场景,判断用户是否是会员:
boolean isVip = user.getUserFlag().equals("1");
这行代码,问题很大,user.getUserFlag()可能使null;
经过下面写法,可避免:
boolean isVip = "1".equals(user.getUserFlag());
日志的使用
关键业务的代码都应该使用日志。
假设你有一个转账业务,客户转装失败后进行了投诉,因为你没有打印日志,这就很难查找问题了。
日志打印在哪里呢?至少,在方法调用之前,入参需要打印,接口调用后,需要捕获一下异常,异常也需要打印日志。
public void transfer(TransferDto transferDto){
log.info("invoke tranfer begin");
try{
res=transferService.transfer(transferDTO);
}catch(Exception e)
{
log.error("transfer fail,cifno:{} account:{}",transferDTO.getCifno());
}
log.info("invoke tranfer end");
}
这里要注意info和error别弄混了,要不问题排查就不对了。
代码行数多是否可以划分呢
我们在看代码的时候总会看到一批几千行的代码。阅读起来真的费力。
我们可以将其进行划分。
一个过于冗长的函数或者一段需要注释才能让人理解的代码,可以考虑把它切分成一个功能明确的函数单元,并定义清晰简短的函数名,这样会让代码变得更加优雅。
可变因素可以做成配置化
现在假设产品提了个红包需求,圣诞节的时候,红包皮肤为圣诞节相关的,春节的时候,红包皮肤等。
if(duringChristmas)
{
img = redPacketChristmasSkin;
}else if()
{}
如果到了元宵节的时候,运营小姐姐突然又有想法,红包皮肤换成灯笼相关的,这时候,是不是要去修改代码了,重新发布了?
从一开始,实现一张红包皮肤的配置表,将红包皮肤做成配置化呢?更换红包皮肤,只需修改一下表数据就好了。
import导入没有用到的类
如果看到代码存在没使用的import 类,没被使用到的局部变量等,就删掉吧。
这些没被引用的局部变量,如果没被使用到,就删掉吧,它又不是陈年的女儿红,留着会越发醇香。它还是会一起被编译的,就是说它还是耗着资源的呢。
查询大表时,是否加入了索引,你的sql走了索引嘛。
查询数据量比较大的表时,我们需要确认三点:
- 你的表是否创建了索引
- 你的查询sql是否命中索引
- 你的sql是否还有优化余地
一般情况下,数据量超过10万的表,就要考虑给表加索引了。哪些情况下,索引会失效呢?like通配符、索引列运算等会导致索引失效。
你的方法返回是空集合还是null?
如果返回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 sattus = '0');
while(true)
{
int size = sqlMap.batchUpdate(params);
if(size<500)
{
break;
}
}
你的接口是否考虑到幂等性, 并发情况呢?
幂等性是什么?一次和多次请求某一个资源对于资源本身应该具有同样的结果。就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。
为什么需要幂等性?
- 用户在APP上连续点击了多次提交订单,总不能生成多个订单吧
- 用户因为网络卡了,连续点击发送消息,接受者总不能收到重复的同一条消息吧。
假设如下业务场景:
用户点击下载按钮,系统开始下载文件,用户再次点击下载,会提示文件正在下载中。
Integer count = sqlMap.selectCount("select count(1) from excel where state=1");
if(count<0)
{
Excel.setStatus(1);
updateExcelStatus();
downLoadExcel();
}else{
"文件正在下载"
}
执行流程:
- 第一步,A查询没有下载中的文件
- 第二步,B查询没有下载中的文件
- 第三步,A开始下载文件
- 第四步,B开始下载文件
这样两个文件就同时下载了,所以这个是不可行的。
正确的方式如下:
if(updateExcelStatus(1))
{
downLoadExcel();
}else{
"文件正在下载中"
}
私有构造器强化工具类
工具类的方法都是静态方法,通过类来直接调用即可。但是有些调用方可能会先实例化,再用对象去调用,而这就不好了。怎么避免这种情况,让你的工具类到达可控状态呢,添加私有构造器。
基本不变的用户数据,缓存起来,性能是否有所提升呢
假设你的接口需要查询很多次数据库,获取到各中数据,然后再根据这些数据进行各种排序等等操作,这一系列猛如虎的操作下来,接口性能肯定不好。典型应用场景比如:直播列表这些。
那么,怎么优化呢?剖析你排序的各部分数据,实时变的数据,继续查DB,不变的数据,如用户年龄这些,搞个定时任务,把它们从DB拉取到缓存,直接走缓存。
因此,这个点的思考就是,在恰当地时机,适当的使用缓存。
总结
我们在开发中,好好的思考一番,一定会越来越有收获,希望对你们有用,加油,在座的各位。。。。