比较容易忽视的几个BUG

2,204 阅读5分钟

本文正在参加「金石计划」

前言

在日常开发的过程中,通常我们需要先看需求文档,根据需求文档的需求进行开发,其实在我们了解业务的时候,头脑就已经在构建代码如何写了,工作几年后可能一些常见的400、404、500、503等一些报错应该会很少出现,可能会出现一些看似正常,但实际业务接口返回有问题的情况,随着我们业务逻辑的复杂性,这些潜存的BUG可能会隐藏的很深,在某种程度上可能会迷惑开发人员、测试人员,从而让BUG溜到生产。

关于bug排查

  1. 首先先借住代码规范工具,可以根据idea的Alibaba Java Coding Guidelines阿里的代码规范,看看代码有没有爆黄之处,可以先从这个点看看逻辑是否有问题,之前公司还会用到checkStyle,自己设定了一套规范只需要传入checkStyle.xml进行配置即可,也可以检查是不是因为代码规范带来的bug
  2. 针对于某一个接口,逐行的去看代码,这里如果你是工作几年后对业务代码有所理解,可以去看到代码,你在看代码的过程中脑子或许就已经在构建没行代码的返回值,每个if判断会出现的情况,对这个结果进行排查。
  3. 如果你刚工作没几年, 那么第二条可以不用去考虑,直接拿到入参,根据postman去请求,然后debug代码一行一行的看,具体看每个对象在执行的过程中里面属性的值的变化,这种方式也是最稳妥的排查方式,关于代码我只相信执行结果,即便是现在我也不会嫌麻烦,还是去拿到接口入参去debug调试,这种方式我认为是最准确也是比较稳妥的bug排查方式。
  4. 接口中打日志,接口请求前期进行logger打日志,对入参出参进行日志记录,在业务相对复杂执行结果处进行日志记录,包括一些状态(status)、标识(flag)、list对象和map集合进行stream函数操作、包括过滤、计算、格式转换后的结果,进行日志记录。

关于出现的bug

入参指针问题

对入参数进行stream函数操作,最后还使用原本改变过的参数进行业务处理,导致数据会出现重复或者缺失。

    /**
     * 创建用户
     * @param userDTOS
     * @return
     */
    @Override
    public boolean createUser(List<UserDTO> userDTOS) {

        List<SysUser> sysUsers = Lists.newArrayList();

        check(userDTOS,sysUsers);
        

        return true;
    }

    /**
     * 校验逻辑
     * @param userDTOList
     * @param sysUsers
     */
    private void check(List<UserDTO> userDTOList, List<SysUser> sysUsers) {

//        List<UserDTO> userDTOS = Lists.newArrayList();
//        List<UserDTO> updateUserDTOS = Lists.newArrayList();

        if (CollectionUtils.isNotEmpty(userDTOList)) {
            //过滤出需要新增的数据
            userDTOList = userDTOList.stream().filter(r -> r.getActionType() != null).collect(Collectors.toList());

            List<SysUser> sysUserList = BeanCopyUtil.copyListProperties(userDTOList, SysUser::new);
            //过滤出需要更新的数据
            userDTOList = userDTOList.stream().filter(r -> r.getActionType() == null && !StringUtils.isEmpty(r.getTag())).collect(Collectors.toList());

            List<SysUser> updateSysUserList = BeanCopyUtil.copyListProperties(userDTOList, SysUser::new);
            //数据更新  逻辑省略

            //数据新增  逻辑省略

            //组装数据
            sysUsers.addAll(sysUserList);
            sysUsers.addAll(updateSysUserList);
            log.info("用户信息数据组装返回结果{}", JSON.toJSONString(sysUsers));
        }

    }

运行结果

这里可以看到,我们的入参是一个List对象,包括三条数据。

12FA2969-595F-47C3-B02D-67E5671168B9.png

这里可以看到,我们通过第一次过滤,过滤出来了两条数据。

909FAE3A-FF0D-4753-A9FA-9C23397955BA.png

再次过滤的时候,发现更新数据没有过滤出来,是因为我们拿已经过滤出来的数据进行了二次过滤,导致不会存在对应的数据,是因为我们拿过滤数据的参数和接受过滤结果的参数,用的是同一个List对象,这样指向就会出现问题,导致我们漏掉了一条数据。

7D4C31AB-E51F-4DA7-838A-9E0FEE44E348.png

这种情况在业务代码很多很复杂的情况下,真的很容易出现,让人眼花导致写错,出现bug,需要重新声明List对象进行过滤结果的接收,这样才不会漏数据,这也是写代码习惯的问题,⚠️我在检查代码的时候确实看到过有人这么写过导致出现数据缺失,出现bug。

EBBA5D36-2156-41D5-9B94-C6762B8B9EE3.png

Mybatis的隐式转换

前端通过字符串类型传值,传了字符串为“0”的参数。

02ED9A1C-F610-4417-A873-0A8372539E06.png

这里可以看到,Mybatis已经将字符串类型转为Integer类型了,

C5D4E7AA-8635-4FC0-A723-187816E9C4FD.png

这时候在mapper.xml里对这个入参,如果进行了!=’‘ 判断,由于入参已经被转为Integer类型,在查询的时候这个参数就不会被拼接到sql后面,导致查询数据出现问题。

710CDF2A-16B1-46E7-B594-F7A5B66F7E36.png

事务接口未提交前的影响

一个事务的接口,还没执行完之前,通过查询,然后更新某个状态,然后在进行查询。

@Override
@Transactional(rollbackFor = Exception.class)
public Boolean transactionCommit(String userName) {
    //查询用户
    SysUser sysUser = userMapper.selectUserByUserName(userName,null);
    if (null != sysUser) {
        //用户信息状态更新 status更新为1
        userMapper.updateStatus(userName);
    }
    //再次查询
    SysUser sysUser1 = userMapper.selectUserByUserName(userName,"1");

    log.info("状态为1的用户信息"+JSON.toJSONString(sysUser1));
    return  true;
    
}

这段代码我通过debug模式断点调试,这里可以看到我已经将user表里的status的状态更新为“1”,并且在事务内进行查询,也是可以查询到状态为“1”的数据,但是我通过在数据库执行sql,查询到的还是status为”0“的数据

85A708F1-2182-417B-A9DE-95E43A5A6157.png

0538981C-6D86-432F-83D4-E233B76F1229.png

这时候我走完debug提交完事务,这里status的状态变为“1”,由此可见mysql的隔离级别是可重复读的,但是这个时候出现时间差的时候会出现幻读,所以在某种程度上我们的接口还没执行完,测试在测试阶段进行数据库查询数据,可能并不是他所想查询到的数据。

总结

这些是最近项目中出现的一些bug贴出来与大家分享,或许也是大家写代码会忽略的一些问题,有时候需求很紧一个接着一个来,我们在写代码的时候,可能会为了赶进度有些代码写出来,是没有经过思考的,导致bug的出现,我们需要在追求新技术的同时,coding好自己手头上工作的代码,即便是一些枯燥无味的CRUD,也是一种锻炼。