7.1.2 語義或“運行期”錯誤 語法錯誤的發現和處理是令人煩惱的,但在編程中會遇到一些真正“令人興奮”的另一類型的錯誤——語義錯誤(semantic error)或稱“運行期”錯誤(runtime error)。這類錯誤僅當運行一個腳本代碼或其他程序時才會發現。換句話說完整有效的代碼已經通過解釋器或編譯器的解釋或編譯,在執行時產生了錯誤。術語“運行期錯誤”通過是指語義錯誤的結果,也就是說這類錯誤存在于代碼的語義中,當代碼運行時它們才變成可見的。 這種區別來自于這種事實:程序編譯器或解釋器在處理程序代碼之前必須建立一種內部代碼的描述,涉及多種結構開頭和結尾的匹配,以便標明每種結構包含什么內容,然后分析每個句子,以便知道如何執行這個句子。例如,如果在程序代碼中有一個If Then … Else … End If 結構,解釋器或編譯器做的第一步工作就是分析哪些語句在“Then”的部分,哪些在“Else”部分。這一步的目的是,在對結構中的If條件進行測試之后,可以決定該到哪個分支去執行。 編譯器(諸如在編程語言像Visual Basic和C++中見到的那種)和解釋器(諸如用于像VBScript和JScript那樣的腳本語言的解釋器)之間真正區別在于:編譯器不試圖運行程序代碼,而是在對源程序進行兩次預處理后,形成二進制指令或符號代碼,并形成一個.exe文件或.dll文件。解釋器不含有代碼的文件,而是在運行時逐步執行。 1. 使運行停止的錯誤 如果程序中含有一個語義錯誤,通常在運行時可得到提示。如果幸運的話,當錯誤發生時,程序會停止,這樣可以容易地找出錯誤所在。例如,下面這段程序定義了一個有六個元素的數組。 <% Dim arrValues(5) 'to hold six elements, indexed from 0 to 5 ArrValues(6) = "Whoops, got an error" %> 如果試圖讀或設置下標為6的元素值,可以得到一個運行期錯誤,如圖7-7所示:
圖7-7 程序執行結果6 注意這里的錯誤類型是“runtime”(相當于語義)錯誤,而不是語法錯誤。錯誤信息顯示了錯誤所在行數和錯誤的描述,有助于我們比較容易地找到相應的錯誤。但這是一個簡單的例子,在更復雜的程序代碼中,這種錯誤可能出現在一些遍歷一些值并把它們加到一個數組中程序中。如下所示: <% Dim arrValues(5) ' to hold six elements For intLoop = 0 To intListCount ' the number of items in some list arrValues(intLoop) = Request.Form("SelectedItems")(intListCount) Next %> 這種情況下,很可能是得到了過多的列表條目,或者是數組的索引不夠,根據代碼的要求,可以判斷是那種錯誤,并且能夠通過增加數組大小來解決這個錯誤。 <% Dim arrValues(10) ' to hold eleven elements For intLoop = 0 To intListCount ' the number of items int some list arrValues(intLoop) = Request.Form("SelectedItems")(intListCount) Next %> 或者相應地設置循環的參數來解決處理這個錯誤。 <% Dim arrValues(5) ' to hold six elements IntArrayMax = intListCount If intArrayMax > 5 Then intArrayMax = 5 For intLoop = 0 To intArrayMax ' only add the first six items arrValues(intLoop) = Request.Form("SelectedItems")(intListCount) Next %> 許多其他運行期錯誤能夠使網頁運行停止,諸如一些組件或對象的實例化失敗,原因是有ProgID錯誤,或者是因為組件沒有正確安裝。在這些情況下,結果總是給出“ActiveX Cannot Create Object”錯誤提示信息,后面跟著調用Server.CreateObject方法的行號。 2. 產生錯誤結果的錯誤 上面提到,如果遇到一個使程序代碼停止的運行期錯誤,我們可能是幸運的。但是另一種情況是程序能很好地執行,好像什么也沒有發生,最后產生一個錯誤的結果。這是最難發現和解決的錯誤,因為意識不到哪里出錯了。例如,假設有一個網頁,這個網頁把用戶的生日作為日期型的值,并且單獨顯示日期元素(可以把它們作為三個條目加到一個數據庫中)。 <% ' get the value from the Request and display it datBirthdate = Request.Form("Birthdate") Response.Write "The value you entered is: " & datBirthdate & "<P>"
' get the individual date elements intDay = Day(datBirthdate) intMonth = Month(datBirthdate) intYear = Year(datBirthdate)
' and display them Response.Write "Day: " & Cstr(intDay) & "<BR>" Response.Write "Month: " & Cstr(intMonth) & "<BR>" Response.Write "Year: " & Cstr(intYear) & "<BR>" %> 圖7-8是結果,是用美國日期風格月/日/年顯示的,好像一切都沒有問題。
圖7-8 顯示生日的屏幕 然而如果輸入一個非法日期,或者讓輸入文本框空著,便得到一個運行期錯誤,如圖7-9所示:
圖7-9 錯誤提示屏幕 (1) 如果不是一位JScript專家 在尋找錯誤時,這不是一個大問題,因為我們能夠迅速發現為什么會出現錯誤。事實上網頁停止運行有助于我們跟蹤錯誤。然而意外的錯誤可能會發生。例如,用JScript重寫程序代碼,由于不是一位JScript專家,里面出現一些細小錯誤。 <% // get the value from the Request and display it var datBirthdate = new Date(Request.Form("Birthdate")); Response.Write("The value you entered is: " + datBirthdate + "<P>");
// get the individual date elements intDay = datBirthdate.getDay(); intMonth = datBirthdate.getMonth(); intYear = datBirthdate.getYear();
// and display them Response.Write("Day: " + intDay.toString() + "<BR>"); Response.Write("Month: " + intMonth.toString() + "<BR>"); Response.Write("Year: " + intYear.toString() + "<BR>"); %> 圖7-10即是運行結果,盡管程序沒有停止運行并給出運行期錯誤,還是馬上看出其中有些問題,月份不可能是0。
圖7-10 顯示生日的屏幕 問題出現的原因在于JScript的getMonth函數返回的結果為0~11范圍內的數,因此需要再加1,才能得到正確的結果。 intMonth = datBirthdate.getMonth() + 1; (2) 衍生錯誤 即使不把初始值賦給網頁去和結果比較,上面這種錯誤也可能是相當明顯的。然而,如果面對的是一個數據庫系統,并且沒有看到顯示出不正確的結果,可能不知道為什么程序不能正確地更新數據庫。更糟糕的是,如果簡單地把數值做為整型數據存入數據庫,可能直到有人試圖對這個數據查詢時才能發現這個錯誤。 現在,發現大約有十二分之一的成員出生在0月份可能會使人吃驚,并會引起一些問題。記住,不僅僅是那些1月份出生的人員存在數據庫中的信息不正確,而且每個成員都是這樣。如果有許多應用程序都能增加和修改這個數據庫中的記錄,跟蹤這個錯誤可能是艱苦的工作,特別是,不能去查找錯誤出現在哪個程序行,而是首先要找出錯誤出現在哪個應用程序中。 (3) 掌握日期的用法 在上面的程序中出現的日期型數據的錯誤不是非常明顯,不論使用都輸入什么樣的日期,程序代碼只能給出0~6之中的值,原因在于編碼中的設定,特別是從VBScript轉換到JScript時。在JScript中,getDay函數返回的周中的某一天,而不是月中的某一天,這等價于VBScript中的Weekday函數,getDay函數的返回值是0(代表星期日)到6(代表星期六)。 注意VBScript的Weekday函數返回1(代表星期日)到7(代表星期六)。 因此,在JScript中由getDate函數獲得某月的日期的正確代碼是: … // get the individual date elements intDay = datBirthdate.getDate(); intMonth = datBirthdate.getMonth() + 1; intYear = datBirthdate.getYear(); … 運行這段程序便可得到想要的結果,如圖7-11所示:
圖7-11 顯示正確生日的屏幕
7.2 各種運行期錯誤 本章前面部分展示了一些問題,包括錯誤如何出現、如何尋找錯誤和如何處理錯誤等等,F在更重要的是要掌握能夠發生不同種類的錯誤,并且如何區分這些錯誤。需要記住的是,如果知道了到哪里去找和尋找什么,調試則是比較容易的。在本章最后,將介紹錯誤確實出現時如何捕獲錯誤,并且要盡可能早地阻止錯誤的發生。 在學習這些內容之前,首先要深入了解一下在某階段肯定會遇到的不同類型的運行期和語義錯誤,主要討論以下內容: · 邏輯錯誤。 · 腳本運行期錯誤。 · ASP和SSI運行期錯誤。 · 客戶端腳本錯誤。
7.2.1 邏輯錯誤 邏輯錯誤在腳本中通常難于跟蹤,因為這些錯誤常常是產生錯誤的結果而不中止網頁運行。通常只有一些值出現超出邊界的情況,如在前面數組實例中看到的那樣,錯誤才顯現出來。 然而,在錯誤和調試環境中,一種算法并不像數學課上所學的那樣復雜。從計算的角度看,算法只是指一段能完成某個任務(通常返回某個結果)的程序。 1. 數值超界(數據溢出) 典型的邏輯錯誤一般涉及到數值,或者是涉及數據溢出等。例如,如果有名為image1.gif、image2.gif等一系列圖像,編寫以下一段程序隨機挑選一幅圖像用以顯示: <% ' create a random number between 1 and 5 intRandom = CInt(Rnd() * 5) +1 %> <IMG SRC="<% = "image” & CStr(intRandom) & ".gif" %>"> 在網頁中創建<IMG>元素用以指定隨機選中的圖像,例如: <IMG SRC=http://www.okasp.com/techinfo/"image3.gif"> 然而,如果碰巧這段程序產生的結果是image6.gif文件。在這種情況下,如果本來僅希望得到在1~5中的一個結果,網頁會是一個破碎的圖像符號。原因是VBScript中的CInt函數將值取整到最近的整數值。為了舍去小數部分,需要使用Int或者Fix函數代替CInt。 2. 運算符號的優先級 其他類型的邏輯錯誤有按指令計算而出現的錯誤,例如想用除法時采用了乘法會產生錯誤的結果。而由于程序中數學運算符號的運行順序或優先級,會引起一些更難發現的錯誤,例如,下面這段程序可能會產生不正確的結果。 intResult = intValue1 * intValue2 + intValue3 因為乘法比加法有較高的運算優先級,所以先進行計算。但是如果想把第一個數和后兩個數的和相乘,必須用括號來改變這種缺省的運算優先權。 intResult = intValue1 * (intValue2 + intValue3) 在VBScript 5.0文檔中的VBScript Basics| VBScript Operators中,給出了所有腳本運行符號的優先級表。對于JScript,在JScript Tutorial|JScript Basic|JScript Operators下也可找到相應的優先級表。然而需要記住的最基本原則是:乘、除法優先于加、減法。 3. 管理和格式化字符串數據 從計算意義上考慮,具有計算功能的任何結構或函數都可看作一種算法。例如,可以從數據庫中取值構成一個字符串,代表顧客的名字。這里不涉及如何從數據庫中提取數據(本書的后面部分進行討論)。下面程序的功能是字符串連接。 strTitle = {get from database} strFirstName = {get from database} strMiddleInitial = {get from database} strLastName = {get from database} strOther = {get from database}
strPrint = strTitle & ". " & strFristName & " " & strMiddleInitial _ & ". " & strstrLastName & " " & strOther 運行這段程序可以得到如下結果: Ms. Janet C. Clarke MBNA.BSc.MechEng. 但不是每個人都和“Janet”一樣,有一個中間名字。并且許多人可能沒有頭銜,所以可能僅僅得到: . Alex . Homer 這當然不是一個能引起腳本不能運行或者產生運行期錯誤的致命錯誤。然而,對于用戶來說,提供這樣的腳本是不可接受的。最好程序能在輸出字符串之前檢查名字的每一部分。 … strPrint = "" If Len(strTitle) Then strPrint = strPrint & strTitle & ". " If Len(strFirstName) Then strPrint = strPrint & strFirstName & " " If Len(strMiddleInitial) Then strPrint = strPrint & strMiddleInitial & ". " If Len(strLastName) Then strPrint = strPrint & strLastName If Len(strOther) Then strPrint = strPrint & " " & strOther 上面這段程序保證了空格和小數點僅加在名字中有值的地方。如果僅給strOther字符串賦值,而對其他都不賦值的話,將在開始處得到一個空格。然而出現這種情況的可能性非常小。如果有姓的話,通過僅添加“Other”部分可以防止這種錯誤的發生。
… strPrint = "" If Len(strTitle) Then strPrint = strPrint & strTitle & ". " If Len(strFirstName) Then strPrint = strPrint & strFirstName & " " If Len(strMiddleInitial) Then strPrint = strPrint & strMiddleInitial & ". " If Len(strLastName) Then strPrint = strPrint & strLastName If Len(strOther) Then strPrint = strPrint & " " & strOther End If 最壞的情況是結果為一個空字符串,可以檢查這種可能性并中止打印。 … If Len(strPrint) = 0 Then Response.Clear Response.End End If
7.2.2 腳本運行期錯誤 使用一個不存在的函數,或者破壞了腳本語言使用的規則,會出現腳本運行期錯誤。許多錯誤是語法錯誤(本章前面討論過的),但是許多錯誤是由于所賦的值和函數參數的要求不一致引起的。例如,用一個窗體收集來自用戶的日期,并存入數據庫中,或者用其他方式進行處理。為了確定日期是有效的,在把數據插入數據庫之前使用CDate函數: <% strDate = Request.Form("TheDate") datDate = CDate(strDate) … 如果用戶在填表時出現了差錯,程序便會產生一個腳本錯誤,如圖7-12所示:
圖7-12 出錯信息的屏幕 查看錯誤信息,可以發現錯誤是由執行程序代碼的腳本引擎產生的。錯誤號用十六進制顯示出來,它是由VBScript錯誤號和十六進制數0x800A0000相加得到的(見第4章),上例中VBScript錯誤號是十六進制0xD,或者十進制數的13。 大多數微軟技術(包括ASP)返回的錯誤號是由8位十六進制數組成的。第一位字符總是8,表明這個狀態信息是服務器錯誤信息。后面跟著2位0,然后是服務代碼。對VBScript和JScript錯誤,服務代碼總是“A”,最后4位字符是用十六進制數表示的錯誤號。 如果查看一下VBScript文檔,你會發現13號錯誤是“Type Mismatch”錯誤。當然,我們從ASP錯誤頁中顯示的錯誤描述中已經知道了這一點。然而,在本章后面我們將要看到,在錯誤處理技術中,得到錯誤號是非常有用的。 注意,在錯誤信息顯示窗口中,顯示的是服務器對錯誤的反饋信息。HTTP狀態代碼為500.100,屬于“Internal Server Error”。在第4章,討論ASP定制錯誤網頁的工作方式時,我們發現這種錯誤常常因為載入了錯誤網頁。本章后面,將會看到在網頁中如何處理這些錯誤。
7.2.3 ASP和SSI的運行期錯誤 腳本錯誤是由正在使用的腳本引擎發現的,然而ASP DLL和SSI DLL也能發現腳本錯誤,盡管它們與使用的腳本引擎無關。典型的SSI例子是在#include指令中給文件一個錯誤的名字或路徑。錯誤是由SSI DLL或ASP發現的,而不是由腳本引擎發現?煽吹酱藭r錯誤類型是“Active Server Pages”,ASP內部錯誤代碼是“ASP 0126”,如圖7-13所示,然而在這種情況下,錯誤號是4005,指出了這是一種SSI DLL(ssinc.dll)定義的特殊錯誤。
圖7-13 出錯信息的屏幕 ASP錯誤代碼總覽 對于在ASP DLL中造成失敗的錯誤,表7-1是返回的錯誤代碼。當這類錯誤發生時,你可以在ASPError對象的ASPCode屬性中找到這些錯誤代碼。 表7-1 ASP錯誤代碼 錯誤代碼 錯誤消息和擴展信息
ASP0100 Out of Memory(內存溢出)
ASP0101 Unexpected error(函數返回exception_name)
ASP0102 Expecting string input(期待字符串輸入)
ASP0103 Expecting numeric input(期待數字輸入)
ASP0104 Operating not allowed(操作不允許)
ASP0105 Index out of range(數組下標溢出)
ASP0106 Type Mismatch(數據類型不匹配)
ASP0107 Stack Overflow(處理的數據量超過了允許的范圍)
ASP0115 Unexpected error(出現在外部對象中的可捕獲的錯誤exception_name,腳本不能繼續運行)
ASP0177 Server.CreateObject Falied(無效的ProgID)
ASP0190 Unexpected error(當釋放外部對象時,出現的可捕獲的錯誤)
ASP0191 Unexpected error(當外部對象的OnStartPage方法中出現的可捕獲的錯誤)
ASP0192 Unexpected error(在外部對象的OnEndPage方法中出現的可捕獲的錯誤)
ASP0193 OnStartPage Failed(在外部對象OnStartPage方法中出現錯誤)
ASP0194 OnEndPage Failed(在外部對象的OnEndPage方法中出現錯誤)
ASP0240 Script Engine Exception(腳本引擎從object_name拋出異常exception_name)
ASP0241 CreateObject Exception(object_name的CreateObject方法所導致的異常exception_name)
ASP0242 Query OnStartPage Interface Exception(查詢對象object_name的OnStartPage或OnEndPage方法所導致的異常exception_name)
ASP錯誤通常僅當組件有問題或服務器本身有問題時才出現。最常見是使用Server.CreateObject時的ASP 0177錯誤和嚴重的ASP 0115錯誤。ASP 0115錯誤通常表示組件程序代碼中發生的錯誤,而ASP 0177錯誤通常是由不能正確安裝組件引起的或者由我們指定的ProgID字符串的錯誤引起的。
|