經驗確實很重要

    爲什麼你寫的代碼好好的,在別人機器上就運行出錯呢?,這大概是所有的程序員最糾結的事情了。最近在開發新模塊也在測試修改遺留的bug,稍微積累了一點心得:對程序員來說,經驗真的很重要。(難怪很多公司都要招有一定工作年限的程序員)好了,詳細說說。    
    我最初比較有印象的一個bug是這樣的。
    現象:一個月前有一個項目,裏面有多個工程,但他們互不影響。假如我需要在工程A中引用工程B中的一個函數,則要麼將A中的這個功能製作成DLL,要麼將這部分代碼,看懂然後不厭其煩的寫一個。好吧我選擇後者。畢竟多幾個dll在用戶的安裝路徑下誰能保證將來不會有某些用戶刪除呢?!一切進展的很順利,但是一個WindowsAPI函數報錯了:GetNativeSystemInfo(XXX);(各位可以看我最近發的帖子)。很納悶,在B工程都沒問題,同一臺電腦,同一個頭文件,爲什麼會錯!!!然後我搜了一下,換成了GetSystemInfo(XXX),沒問題,槓槓的!但是,這個API它可能會獲取到錯誤的信息(詳情百度)。當時和其他人討論了一下這個問題,領導的意見是:就用這個,當獲取的信息是錯誤的時候肯定Windows都更新到N版本了沒我們的事,先發布再說,短時間內沒問題。好吧,就這樣把這個模塊完成了。一個月後,測試那邊告訴我,爲什麼檢查出的數據都是32位的系統,這不可能啊!然後……就是這個api用錯了!
    問題根本原因:原來GetSystemInfo(XXX)這個API在獲取64位系統的位數的時候是會出錯的。然後我想用回那個編譯報錯的GetNativeSystemInfo(XXX)。詳細找了一下出現這個錯誤的根本原因,發現:這個API必須在這個條件下使用:
#if _WIN32_WINNT >= 0x0501
WINBASEAPI
VOID
WINAPI
GetNativeSystemInfo(
    __out LPSYSTEM_INFO lpSystemInfo
    );
#endif
但是程序中的 定義的是#define _WIN32_WINNT 0x0500 。至於說定義這個數值的原因是爲了讓軟件兼容早起的Windows版本。
    解決方法:不能修改定義好的_WIN32_WINNT 值,但是可以通動態加載(LoadLibrary, GetProcAddress)的方式使用 GetNativeSystemInfo() 獲取正確的值(看我的帖子,裏面也有解答)。
    這個bug啊,說實話,就是難找根本原因,誰能想到系統每次建立工程後自動生成的stdafx.h文件會被改掉呢?而且這種問題應該說很少見,但是碰到了確實會讓人糾結。

 

   第二個bug,還是和上面描述的B項目相關。

    現象:我要去查看用戶的office版本。因爲A項目也有這個功能,但是以插件的形式做出來的。我還是看了一下那部分邏輯,然後照着樣子寫了一個出來,誒,測試通過。但是別人反饋給我:有些人電腦上讀出來了,有些人電腦上讀不出來!奇怪哈,對比一下兩份代碼發現基本一樣,不應該啊!然後看了一下讀取成功的用戶和讀取失敗的用戶,發現他們的環境變量啊,用戶保護等級設置什麼的都沒有太大出入。這就出鬼了!

    根本原因:在接下來的幾天中,我無意中發現,那個A模塊的這個功能會有一個彈窗:用戶賬戶控制。我突然想到,難道是因爲我的B模塊沒有獲得用戶權限個?!跟蹤代碼後發現,在讀取註冊表的模塊失敗了,但是假如我以管理員賬戶運行我的程序,讀取成功!!!
    解決方法:我試着按照讀註冊表的方法使用部分api而不是去讀寫註冊表,發現window確實在你讀取的時候還是寬容的,至少它發現你沒有改變註冊表鍵的情況下,沒有要求你驗證什麼。
    就該bug本身,當初自己測試的時候確實沒有意識到讀取註冊表可能會引起權限問題,然後測試的時候獲取的數據也符合我的邏輯(沒有讀取到office版本,也算一個合理的結果)。經過這個bug,我發現其實軟件的細節不僅僅在語法和邏輯上,還在運行這些代碼本身的機器上,假如我之前碰過這麼一個問題,也就不會有這次的bug了。
    
    第三個bug!這一次真是長經驗了。
    現象:我有一個模塊,在用戶界面顯示了亂碼.整個項目的同事都說我又出問題了(特別是:沒準那塊內存又越界了),然後加班唄.
    解決方法:我仔細的跟蹤管理一下代碼,代碼中的邏輯顯然是正確的,然後又同事在旁邊嘀咕了一句:是不是服務端的數據不正確?然後我想啊:我的數據經過了服務端,然後服務端會返回整理後的數據.是不是那邊的就是亂碼?我去服務端查了一下:果然,就是你妹的服務端down了,返回的是一大堆亂碼!當然要是有log的話我也就不用找怎麼久了。
    總結:有時候,真的是應該全局的看待一個問題,不要人云亦云(他們都說我的模塊就可能問題出現在我的代碼裏面)!由於本公司的傢伙都不喜歡加log功能,導致後期維護人員查一個bug特別困難,因此以後自己開發的項目,要合理的添加log模塊.
    
    第四個bug!
    這個bug特別有意思,有一個模塊,必須要一些數據返回給服務端,通過jso包返回,然後就是有一個傢伙的電腦上發現:他的jso包數據在服務端上總是顯示不出來!然後,不好意思,這個模塊又是我寫的.然後查代碼、查邏輯,最後發現,代碼和邏輯一點問題都沒有啊,傳遞上去的數據也是合理的。爲毛這個人的數據會丟失呢?我只好在服務端查看了一下接受的流程。然後發現額呵:他的數據中有個字符"\uXXXX",但是"\u"被轉義了……然後它那邊就顯示這個人的數據沒有收集到!!!!這!怎麼說呢?確實可以說是我的問題,但是人無完人嘛,誰能想到的那麼仔細呢。
    解決方法:在jso數據上傳前,將“\”替換爲“/”防止轉義!
    經驗總結:雖然我不處理由其他模塊生成的數據,因爲99%的情況下這些數據都是不會有問題的,但是不排除某些用戶比較“隨便”,比如隨便設置一個文件名,導致檢測該文件路徑的時候出現了問題。所以,下次應該將所有數據中的“轉義字符”重新編碼!
    
    第五個bug,展示經驗和技術的時候到了.
    現象:我有一個操作excel的指針變量,new出來後,在delete的時候發生了heap corruption情況大概類似這個鏈接描述的樣子(http://blog.sina.com.cn/s/blog_4ae178ba0101164d.html);假如換成了普通成員(非指針),用完後,在右括號地方發生了statck corrupted情況類似這個鏈接中圖片的樣子(http://blog.csdn.net/chenyujing1234/article/details/8261914)按照他們的方法改,都不行!!!!然後找變量是否被別的線程佔用?沒有!看變量是否在其他地方釋放?沒有!然後叫同事過來看一下,同事說,要不你把new 和delete的操作包裹到該變量所屬類的地方,然後在使用的時候引用這兩個函數.這樣操作後,確實沒有問題了.
    根本原因:事後,我請教了一下跟我說這個方法的同事.答案類似如下:你在函數的局部new的時候,可能該變量(一個類的對象)的部分內存是在主框架分配的,但是該類部分成員是DLL裏面分配的(操作excel確實用到了dll).然後在主框架釋放的時候並沒有將dll裏面分配的內存釋放到,導致出現了這個問題,你把new和delete操作包裹成所屬類的成員函數,保證了:在哪裏分配,就會在哪裏釋放!妥妥的很有道理啊!
    總結一下該bug:內存在哪裏分配,就應該在哪裏釋放!這句話簡單啊,假如沒有碰到此類問題,我肯定還是想當然的以爲:這句話的意思不就是必須在同一個代碼塊內處理內存問題嘛.其次,其實早點碰到此類問題是有好處的,保證了下次碰到類似問題的時候多一個思考方向.
    通過上面三個小例子,我確實發現,經驗確實挺重要的,也許我有很好的coding功底以及邏輯思維功底,但是,有些問題,確實和這些無關。所依不論是通過別人的例子獲取經驗還是自己從問題中汲取經驗對於從事我們這行的人來說,尤爲重要!至少可以保證,當你發現一個問題的時候,會有多一個思考方向,而不是在原來的衚衕裏面繞來繞去。
    望各位同行共勉!
   
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章