如何解決代碼中難以重現的BUG?
難以重現的bug一般有幾類。
並發相關的。並發高,負載重時,隨機出現。這類bug,有可能在測試環境通過load testing 重現。加log,儘可能縮小範圍,細化粒度,然後請經驗豐富的同事 code review。
數據相關的。只有某些數據,甚至某種處理數據的順序,才能觸發bug。測試環境中跑mock data沒用。可以把生產環境的數據拿下來,在測試環境里跑。
環境相關的。生產環境和測試環境的framework,lib,jvm,或os某個版本不同。這類是比較坑的,有時一個os的patch不同,都可能有影響。只能一點點嘗試替換,是苦力活。
最好的辦法,是和某個資深程序員聊天,有技巧的提起這個難題,激起他的好奇心和好勝心,然後他就會接手了。
我覺得,遇到這樣的情況,首先應該做的就是叫上幾個熟悉多線程,高並發的先審查一下代碼,看是否哪個地方有多線程風險之類的。
然後,可能需要把生產環境的數據弄下來測試,關鍵位置用日誌輸出關鍵的信息,仔細核對日誌。最後還無望,那可能要考慮使用的平台,或者框架,甚至更底層出故障的可能性。
提出另一個點的解決方案,除了打log之外,有時候也可以考慮看看代碼中是不是有某些顯而易見或者很難找到的問題 :空指針引用:有時候路徑走到了崩潰,路徑沒走到不崩潰。緩衝區溢出:大內存沒問題,小內存出問題。資源泄漏:內存逐漸被佔滿,重啟後解決,長時間後重現。多線程問題(該加鎖沒加鎖,加了鎖沒釋放,競態條件....):這事兒是最難調試的,見過太多多線程問題,十個使用有9個加了鎖,1個沒加鎖,結果問題就出現在這個上。。。客戶保護原因隱去名字。。。。做了些更改,刪除所有評論。
1,越詭異,越感覺複雜的Bug,越是非常弱智,非常渺小的問題導致的。2,實在難以重現,就走白盒,2分法切除邏輯,逐步確認錯誤代碼區間。3,還有一種難以重現的Bug,就只能看Dump,錯誤信息相關記錄,然後一點點推業務邏輯解決了。
一般兩個思路:自頂向下、自底向上。
前者是說從現象和日誌中查找有用線索,縮小排查範圍,逐步找出可疑點。
後者是說從代碼層面分析,看看運行中覆蓋了哪些邊界條件,寫測試用例嘗試復現場景。
兩者也可以結合起來使用。
定位出來之後,就趕緊還上開發時欠下的債。把這個場景的測試用例補齊吧:)
還是要考慮預先做好完善的log,才容易排查這類問題很多碼農懶於寫注釋,打log,認為太浪費時間其實這會導致代碼上線後一旦出現問題,排查問題時的被動log也不用太多,只要放在關鍵的步驟就行,記錄涉及到的所有變數、環境變數、外部參數等等現在打log已經很難會導致伺服器性能下降了,所以只要不是海量的log儘管打吧,怕磁碟不夠就定時遠程scp到備份伺服器去,或者乾脆走內網打遠程log,開銷也不大的
個人做法1. 選用一種可以運行期熱配置的log框架 (比如說java的log4j)2. 把代碼里說明執行步驟的的注釋改為log.trace,並記錄關鍵的變數值
3. 在代碼的主流程上使用TDD (比如說spock),把測試里的步驟說明複製到代碼里改成log.trace。例如這樣: Test Driven Development 一定要用單元測試來實現嗎?單元測試是否會影響工作效率? - 知乎用戶的回答
大批量的數據測試環境也是可以模擬的,只能說是測試不嚴格。具體你說的這個問題,大概率在同步問題上。比如請求a,檢查沒有重複,添加數據。請求b,檢查沒有重複,添加數據。如果a,b添加的都是相同數據,同時檢查都沒有重複,添加之後就重複了。對LZ的項目情況不了解,建議檢查資料庫和代碼是否存在不合理地方
此外,測試環境儘可能與生產環境一致,加大數據測試量(用真實數據)上高精度的log,任何問題都能查到原因。
但是,在生產環境中上高精度log:
1.會非常影響系統性能。
2.需要特大容量的存儲設備來存儲這些日誌。
3.需要非常強大的集群來處理這些日誌,從而找到bug根源。
我猜題主的問題是一個事務還沒提交,另一個事務已經開始造成的。看問題描述是還沒找到bug嘛。
上下文不一樣,只能回答一般怎麼解決的。
一、找bug(bug復現) 從寫入dao從低向上review到業務觸發代碼,特別注意線程的使用,代碼里用的線程池還是新開線程,該加鎖的地方是不是粒度控制有問題。找到之後,fix掉,補充用例,回歸用例。其實這時候也是有點手心冒汗的,因為你也不確定還有多少類似隱藏的問題。總不能case by case吧 二、如何更有效的保障應用質量呢? 題主也說了,線下OK,線上不OK,線上線下環境是有區別的,如果有機制能夠將線上的調用記錄下來,(記錄入參,出參,異常等),然後在線下將這些調用執行一遍,對出入參和異常做對比,OK?改動OK。不OK?那就再看看吧。 三、多重校驗都沒有發現這個問題,首先要看這個多重校驗是不是必要了~,也從側面可以排除一類問題了。上下文不同,確實很難一針見血,這類問題其實最好的去處是 stackoverflow。
希望對你有所幫助。
打 log,想辦法重現出來,辦法比問題多,「膽大,心細,打 log」
record replay
正好之前寫過這方面的東西,強答一記:還有樓上有人回答的自定而下的方法:
排除法
一點點排除可能引起問題的關鍵因素,直到確定是由某個原因引起的。此方法適用於以前工作正常,在某個時間或條件之後才出現的問題。這個問題是什麼時候開始出現的?我們把這之後添加的新功能(代碼)一點點拿掉看看問題是否出現,直到定位到具體某次改動。舉個例子,比如你在完成一個組裝電腦之後,開機沒有任何反應,此時你可以考慮從正常工作的電腦上逐步替換掉問題電腦的內存硬碟CPU等零件,直到問題消失就能定位到底是具體哪個零件出了問題。
最小環境法
在一個簡單且沒有過多因素干擾的環境中驗證問題根源和本質。此方法適用於比較複雜的系統和環境,和適用如果使用排除法需要排除太多東西,並且我們對該問題的起因有一定的認識的情況。比如你在調試一個程序問題的時候,出問題的部分和其他模塊或子系統有錯綜複雜的關係,為了定位問題的本質和消除其他外部因素的影響和干擾,可以考慮新建一個簡單項目,在這個沒有其他因素影響的環境中重現和解決問題之後再應用到原來的項目中。對於可能由一些驅動或者硬體平台引起的問題,可以考慮在一個純凈的虛擬機環境中重現從而定位你的問題。
使用日誌和斷點
這個是調試程序問題的慣用方法,對此不打算詳細論述。主要就是在懷疑可能出問題的地方設置斷點或者列印出相關變數的值,以便於確定從哪兒開始,程序已經出現了錯誤了,從而定位到一開始出錯的地方加以修正。就是把出問題的系統或者模塊劃分成幾個部分,把可能的解決方案列一一列出來,針對每個部分逐步驗證。在嘗試的時候應該把確定不能解決問題的方案以及和問題無關的部分從列表中刪除,不再嘗試。對於確定問題所在的部分繼續逐步劃分,直到定位問題的根源和找到解決方案。
來自如何系統性的解決難題www.macode.net/resolve-problem-in-systematic-way/1. 確認是數據問題還是邏輯問題?
是不是你的校驗機制導致在大批量的數據下,一定會讓某些數據的checksum相等,雖然概率很小。這個驗證也很簡單,找到重複的數據,然後走一遍邏輯。然後假如不是數據問題,確實是邏輯問題。請看22. 查一些特定環境下的極小概率BUG。除了靠運氣,憑感覺在某些地方加調試信息或者改代碼以外,唯一有用的幫助,就是做快照。做快照就是當出了問題以後,完整的保留當時的環境信息,雲風以前查過一個極難查的BUG,最後是通過對當時的整個系統做內存快照查的。我們一般是在所有的函數裡面加入進出的說明,然後把列印全部放開,等處了問題,集中的調查那部分日誌,真的是大海撈針····。你的這個問題,要是我的話,假如系統CPU富裕的情況下,會在每單數據跑完以後,把這個數據做MD5,然後進HASH表,然後直接掛GDB,然後條件斷點,發現相同的直接掛在那裡。
別說跑伺服器上不能GDB,你直接把整個伺服器上的數據都考出來···
log,log,還是log我覺得,我們好像先得把邏輯講清楚,在考慮代碼問題之前先把非代碼的因素給排除掉,如運行環境的問題,如果本身環境就不支持大量並發,或者數據在過負荷傳輸中出現誤差,這些問題非人為因素應該先確認。
然後就是回來看代碼,測試下無法重現bug,有的答主認為是多線程的問題,我覺得有點片面,如果代碼沒有自查,那麼在延遲的情況下,單單解決代碼也沒啥解決的啊。
一般如在機器學習中,測試用例已經開始用非常大的數字了,那麼在一般商業開發中,我們α測試中選取一般量測試沒發現問題,β測試出現反饋bug,提高測試量應該是正常的,然後還不能夠重現,那麼樣本可能就存在問題了,樣本在程序里過的意義是模擬現實應用的情況,那麼我覺得,不可重現的原因主要還是測試樣本的問題。
當然這是廢話,我要是知道是什麼樣本出的bug,我不就修復了嗎。
那麼,一般對於如何找出這個樣本的一般方法不就是抓異常嘛,在關鍵位置寫入catch代碼放到線上繼續測試咯。例如查重,是不是可能各終端請求ID混了,是不是延遲啊,等等,錯誤就log唄。
馬克和女友討論一下再回答
搞過類似的,建表約束,捕獲異常,打log,上線,結束。不要共享資源,不要使用變數,把純函數和副作用分開。。。
相信我,再多線程也很難寫出「無法重現的bug」
沒必要請一群多線程經驗非常豐富的高級程序員來解決一些可以想辦法避免掉的問題
總結:不用java
推薦閱讀:
※習得一種編程語言後如何高效地學習其他語言?
※一個簡單C語言編程問題?
※iOS程序員的第二門語言選什麼?
※目前有哪些計算機編程語言能(或者不能)自舉?
※R語言和D3可視化可以結合起來嗎?