記一個(gè)玄學(xué)bug

Part I

寫了一段代碼要批量導(dǎo)出數(shù)據(jù),格式如下,

<data>
    <card_no>************</card_no>
    <id>16111710001385</id>
    <mobile_no>*********</mobile_no>
    <pid_code>************</pid_code>
    <pid_type>01</pid_type>
    <real_name>測(cè)試</real_name>
    <status>1</status>
    <user_id>000000000000099</user_id>
</data>

然后經(jīng)過檢查發(fā)現(xiàn)大量數(shù)據(jù)<real_name>字段為空,首先懷疑是數(shù)據(jù)庫(kù)問題,執(zhí)行sql測(cè)試,發(fā)現(xiàn)可以取得正常結(jié)果,數(shù)據(jù)庫(kù)中有正常的密文存儲(chǔ)結(jié)果,

sql執(zhí)行結(jié)果.png

并且結(jié)果數(shù)量與xml文件中數(shù)量一致,說明提取的sql肯定沒問題,然后排查結(jié)果集映射,跟real_name有關(guān)的映射如下:

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

經(jīng)過仔細(xì)驗(yàn)證也沒問題(其實(shí)這里有點(diǎn)不自信,浪費(fèi)了好久時(shí)間).
至此我們已經(jīng)基本排除了數(shù)據(jù)源和model層的問題(我以為),隨后進(jìn)下一層進(jìn)行排查.

Part II

我注意到除了大量為空的數(shù)據(jù)以外,還有少量亂碼,由于realName都是中文,所以懷疑是編碼問題,業(yè)務(wù)流程是從數(shù)據(jù)庫(kù)取值存到一個(gè)model的List里,再將List遍歷逐個(gè)轉(zhuǎn)化為XML元素,依次寫入文件流,最終將文件流保存到本地文件中.
那么可以看出最容易出問題的兩個(gè)環(huán)節(jié),一個(gè)是轉(zhuǎn)換為xml時(shí)候的編碼,一個(gè)是寫入文件流時(shí)候的編碼.(伏筆2)
檢查設(shè)置如下

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

多次測(cè)試以后發(fā)現(xiàn)并沒有問題.

Part III

隨后進(jìn)行單元測(cè)試,直接運(yùn)行用例,發(fā)現(xiàn)遍歷列表輸出無誤,但是當(dāng)遍歷列表后再進(jìn)入xml轉(zhuǎn)換時(shí)出現(xiàn)問題.此時(shí)進(jìn)入debug,徹底進(jìn)入玄學(xué)

@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);
//      
    }

進(jìn)入debug后,點(diǎn)斷點(diǎn)進(jìn)入遍歷,發(fā)現(xiàn)每個(gè)card的realName全部為空,這時(shí)在UserCardInf里面加入斷點(diǎn)

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

發(fā)現(xiàn)進(jìn)入get方法以后realName直接為空,百思不得其解,我決定去上個(gè)廁所.

這個(gè)決定讓我徹底走上了玄學(xué)的道路.

等我回來以后,隨手debug一次,一路F6,發(fā)現(xiàn)居然打印出了正確結(jié)果

201611171000811:|6214*********6575:|182******557:|測(cè)試:|01:|510*********7999:|1:|

我以為我的鍵盤成精,有個(gè)傳說中的鍵盤姑娘什么的,畢竟不能辜負(fù)我的飲料餅干經(jīng)常分它一部分,還經(jīng)常給它洗澡,而且鍵盤出身好,大戶人家(F廠),優(yōu)質(zhì)內(nèi)涵(原廠軸)成精必然是個(gè)美女,我想還是可以接受的...因此我單方面宣布其實(shí)鍵盤自動(dòng)幫我改正了.
然而經(jīng)過之前跟別人討論時(shí)候的截圖仔細(xì)對(duì)比,發(fā)現(xiàn)代碼沒有變化過.
那么從科學(xué)的角度出發(fā),我們可以考慮其他變量,首先快速debug一遍,一路斷點(diǎn)直接F6,發(fā)現(xiàn)可以正常打印,這時(shí)我懷疑是在我斷點(diǎn)期間其他線程操作了我的變量,導(dǎo)致沒有正確結(jié)果.這時(shí)我很好奇?zhèn)魅氲闹?所以將鼠標(biāo)指向card,這時(shí)第一個(gè)card已經(jīng)打印完畢,結(jié)果正確,但是第二個(gè)card進(jìn)入后依然為null,這時(shí)我意識(shí)到可能是我的鼠標(biāo)指向操作影響了屬性值,于是我在getRealName()方法上加了斷點(diǎn),如果調(diào)用get方法,必然會(huì)跳入斷點(diǎn),從而證實(shí)我的猜想.
而結(jié)果令我非常失望,結(jié)果依然為null,并且沒有跳入斷點(diǎn).所以這時(shí)我進(jìn)行了大量重復(fù)試驗(yàn),發(fā)現(xiàn)其他操作因素(時(shí)間/快慢/列表長(zhǎng)度)都不影響結(jié)果,唯一影響結(jié)果的就是鼠標(biāo)指向,那么我的鼠標(biāo)又不是金手指可以直接擦寫內(nèi)存,我是如何影響realName屬性的呢,我再次在getRealName方法上做文章,這次我加入了System.out.println(realName),并且采用了之前是亂碼的那條數(shù)據(jù),發(fā)現(xiàn)其規(guī)律是 密文->明文->亂碼->亂碼->null,因此我判斷一定是我的鼠標(biāo)指向操作調(diào)用了get方法,使得其多次調(diào)用解密操作,使得數(shù)據(jù)結(jié)果有誤,最終由于數(shù)據(jù)太短無法解密返回null,于是我加上調(diào)用解密方法的日志,發(fā)現(xiàn)我每次鼠標(biāo)指向果然會(huì)調(diào)用解密操作.
最后我將get方法修改如下:

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

我發(fā)現(xiàn)每次鼠標(biāo)指向都是IDE在后臺(tái)自動(dòng)調(diào)用了get方法,所以i的數(shù)量在依次變大,同時(shí)不會(huì)跳入get方法的斷點(diǎn)...
最終測(cè)試結(jié)果打印日志:

調(diào)用get@20477080:1
調(diào)用get@17485453:1
調(diào)用get@20477080:2
調(diào)用get@17485453:2
調(diào)用get@20477080:3
調(diào)用get@17485453:3
調(diào)用get@20477080:4
調(diào)用get@17485453:4
調(diào)用get@20477080:5
調(diào)用get@17485453:5
調(diào)用get@20477080:6
201611171000811:|621**************685:|182*******57:|測(cè)試:|01:|51***********99:|1:|
調(diào)用get@17485453:6
201611171000811:|621***********04:|1822*********57:|測(cè)試:|01:|5108****************99:|1:|
databackup結(jié)束
databackup執(zhí)行時(shí)間為:28718 ms-------------

經(jīng)過一下午的努力,終于用科學(xué)破解了玄學(xué)了秘密,其實(shí)有很多次提前發(fā)現(xiàn)問題的關(guān)鍵,但是一方面是對(duì)自己的代碼沒自信,浪費(fèi)了大量時(shí)間復(fù)查,一方面是對(duì)別人的代碼太自信,導(dǎo)致了我一開始堅(jiān)持認(rèn)為調(diào)用解密方法不會(huì)出問題,更不可能返回null..

雖然玄不救非,氪不改命,但是我用科學(xué)的方法破解了玄學(xué),所以我經(jīng)過科學(xué)推理發(fā)現(xiàn)我遲早要出循環(huán)儀式護(hù)肩...
謝謝大家收看...我下班了...讓我們拭目以待

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容