美文网首页
记一个玄学bug

记一个玄学bug

作者: 哟哟黑喂狗康忙北鼻来此狗 | 来源:发表于2016-11-18 00:22 被阅读0次

    Part I

    写了一段代码要批量导出数据,格式如下,

    <data>
        <card_no>************</card_no>
        <id>16111710001385</id>
        <mobile_no>*********</mobile_no>
        <pid_code>************</pid_code>
        <pid_type>01</pid_type>
        <real_name>测试</real_name>
        <status>1</status>
        <user_id>000000000000099</user_id>
    </data>
    

    然后经过检查发现大量数据<real_name>字段为空,首先怀疑是数据库问题,执行sql测试,发现可以取得正常结果,数据库中有正常的密文存储结果,

    sql执行结果.png

    并且结果数量与xml文件中数量一致,说明提取的sql肯定没问题,然后排查结果集映射,跟real_name有关的映射如下:

    <result property="realName" column="user_name" />
    

    经过仔细验证也没问题(其实这里有点不自信,浪费了好久时间).
    至此我们已经基本排除了数据源和model层的问题(我以为),随后进下一层进行排查.

    Part II

    我注意到除了大量为空的数据以外,还有少量乱码,由于realName都是中文,所以怀疑是编码问题,业务流程是从数据库取值存到一个model的List里,再将List遍历逐个转化为XML元素,依次写入文件流,最终将文件流保存到本地文件中.
    那么可以看出最容易出问题的两个环节,一个是转换为xml时候的编码,一个是写入文件流时候的编码.(伏笔2)
    检查设置如下

    m.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
    

    多次测试以后发现并没有问题.

    Part III

    随后进行单元测试,直接运行用例,发现遍历列表输出无误,但是当遍历列表后再进入xml转换时出现问题.此时进入debug,彻底进入玄学

    @Test
        public void singleXMLTest(){
    
            List<UserCardInf> list = backupDao.selectUserCardInf("20161116000000");
            for(UserCardInf card:list){
                System.out.println(card);
            }
    //      BackupUtils.XMLIO(list,
    //              BackupUtils.getFileName(BackupUtils.USER_CARD_INF_NAME),
    //              BackupUtils.USER_CARD_INF_NAME);
    //      
        }
    

    进入debug后,点断点进入遍历,发现每个card的realName全部为空,这时在UserCardInf里面加入断点

    @XmlElement(name="real_name")
        public String getRealName() {   
            realName=DecodeAndEncodeUtil.desDecrypt(realName);
            return realName==null?"":realName;
        }
    

    发现进入get方法以后realName直接为空,百思不得其解,我决定去上个厕所.

    这个决定让我彻底走上了玄学的道路.

    等我回来以后,随手debug一次,一路F6,发现居然打印出了正确结果

    201611171000811:|6214*********6575:|182******557:|测试:|01:|510*********7999:|1:|
    

    我以为我的键盘成精,有个传说中的键盘姑娘什么的,毕竟不能辜负我的饮料饼干经常分它一部分,还经常给它洗澡,而且键盘出身好,大户人家(F厂),优质内涵(原厂轴)成精必然是个美女,我想还是可以接受的...因此我单方面宣布其实键盘自动帮我改正了.
    然而经过之前跟别人讨论时候的截图仔细对比,发现代码没有变化过.
    那么从科学的角度出发,我们可以考虑其他变量,首先快速debug一遍,一路断点直接F6,发现可以正常打印,这时我怀疑是在我断点期间其他线程操作了我的变量,导致没有正确结果.这时我很好奇传入的值,所以将鼠标指向card,这时第一个card已经打印完毕,结果正确,但是第二个card进入后依然为null,这时我意识到可能是我的鼠标指向操作影响了属性值,于是我在getRealName()方法上加了断点,如果调用get方法,必然会跳入断点,从而证实我的猜想.
    而结果令我非常失望,结果依然为null,并且没有跳入断点.所以这时我进行了大量重复试验,发现其他操作因素(时间/快慢/列表长度)都不影响结果,唯一影响结果的就是鼠标指向,那么我的鼠标又不是金手指可以直接擦写内存,我是如何影响realName属性的呢,我再次在getRealName方法上做文章,这次我加入了System.out.println(realName),并且采用了之前是乱码的那条数据,发现其规律是 密文->明文->乱码->乱码->null,因此我判断一定是我的鼠标指向操作调用了get方法,使得其多次调用解密操作,使得数据结果有误,最终由于数据太短无法解密返回null,于是我加上调用解密方法的日志,发现我每次鼠标指向果然会调用解密操作.
    最后我将get方法修改如下:

    private int i=0;
        @XmlElement(name="real_name")
        public String getRealName() {
            if(nameFlag){
                realName = DecodeAndEncodeUtil.desDecrypt(realName);
                nameFlag=false;
            }
            System.out.println("调用get@"+this.hashCode()+":"+i++);
            return realName==null?"":realName;
        }
    

    我发现每次鼠标指向都是IDE在后台自动调用了get方法,所以i的数量在依次变大,同时不会跳入get方法的断点...
    最终测试结果打印日志:

    调用get@20477080:1
    调用get@17485453:1
    调用get@20477080:2
    调用get@17485453:2
    调用get@20477080:3
    调用get@17485453:3
    调用get@20477080:4
    调用get@17485453:4
    调用get@20477080:5
    调用get@17485453:5
    调用get@20477080:6
    201611171000811:|621**************685:|182*******57:|测试:|01:|51***********99:|1:|
    调用get@17485453:6
    201611171000811:|621***********04:|1822*********57:|测试:|01:|5108****************99:|1:|
    databackup结束
    databackup执行时间为:28718 ms-------------
    

    经过一下午的努力,终于用科学破解了玄学了秘密,其实有很多次提前发现问题的关键,但是一方面是对自己的代码没自信,浪费了大量时间复查,一方面是对别人的代码太自信,导致了我一开始坚持认为调用解密方法不会出问题,更不可能返回null..

    虽然玄不救非,氪不改命,但是我用科学的方法破解了玄学,所以我经过科学推理发现我迟早要出循环仪式护肩...
    谢谢大家收看...我下班了...让我们拭目以待

    相关文章

      网友评论

          本文标题:记一个玄学bug

          本文链接:https://www.haomeiwen.com/subject/ujndpttx.html