官术网_书友最值得收藏!

1.6 程序錯誤與調試

絕大多數的初學者在編寫第一個程序時,往往都會出現這樣或那樣的錯誤,這是完全正常的。事實上,即使是有著豐富開發經驗的編程者,也幾乎不可能編寫出不經任何修改就運行無誤的程序(除非功能特別簡單)。程序中的錯誤可以分為3類—語法錯誤、運行時錯誤和邏輯錯誤。

1.6.1 語法錯誤

語法錯誤是指源文件的某些代碼不符合編程語言的語法規范(如缺少一個分號),具有語法錯誤的程序是不能運行的—因為無法通過編譯。

在編譯源文件時,編譯器會分析源文件的語法,若有錯誤則會給出錯誤所在的行號與描述信息,因此語法錯誤的定位與修改較為簡單。以1.5節中的HelloWorld.java為例,現對其做幾處修改以故意制造語法錯誤:將第3行中的public改成Public,刪除第8行中字符串的結束雙引號。保存并編譯,結果如圖1-14所示。

圖1-14 編譯有語法錯誤的源文件

圖1-14中,編譯器提示了錯誤的個數、每個錯誤所在的行號(源文件名后的數字)、具體位置(“^”符號指示的地方)及描述信息(行號后的文字)等,需要注意以下幾點。

(1)編譯器提示的錯誤個數可能不準確。如剛才制造了2個語法錯誤,但編譯器提示有4個。實際上,后3個錯誤都是由同一個錯誤引起的(缺少雙引號)。因此,不要總是試圖在程序中找出與提示相一致的錯誤個數,比較好的做法是,每改正一個錯誤(或所有能夠確定的錯誤)后立刻保存并重新編譯。

(2)編譯器提示的錯誤描述信息也不一定準確。如第3個錯誤提示第8行“需要分號”,但該行并不缺少分號,而是缺少結束的雙引號,因為Java中的字符串字面值(即常量)都是用一對雙引號括起來的—該錯誤實際上已被第2個錯誤提示描述了。

(3)一般來說,提示的錯誤所在行號總是準確或相對準確的。因此,要根據錯誤所在行號并結合描述信息及位置,綜合判斷出真正的錯誤所在。

語法錯誤還包括一類不安全的、無效的或在特定情況下可能引發邏輯錯誤的“輕微錯誤”,例如,聲明了從未被用到的變量、存在永遠都不可能被執行的代碼、使用了原始類型的容器等,這類“錯誤”被稱為警告。

嚴格來說,警告并不屬于語法錯誤的范疇,有警告的源文件依然能夠被成功編譯并運行,并且在通常情況下對程序的運行邏輯沒有影響。因此,警告的去除并不是必需的,但出于可靠性和代碼優化角度的考慮,去除警告有助于降低運行程序時出現邏輯錯誤的可能性要求比較嚴格的商業軟件項目的開發文檔一般會規定程序員編寫的代碼必須符合“零警告”。

此外,大多數編程語言的編譯器及IDE都提供了多種編譯選項,允許以不同的寬松級別對待程序中的警告。對于Java語言,還可以通過注解機制有選擇性地“抑制”程序中的某些警告(參見17.2.3節)。

1.6.2 運行時錯誤

運行時錯誤是指程序在運行階段出現的錯誤,這種錯誤通常由程序中的某些數據(如表示數組下標的變量超出范圍)、來自用戶的輸入(如輸入的除數是零)或程序所處的運行環境(如程序試圖訪問本機不存在的盤符)引起,因而無法在編譯階段檢查出來。

運行時錯誤通常會中斷程序的執行,嚴重的運行時錯誤甚至會引起程序的崩潰。在Java中,運行時錯誤通常以異常的形式出現(具體見第10章),編程者根據程序輸出的異常信息,一般能夠快速判斷出運行時錯誤出現的原因及出錯代碼所在的位置。

因運行時錯誤與程序要處理的數據(特別是來自于用戶輸入的數據)及運行環境有關,故很多時候需要對程序進行大量的測試才能重現這種錯誤。為降低運行時錯誤出現的可能性,應盡量避免數據硬編碼,并充分考慮各種有代表性的、將來用戶可能會輸入的數據。

1.6.3 邏輯錯誤

邏輯錯誤是指程序通過了編譯,且運行時沒有出現任何異常,但運行結果與預期不一致,例如,要求計算A乘B的值,但實際計算的是A加B。

程序出現邏輯錯誤是不可避免的,即使對于有著豐富經驗的程序員也是如此。另一方面,邏輯錯誤發生時,程序不會出現任何異常或提示,因而這種錯誤也是最難察覺的。尋找具有邏輯錯誤的代碼所耗費的時間往往比改正這個錯誤要多得多。

通常,應先根據程序的輸出信息判斷出錯誤所在的大致位置(范圍),然后通過人工檢查的方式逐行檢查范圍內的每行代碼是否正確—注意不是檢查語法上是否正確,而是檢查代碼是否完成了預期的邏輯,如“應該是乘而不是加”。

人工檢查的方式只適合程序的代碼行數較少或判斷出的錯誤所在范圍較小的情況,在實際開發中,這些情況很少滿足。另一方面,由于粗心或思維定勢等原因,這種方式經常不能檢查出錯誤所在。因此,更為可靠的定位并改正邏輯錯誤的方法是調試程序。

1.6.4 程序調試

無論使用何種編程語言和編程工具,調試(Debug)的基本原理總是一樣的。

(1)通過人工檢查的方式粗略判斷出錯誤所在的范圍,若無法判斷,則認為被執行的第一行代碼就可能有錯誤。

(2)在范圍的起始處設置斷點,并以調試方式執行程序,程序執行到斷點處會暫時停止執行。

(3)查看相關的、感興趣的變量或者表達式在這一時刻的實際值與預期值(即人腦計算出來的值,假設該值總是正確的)是否一致,若一致,則讓程序從該斷點處繼續執行下一行代碼。重復這一過程直至發現不一致,此時,被執行完的最后一行代碼就是錯誤所在的位置。在對比實際值與預期值的過程中,同時需要注意對比程序的實際執行流程是否與預期流程一致,這對于判斷分支、循環等結構的代碼錯誤所在尤為有用。

編程工具一般會提供諸如設置斷點、查看變量或表達式,以及讓程序從斷點處執行下一行(或下若干行)代碼等功能。JDK也提供了用于調試程序的工具—jdb.exe,但該工具是基于命令行的,在實際使用中非常不方便,對于較為復雜的程序,通常借助IDE來調試(參見附錄A)。

人工檢查與調試這兩種判斷邏輯錯誤所在位置的方式,讀者都應當熟練掌握,當程序邏輯較為復雜時,應優先考慮使用調試方式。當然,調試方式也有自身的局限性,對于某些特定的程序,有時很難用調試的方式來定位邏輯錯誤,如多線程程序如果在多線程程序的代碼中設置了斷點,程序會因為該斷點而暫停執行其他線程。另一方面,由于CPU對多個線程的調度時機是無法預期的,斷點的設置將影響實際的線程執行順序,換句話說,對于完全相同的代碼和數據,程序的運行邏輯和結果與用戶選擇讓程序從斷點處繼續向后執行的時機有關,這使得對程序的調試具有不確定性。。對于這樣的程序,可以采用一些輔助的技巧來幫助尋找邏輯錯誤,如在程序的合適位置添加輸出語句將變量或表達式的值輸出到命令行窗口以觀察程序的運行細節、注釋或取消注釋某些代碼行并結合排除法判斷錯誤所在行等。

總而言之,在定位程序的邏輯錯誤時,沒有一種方法能適用于所有場合。編程時,應學會并盡量遵守某些已被證明是行之有效的編碼規范和最佳實踐(參見附錄C),以最大限度減少邏輯錯誤的出現機會。當錯誤不可避免地發生時,應當根據錯誤所表現的具體特征及實際情況,靈活運用多種方式來分析和定位邏輯錯誤。

主站蜘蛛池模板: 伊川县| 常德市| 桦甸市| 泗阳县| 双鸭山市| 罗甸县| 宜川县| 本溪| 河北省| 萍乡市| 抚顺市| 许昌县| 思南县| 衡阳县| 乳山市| 浦城县| 汉寿县| 乐安县| 阜城县| 宁陕县| 湘乡市| 瑞金市| 大名县| 北碚区| 农安县| 保山市| 西宁市| 普兰县| 奉化市| 呼玛县| 虹口区| 饶河县| 志丹县| 镇沅| 禹州市| 利川市| 上蔡县| 金川县| 琼结县| 大庆市| 敦化市|