又一个低级的生产BUG
if(newDto.getStoreName().equals(oldDto.getStoreName())&&newDto.getStoreCode().equals(oldDto.getStoreCode())&&newDto.getGoodsName().equals(oldDto.getGoodsName())&&newDto.getGoodsSpec().equals(oldDto.getGoodsSpec())&&newDto.getStallName().equals(oldDto.getStallName())&&newDto.getUnitName().equals(oldDto.getUnitName())){//不更新}else{updList.add(newDto);}- 问题是:没有对属性null做判断导致NPE。
修改后的代码:
if(Objects.equals(newDto.getStoreName(),oldDto.getStoreName())&&Objects.equals(newDto.getStoreCode(),oldDto.getStoreCode())&&Objects.equals(newDto.getGoodsName(),oldDto.getGoodsName())&&Objects.equals(newDto.getGoodsSpec(),oldDto.getGoodsSpec())&&Objects.equals(newDto.getStallName(),oldDto.getStallName())&&Objects.equals(newDto.getUnitName(),oldDto.getUnitName())){//不更新}else{updList.add(newDto);}java.util包都提供这个工具类了,还有什么不用起来的理由!!出这种问题太他妈丢人了,写的时候是咋想的啊!
又一个低级的生产BUG
publicstaticMultiPriceInfogetMultiPriceInfo(List<MultiPriceInfo>multiPriceInfos,Stringcode){if(CollectionUtils.isEmpty(multiPriceInfos)){returnnull;}for(MultiPriceInfopriceInfo:multiPriceInfos){if(priceInfo.getPriceCode().equals(code)){returnpriceInfo;}}returnnull;}- 问题1 作为一个public方法,没有校验参数code。
- 问题2. priceInfo.getPriceCode().equals(code)可能会抛出NPE,因为priceInfo.getPriceCode()可能会返回null。
修改以后的代码:
publicstaticMultiPriceInfogetMultiPriceInfo(List<MultiPriceInfo>multiPriceInfos,Stringcode){// 增加对code的校验if(CollectionUtils.isEmpty(multiPriceInfos)||StringUtils.isEmpty(code)){returnnull;}for(MultiPriceInfopriceInfo:multiPriceInfos){// 前面已经确保了code不是nullif(code.equals(priceInfo.getPriceCode())){returnpriceInfo;}}returnnull;}两个修改点:
- 增加对入参code为空的校验。
- 改为code.equals(priceInfo.getPriceCode())。
就因为这个小小的问题,从凌晨0点搞到将近3点,就是找不到问题在哪,对代码还是有敬畏之心,不能太随意,安安稳稳躺床上睡觉它不香吗!!!
还有一点:如果是异步执行,一定要在异步方法的最外层加一个try-catch-finally进行兜底,否则全局异常拦截器拦截不到,再没有catch的话,就只能进shell看日志了!就怕没有shell的权限!
又一个低级的生产BUG
上周五,生产环境的二维码下载功能开始频繁报警,看了下代码,下载二维码这个功能服务端提供了2个接口,一个是生成二维码的压缩包,放到tomcat的临时目录里面,然后第二个是下载接口,现在的问题是,第一个接口没问题,第二个接口报的NPE。第二个接口非常简单,就是读取文件写到respoonse里面,如果NPE那就只有一个可能,那就是下载的文件不存在。并且这个接口有一定概率报错,还不是必现的,我只好猜测因为下载的文件是存放在临时目录,是不是因为系统硬盘空间不足而被系统删除了,但是本地怎么也模拟不出来!在这个方面浪费了大量的时间,还是没找到一点头绪,幸亏系统有全链路监控日志,后来仔细观察日志,才发现是异常是在一个Aop的拦截器里面抛出来,根本不是业务代码抛出来的!它是这么写的:
@Aspect@ComponentpublicclassLogApiAdvice{@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")publicObjectdoAround(ProceedingJoinPointjoinPoint)throwsThrowable{try{Objectresult=joinPoint.proceed();if(result.getClass().isAssignableFrom(ApiResult.class)){....}else{....}returnresult;}catch(Throwablethr){....throwthr;}}}我们的下载的接口是这样的:
@ApiOperation("下载二维码")@RequestMapping(value="/downCodeZip")publicvoiddownCodeZip(HttpServletResponseresponse,StringzipFilePath){}很显然,方法的返回值是null,因此在aop里面就发生了NPE!很低级的一个失误,导致生产不停报警,最终代码回滚,写代码还是得注意啊!
修改方法签名导致的bug
有一个方法原先是返回Integer,此方法在多个地方被调用,改的时候改成了返回String,自测通过,junit也正常,但是上生产报错了!
按道理说,只要方法签名名改了,所有调用的地方应该编译就报错啊,操蛋的是项目使用了MapStruct,背后自动把String又转成了Integer,所以编译完全没问题,但是一旦返回null就炸了。
结论:修改方法一定要全局检索调用的地方,不要仅仅依赖编译器。