C# Release和debug模式下調試跟蹤程式的原則和方法
取相對路徑:AppDomain.CurrentDomain.BaseDirectory
入門簡介
VS.Net 提供了兩種機制來協助開發人員診斷和糾正程式中的錯誤.一個是debug類, 另一個是trace類.這兩個類都有了一個assert函數. Assert在很多情況下可以用來檢驗變數,比如檢驗一個指標在調用了某一個系統的API之後是不是為空白。即使是使用try-catch塊,我們也要面對下面兩種情況,其一是我們要抓取系統的API拋出的異常,但是我們無法簡單的得到API失敗時的傳回值,我們只好來處理“異常”,,這會使編程變得複雜.其二,我們往往要產生兩個版本-測試版和發布版,這也使開發變得複雜。
如果你是一個從來沒有使用過assert和trace的程式員,你大可以讀到這兒為止了,因為下面我要講的東西可能沒有一點會減輕你編程工作的強度,也不會減少開發費用,也不能給你正在測試的程式一些好的建議.
這是我在實際開發中親身經曆的一個例子
好幾年以前,我還是一個多線路視頻監視系統的開發組長,我們的視頻系統可以通過RS-232線接收多達64個攝像機.這些攝像機直接或者通過專線或者通過撥號連線到視頻系統上來.這是一個複雜的系統,我在這兒就不多說了。
實現跟蹤的手段之一: 追蹤記錄檔(Trace logs)
在每一個項目的開始,我們通常要記錄下面的事情:
1. 所有與GUI有關的事件: 選擇菜單,選擇按鈕等等.
2. 所有內部的訊息(我們使用訊息體系來調用沒有個功能的實現)
3. 所有狀態的變化.
對於上面的實現方法,我們輸出一個調試日誌來報告與訊息相關的事件,訊息和當前的狀態.
實現跟蹤的手段之二: 斷言(assert)
我們也才有用assert來檢驗所有函數的參數和他們的傳回值.當觸發一個assert的時候,我們會把與這個相關的資訊寫的調試日誌中.這時候,就會關閉調試日誌,顯示一個錯誤資訊,並且中止程式的運行.
不配合的品質評估人員
在開發過程中,品質評估部門會測試不同的模組.最初,我們與品質評估部門的關係是敵對的,我們使用了先前的調試手段,而我們卻發現, 品質評估部門正好測試到我們不讓他們那麼實現的情況(他們只想引起管理層的注意).當我們糾正了各種不同的觀念之後, 品質評估部門的工作開始作的非常好.有一個問題,對於任何測試人員,當你問他具體作了些什麼,他們會非常安心的對你說,我點擊了X,然後是Y,接下去是Z. 而事實上,他們點擊的是A,B,C. 有了我們上面對於GUI的實現手段,我們沒有必要去問測試人員他們作了些什麼,我們自己完全可以從記錄檔中看的出來.
實地測試(Field Testing)
因為自我裝載與實際使用有很大的出入,我們會選擇一些使用者,提供一個beta版本給他們測試,這些版本中我們開啟了我們前面所講的調試手段.這可以顯示一些問題,多數是因為硬體的不同,而這個我們在自我裝載中並沒有發現.
迴歸測試(Regression Testing)
最後,用於我們前面說的調試手段已經做到GUI中,所有我們可以記錄和回放我們的測試教本.所以我們可以實現迴歸測試,這個測試是用來測試我們的新代碼對以前的東西有沒有影響.
開發測試(Development Testing) [Page]
不必多說,上面的調試手段對我們自己的開發測試也是非常有用的(特別是有了版本控制以後,它可以允許我們退回到舊版本上,測試相同的事件序列.)
提前發布產品
管理層因為上一個項目的原因非常惱火,因為他們要應付客戶的發火,所以這次分配了三個月的事件用來測試(以前從來沒有聽說這麼長的時間,我們的編碼僅用了六個月,而測試在編碼過程中就已經開始了!),由於我們的調試手段,品質評估部門僅用一個月的時間就沒有辦法再發現新的問題了,所以我們的產品提前發布.
發布版本 VS 測試版本
由於我們得調試手段非常深入,出於效能的原因,我們在發布版本中關閉了跟蹤的語句,但是我們在發布版本中保留了assert. 這是因為在實地工作中,我們的程式會連續不斷的運行,會使記錄檔非常大.此外,assert語句會報告一個非常有價值的訊息,為什麼會處罰這個assert,因為在品質評估部門必然會有東西會出錯,特別是由於世界各地不同的硬體產品.
我認為這是一個非常好的體系,發布一個不帶調試手段的版本給我們的客戶. 如果客戶那兒持續有問題,並且想和你一起來解決這個問題,你可以給他一個帶有全部調試手段的版本. 除此之外,使用正確的assert控制,你可以顯示一個非常漂亮的出錯資訊給你的客戶.我比較喜歡IE6的出錯資訊,就象這樣”對此使用引起的不方便表示道歉,我們發現一個問題等等”. 你可以用這個機會自動重新啟動你的程式.從一個使用者的觀點來看,這好像是被發生事故是被安全氣囊劃傷了一下子,卻不是直接撞在擋風玻璃上,這就是進步.如果客戶可以接入互連網,那麼你的程式可以把錯誤資訊發送給你,這就是為什麼麼在發布版本中保留assert的原因,你可以得到除了錯誤發生地址,棧的資訊,寄存器和記憶體映象以外更多的資訊.
調試(Debug)和跟蹤(Trace)
在C#中, debug 和 trace 類都提供了這些功能,但是它們都可以被關閉.預設情況下,在調試(Debug)模式下它們是可用的.在發布(release)模式下,dubg的功能是關閉的, trace仍然可以使用.它們兩個都支援 assert 和 trace. 通常的使用方法這樣. Debug.Assert 來使用assert功能, Trace.Write來使用trace功能.
如果你研究了我前面說得內容,就會覺得這樣作不對. 出於對程式效能和生存周期的考慮,純粹的trace應該在發布版本中拿去, 而Debug.Assert應該保留.
因此, 程式中應該用Debug.Write來儲存追蹤檔案, Trace.Assert來實現assert. 當在產生發布版時, Debug.Write就會失去作用,而assert仍然保留.
在Try-Catch 塊中處理異常
通常開發人員對於異常只有一個願望:它能不能看起來更溫和友好一些。不如,是不是可以讓使用者不必停止程式就可以結束對一個檔案的度操作?如果答案是是的話,那麼我們的代碼就是正確的,如果答案是否,我們就要產生一個斷言(assert).我們收集類似像這種情況的資訊,他們在處理異常是就會成為非常有用的東西,我們就可以在類似“程式運行失敗“訊息中,顯示更多的資訊給使用者。
Behind The Scenes
我們用assert的WriteLine來測試一下在編譯過的中間代碼中,測試版和發布版中的debug類實現起來有什麼不同
使用如下的C#代碼
Debug.WriteLine(\"Debug.WriteLine\");
System.Diagnostics.Trace.WriteLine(\"Test.WriteLine\");
Debug.Assert(false, \"Debug.Assert\");
System.Diagnostics.Trace.Assert(true, \"Trace.Assert\");
在測試模式下,編譯器產生如下的代碼
Debug.WriteLine(\"Debug.WriteLine\");
0000000f mov ecx,dword ptr ds:[01BA03CCh]
00000015 call dword ptr ds:[02EF870Ch]
System.Diagnostics.Trace.WriteLine(\"Test.WriteLine\");
0000001b mov ecx,dword ptr ds:[01BA03D0h]
00000021 call dword ptr ds:[02EF89F4h]
Debug.Assert(false, \"Debug.Assert\");
00000027 mov edx,dword ptr ds:[01BA03D4h]
0000002d xor ecx,ecx
0000002f call dword ptr ds:[02EF86ECh]
System.Diagnostics.Trace.Assert(true, \"Trace.Assert\");
00000035 mov edx,dword ptr ds:[01BA03D8h]
0000003b mov ecx,1
00000040 call dword ptr ds:[02EF89D4h]
在發布模式下,編譯器產生如下代碼
0000000f mov ecx,dword ptr ds:[01BA03CCh] [Page]
00000015 call dword ptr ds:[02EF870Ch]
0000001b mov edx,dword ptr ds:[01BA03D0h]
00000021 mov ecx,1
00000026 call dword ptr ds:[02EF86ECh]
你可以注意到如下有趣的幾點:
1. Deubg語句被完全拿掉了。
2. Trace.WriteLine實際上是調用了Debug.WriteLine.
3. Trace.Asser是調用了Debug.Assert
總結:
所有的debug函數都被拿掉了,所有的Trace函數都轉化為調用debug的函數。這是因為c#中條件屬性控制的原因,我們會用一個相同的機制來建立自己的一個更聰明的debug類。