直播中
圖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ū)ο蟮膶嵗?,原因是有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)
' and display them
Response.Write "Day: " & Cstr(intDay) & "<BR>"
Response.Write "Month: " & Cstr(intMonth) & "<BR>"
Response.Write "Year: " & Cstr(intYear) & "<BR>"
%>
圖7-8是結(jié)果,是用美國日期風(fēng)格月/日/年顯示的,好像一切都沒有問題。
圖7-8 顯示生日的屏幕
然而如果輸入一個非法日期,或者讓輸入文本框空著,便得到一個運(yùn)行期錯誤,如圖7-9所示:
圖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();
// and display them
Response.Write("Day: " + intDay.toString() + "<BR>");
Response.Write("Month: " + intMonth.toString() + "<BR>");
Response.Write("Year: " + intYear.toString() + "<BR>");
%>
圖7-10即是運(yùn)行結(jié)果,盡管程序沒有停止運(yùn)行并給出運(yùn)行期錯誤,還是馬上看出其中有些問題,月份不可能是0。
圖7-10 顯示生日的屏幕
問題出現(xiàn)的原因在于JScript的getMonth函數(shù)返回的結(jié)果為0~11范圍內(nèi)的數(shù),因此需要再加1,才能得到正確的結(jié)果。
intMonth = datBirthdate.getMonth() + 1;
(2) 衍生錯誤
即使不把初始值賦給網(wǎng)頁去和結(jié)果比較,上面這種錯誤也可能是相當(dāng)明顯的。然而,如果面對的是一個數(shù)據(jù)庫系統(tǒng),并且沒有看到顯示出不正確的結(jié)果,可能不知道為什么程序不能正確地更新數(shù)據(jù)庫。更糟糕的是,如果簡單地把數(shù)值做為整型數(shù)據(jù)存入數(shù)據(jù)庫,可能直到有人試圖對這個數(shù)據(jù)查詢時才能發(fā)現(xiàn)這個錯誤。
現(xiàn)在,發(fā)現(xiàn)大約有十二分之一的成員出生在0月份可能會使人吃驚,并會引起一些問題。記住,不僅僅是那些1月份出生的人員存在數(shù)據(jù)庫中的信息不正確,而且每個成員都是這樣。如果有許多應(yīng)用程序都能增加和修改這個數(shù)據(jù)庫中的記錄,跟蹤這個錯誤可能是艱苦的工作,特別是,不能去查找錯誤出現(xiàn)在哪個程序行,而是首先要找出錯誤出現(xiàn)在哪個應(yīng)用程序中。
(3) 掌握日期的用法
在上面的程序中出現(xiàn)的日期型數(shù)據(jù)的錯誤不是非常明顯,不論使用都輸入什么樣的日期,程序代碼只能給出0~6之中的值,原因在于編碼中的設(shè)定,特別是從VBScript轉(zhuǎn)換到JScript時。在JScript中,getDay函數(shù)返回的周中的某一天,而不是月中的某一天,這等價于VBScript中的Weekday函數(shù),getDay函數(shù)的返回值是0(代表星期日)到6(代表星期六)。
注意VBScript的Weekday函數(shù)返回1(代表星期日)到7(代表星期六)。
因此,在JScript中由getDate函數(shù)獲得某月的日期的正確代碼是:
…
// get the individual date elements
intDay = datBirthdate.getDate();
intMonth = datBirthdate.getMonth() + 1;
intYear = datBirthdate.getYear();
…
運(yùn)行這段程序便可得到想要的結(jié)果,如圖7-11所示:
圖7-11 顯示正確生日的屏幕
7.2 各種運(yùn)行期錯誤
本章前面部分展示了一些問題,包括錯誤如何出現(xiàn)、如何尋找錯誤和如何處理錯誤等等。現(xiàn)在更重要的是要掌握能夠發(fā)生不同種類的錯誤,并且如何區(qū)分這些錯誤。需要記住的是,如果知道了到哪里去找和尋找什么,調(diào)試則是比較容易的。在本章最后,將介紹錯誤確實出現(xiàn)時如何捕獲錯誤,并且要盡可能早地阻止錯誤的發(fā)生。
在學(xué)習(xí)這些內(nèi)容之前,首先要深入了解一下在某階段肯定會遇到的不同類型的運(yùn)行期和語義錯誤,主要討論以下內(nèi)容:
· 邏輯錯誤。
· 腳本運(yùn)行期錯誤。
· ASP和SSI運(yùn)行期錯誤。
· 客戶端腳本錯誤。
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ù)點僅加在名字中有值的地方。如果僅給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
7.2.2 腳本運(yùn)行期錯誤
使用一個不存在的函數(shù),或者破壞了腳本語言使用的規(guī)則,會出現(xiàn)腳本運(yùn)行期錯誤。許多錯誤是語法錯誤(本章前面討論過的),但是許多錯誤是由于所賦的值和函數(shù)參數(shù)的要求不一致引起的。例如,用一個窗體收集來自用戶的日期,并存入數(shù)據(jù)庫中,或者用其他方式進(jìn)行處理。為了確定日期是有效的,在把數(shù)據(jù)插入數(shù)據(jù)庫之前使用CDate函數(shù):
<%
strDate = Request.Form("TheDate")
datDate = CDate(strDate)
…
如果用戶在填表時出現(xiàn)了差錯,程序便會產(chǎn)生一個腳本錯誤,如圖7-12所示:
圖7-12 出錯信息的屏幕
查看錯誤信息,可以發(fā)現(xiàn)錯誤是由執(zhí)行程序代碼的腳本引擎產(chǎn)生的。錯誤號用十六進(jìn)制顯示出來,它是由VBScript錯誤號和十六進(jìn)制數(shù)0x800A0000相加得到的(見第4章),上例中VBScript錯誤號是十六進(jìn)制0xD,或者十進(jìn)制數(shù)的13。
大多數(shù)微軟技術(shù)(包括ASP)返回的錯誤號是由8位十六進(jìn)制數(shù)組成的。第一位字符總是8,表明這個狀態(tài)信息是服務(wù)器錯誤信息。后面跟著2位0,然后是服務(wù)代碼。對VBScript和JScript錯誤,服務(wù)代碼總是“A”,最后4位字符是用十六進(jìn)制數(shù)表示的錯誤號。
如果查看一下VBScript文檔,你會發(fā)現(xiàn)13號錯誤是“Type Mismatch”錯誤。當(dāng)然,我們從ASP錯誤頁中顯示的錯誤描述中已經(jīng)知道了這一點。然而,在本章后面我們將要看到,在錯誤處理技術(shù)中,得到錯誤號是非常有用的。
注意,在錯誤信息顯示窗口中,顯示的是服務(wù)器對錯誤的反饋信息。HTTP狀態(tài)代碼為500.100,屬于“Internal Server Error”。在第4章,討論ASP定制錯誤網(wǎng)頁的工作方式時,我們發(fā)現(xiàn)這種錯誤常常因為載入了錯誤網(wǎng)頁。本章后面,將會看到在網(wǎng)頁中如何處理這些錯誤。
7.2.3 ASP和SSI的運(yùn)行期錯誤
腳本錯誤是由正在使用的腳本引擎發(fā)現(xiàn)的,然而ASP DLL和SSI DLL也能發(fā)現(xiàn)腳本錯誤,盡管它們與使用的腳本引擎無關(guān)。典型的SSI例子是在#include指令中給文件一個錯誤的名字或路徑。錯誤是由SSI DLL或ASP發(fā)現(xiàn)的,而不是由腳本引擎發(fā)現(xiàn)??煽吹酱藭r錯誤類型是“Active Server Pages”,ASP內(nèi)部錯誤代碼是“ASP 0126”,如圖7-13所示,然而在這種情況下,錯誤號是4005,指出了這是一種SSI DLL(ssinc.dll)定義的特殊錯誤。
圖7-13 出錯信息的屏幕
ASP錯誤代碼總覽
對于在ASP DLL中造成失敗的錯誤,表7-1是返回的錯誤代碼。當(dāng)這類錯誤發(fā)生時,你可以在ASPError對象的ASPCode屬性中找到這些錯誤代碼。
表7-1 ASP錯誤代碼
錯誤代碼
錯誤消息和擴(kuò)展信息
ASP0100
Out of Memory(內(nèi)存溢出)
ASP0101
Unexpected error(函數(shù)返回exception_name)
ASP0102
Expecting string input(期待字符串輸入)
ASP0103
Expecting numeric input(期待數(shù)字輸入)
ASP0104
Operating not allowed(操作不允許)
ASP0105
Index out of range(數(shù)組下標(biāo)溢出)
ASP0106
Type Mismatch(數(shù)據(jù)類型不匹配)
ASP0107
Stack Overflow(處理的數(shù)據(jù)量超過了允許的范圍)
ASP0115
Unexpected error(出現(xiàn)在外部對象中的可捕獲的錯誤exception_name,腳本不能繼續(xù)運(yùn)行)
ASP0177
Server.CreateObject Falied(無效的ProgID)
ASP0190
Unexpected error(當(dāng)釋放外部對象時,出現(xiàn)的可捕獲的錯誤)
ASP0191
Unexpected error(當(dāng)外部對象的OnStartPage方法中出現(xiàn)的可捕獲的錯誤)
ASP0192
Unexpected error(在外部對象的OnEndPage方法中出現(xiàn)的可捕獲的錯誤)
ASP0193
OnStartPage Failed(在外部對象OnStartPage方法中出現(xiàn)錯誤)
ASP0194
OnEndPage Failed(在外部對象的OnEndPage方法中出現(xiàn)錯誤)
ASP0240
Script Engine Exception(腳本引擎從object_name拋出異常exception_name)
ASP0241
CreateObject Exception(object_name的CreateObject方法所導(dǎo)致的異常exception_name)
ASP0242
Query OnStartPage Interface Exception(查詢對象object_name的OnStartPage或OnEndPage方法所導(dǎo)致的異常exception_name)
ASP錯誤通常僅當(dāng)組件有問題或服務(wù)器本身有問題時才出現(xiàn)。最常見是使用Server.CreateObject時的ASP 0177錯誤和嚴(yán)重的ASP 0115錯誤。ASP 0115錯誤通常表示組件程序代碼中發(fā)生的錯誤,而ASP 0177錯誤通常是由不能正確安裝組件引起的或者由我們指定的ProgID字符串的錯誤引起的。