7.1.2 語義或“運(yùn)行期”錯誤 語法錯誤的發(fā)現(xiàn)和處理是令人煩惱的,但在編程中會遇到一些真正“令人興奮”的另一類型的錯誤——語義錯誤(semantic error)或稱“運(yùn)行期”錯誤(runtime error)。這類錯誤僅當(dāng)運(yùn)行一個腳本代碼或其他程序時才會發(fā)現(xiàn)。換句話說完整有效的代碼已經(jīng)通過解釋器或編譯器的解釋或編譯,在執(zhí)行時產(chǎn)生了錯誤。術(shù)語“運(yùn)行期錯誤”通過是指語義錯誤的結(jié)果,也就是說這類錯誤存在于代碼的語義中,當(dāng)代碼運(yùn)行時它們才變成可見的。 這種區(qū)別來自于這種事實:程序編譯器或解釋器在處理程序代碼之前必須建立一種內(nèi)部代碼的描述,涉及多種結(jié)構(gòu)開頭和結(jié)尾的匹配,以便標(biāo)明每種結(jié)構(gòu)包含什么內(nèi)容,然后分析每個句子,以便知道如何執(zhí)行這個句子。例如,如果在程序代碼中有一個If Then … Else … End If 結(jié)構(gòu),解釋器或編譯器做的第一步工作就是分析哪些語句在“Then”的部分,哪些在“Else”部分。這一步的目的是,在對結(jié)構(gòu)中的If條件進(jìn)行測試之后,可以決定該到哪個分支去執(zhí)行。 編譯器(諸如在編程語言像Visual Basic和C++中見到的那種)和解釋器(諸如用于像VBScript和JScript那樣的腳本語言的解釋器)之間真正區(qū)別在于:編譯器不試圖運(yùn)行程序代碼,而是在對源程序進(jìn)行兩次預(yù)處理后,形成二進(jìn)制指令或符號代碼,并形成一個.exe文件或.dll文件。解釋器不含有代碼的文件,而是在運(yùn)行時逐步執(zhí)行。 1. 使運(yùn)行停止的錯誤 如果程序中含有一個語義錯誤,通常在運(yùn)行時可得到提示。如果幸運(yùn)的話,當(dāng)錯誤發(fā)生時,程序會停止,這樣可以容易地找出錯誤所在。例如,下面這段程序定義了一個有六個元素的數(shù)組。 <% Dim arrValues(5) 'to hold six elements, indexed from 0 to 5 ArrValues(6) = "Whoops, got an error" %> 如果試圖讀或設(shè)置下標(biāo)為6的元素值,可以得到一個運(yùn)行期錯誤,如圖7-7所示:
圖7-7 程序執(zhí)行結(jié)果6 注意這里的錯誤類型是“runtime”(相當(dāng)于語義)錯誤,而不是語法錯誤。錯誤信息顯示了錯誤所在行數(shù)和錯誤的描述,有助于我們比較容易地找到相應(yīng)的錯誤。但這是一個簡單的例子,在更復(fù)雜的程序代碼中,這種錯誤可能出現(xiàn)在一些遍歷一些值并把它們加到一個數(shù)組中程序中。如下所示: <% 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 %> 這種情況下,很可能是得到了過多的列表條目,或者是數(shù)組的索引不夠,根據(jù)代碼的要求,可以判斷是那種錯誤,并且能夠通過增加數(shù)組大小來解決這個錯誤。 <% 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 %> 或者相應(yīng)地設(shè)置循環(huán)的參數(shù)來解決處理這個錯誤。 <% 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 %> 許多其他運(yùn)行期錯誤能夠使網(wǎng)頁運(yùn)行停止,諸如一些組件或?qū)ο蟮膶嵗?,原因是?a >PRogID錯誤,或者是因為組件沒有正確安裝。在這些情況下,結(jié)果總是給出“ActiveX Cannot Create Object”錯誤提示信息,后面跟著調(diào)用Server.CreateObject方法的行號。 2. 產(chǎn)生錯誤結(jié)果的錯誤 上面提到,如果遇到一個使程序代碼停止的運(yùn)行期錯誤,我們可能是幸運(yùn)的。但是另一種情況是程序能很好地執(zhí)行,好像什么也沒有發(fā)生,最后產(chǎn)生一個錯誤的結(jié)果。這是最難發(fā)現(xiàn)和解決的錯誤,因為意識不到哪里出錯了。例如,假設(shè)有一個網(wǎng)頁,這個網(wǎng)頁把用戶的生日作為日期型的值,并且單獨(dú)顯示日期元素(可以把它們作為三個條目加到一個數(shù)據(jù)庫中)。 <% ' 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)
圖7-9 錯誤提示屏幕 (1) 如果不是一位JScript專家 在尋找錯誤時,這不是一個大問題,因為我們能夠迅速發(fā)現(xiàn)為什么會出現(xiàn)錯誤。事實上網(wǎng)頁停止運(yùn)行有助于我們跟蹤錯誤。然而意外的錯誤可能會發(fā)生。例如,用JScript重寫程序代碼,由于不是一位JScript專家,里面出現(xiàn)一些細(xì)小錯誤。 <% // 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();
7.2.1 邏輯錯誤 邏輯錯誤在腳本中通常難于跟蹤,因為這些錯誤常常是產(chǎn)生錯誤的結(jié)果而不中止網(wǎng)頁運(yùn)行。通常只有一些值出現(xiàn)超出邊界的情況,如在前面數(shù)組實例中看到的那樣,錯誤才顯現(xiàn)出來。 然而,在錯誤和調(diào)試環(huán)境中,一種算法并不像數(shù)學(xué)課上所學(xué)的那樣復(fù)雜。從計算的角度看,算法只是指一段能完成某個任務(wù)(通常返回某個結(jié)果)的程序。 1. 數(shù)值超界(數(shù)據(jù)溢出) 典型的邏輯錯誤一般涉及到數(shù)值,或者是涉及數(shù)據(jù)溢出等。例如,如果有名為image1.gif、image2.gif等一系列圖像,編寫以下一段程序隨機(jī)挑選一幅圖像用以顯示: <% ' create a random number between 1 and 5 intRandom = CInt(Rnd() * 5) +1 %> <IMG SRC="<% = "image” & CStr(intRandom) & ".gif" %>"> 在網(wǎng)頁中創(chuàng)建<IMG>元素用以指定隨機(jī)選中的圖像,例如: <IMG SRC="image3.gif"> 然而,如果碰巧這段程序產(chǎn)生的結(jié)果是image6.gif文件。在這種情況下,如果本來僅希望得到在1~5中的一個結(jié)果,網(wǎng)頁會是一個破碎的圖像符號。原因是VBScript中的CInt函數(shù)將值取整到最近的整數(shù)值。為了舍去小數(shù)部分,需要使用Int或者Fix函數(shù)代替CInt。 2. 運(yùn)算符號的優(yōu)先級 其他類型的邏輯錯誤有按指令計算而出現(xiàn)的錯誤,例如想用除法時采用了乘法會產(chǎn)生錯誤的結(jié)果。而由于程序中數(shù)學(xué)運(yùn)算符號的運(yùn)行順序或優(yōu)先級,會引起一些更難發(fā)現(xiàn)的錯誤,例如,下面這段程序可能會產(chǎn)生不正確的結(jié)果。 intResult = intValue1 * intValue2 + intValue3 因為乘法比加法有較高的運(yùn)算優(yōu)先級,所以先進(jìn)行計算。但是如果想把第一個數(shù)和后兩個數(shù)的和相乘,必須用括號來改變這種缺省的運(yùn)算優(yōu)先權(quán)。 intResult = intValue1 * (intValue2 + intValue3) 在VBScript 5.0文檔中的VBScript Basics| VBScript Operators中,給出了所有腳本運(yùn)行符號的優(yōu)先級表。對于JScript,在JScript Tutorial|JScript Basic|JScript Operators下也可找到相應(yīng)的優(yōu)先級表。然而需要記住的最基本原則是:乘、除法優(yōu)先于加、減法。 3. 管理和格式化字符串?dāng)?shù)據(jù) 從計算意義上考慮,具有計算功能的任何結(jié)構(gòu)或函數(shù)都可看作一種算法。例如,可以從數(shù)據(jù)庫中取值構(gòu)成一個字符串,代表顧客的名字。這里不涉及如何從數(shù)據(jù)庫中提取數(shù)據(jù)(本書的后面部分進(jìn)行討論)。下面程序的功能是字符串連接。 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 運(yùn)行這段程序可以得到如下結(jié)果: Ms. Janet C. Clarke MBNA.BSc.MechEng. 但不是每個人都和“Janet”一樣,有一個中間名字。并且許多人可能沒有頭銜,所以可能僅僅得到: . Alex . Homer 這當(dāng)然不是一個能引起腳本不能運(yùn)行或者產(chǎn)生運(yùn)行期錯誤的致命錯誤。然而,對于用戶來說,提供這樣的腳本是不可接受的。最好程序能在輸出字符串之前檢查名字的每一部分。 … 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 上面這段程序保證了空格和小數(shù)點(diǎn)僅加在名字中有值的地方。如果僅給strOther字符串賦值,而對其他都不賦值的話,將在開始處得到一個空格。然而出現(xiàn)這種情況的可能性非常小。如果有姓的話,通過僅添加“Other”部分可以防止這種錯誤的發(fā)生。
… 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 最壞的情況是結(jié)果為一個空字符串,可以檢查這種可能性并中止打印。 … If Len(strPrint) = 0 Then Response.Clear Response.End End If