错误
本章前面部分展示了一些问题,包括错误如何出现、如何寻找错误和如何处理错误等等。现在更重要的是要掌握能够发生不同种类的错误,并且如何区分这些错误。需要记住的是,如果知道了到哪里去找和寻找什么,调试则是比较容易的。在本章最后,将介绍错误确实出现时如何捕获错误,并且要尽可能早地阻止错误的发生。
在学习这些内容之前,首先要深入了解一下在某阶段肯定会遇到的不同类型的运行期和语义错误,主要讨论以下内容:
· 逻辑错误。
· 脚本运行期错误。
· 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.163design.net/a/j/"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所示:
查看错误信息,可以发现错误是由执行程序代码的脚本引擎产生的。错误号用十六进制显示出来,它是由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发现的,而不是由脚本引擎发现。可看到此时错误类型是“Active Server Pages”,ASP内部错误代码是“ASP 0126”,如图7-13所示,然而在这种情况下,错误号是4005,指出了这是一种SSI DLL(ssinc.dll)定义的特殊错误。
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字符串的错误引起的。
7.2.4 客户端脚本错误
到目前为止,我们已了解了来自ASP的错误。然而ASP也经常用于创建包含客户端脚本的网页。如果包含客户端代码的<SCRIPT>元素没有被设置成RUNAT="SERVER"属性,ASP将不考虑服务器,而把网页信息不加改变地传送到客户端。
因此,如果打开了一个ASP网页,并且显示的是一个浏览器错误对话框,就不应该在服务器端寻找ASP程序代码的错误。浏览器看不到ASP程序代码,所以不能识别任何错误,如果有一个对话框出现在客户端,那么在客户端代码中必定有一个错误。
1. 语法错误
如果在网页中的客户端程序代码有语法错误的话,当脚本下载到客户端,浏览器便会出现相应的错误。尽管网页中内容仍可正常载入(除非由这些客户端脚本代码动态装入),但网页停止执行。用户将看到一个包含错误细节的对话框,或者是一个指示网页包含错误的状态条消息。
现代浏览器趋向于隐藏网页脚本错误的细节,而仅在状态条上显示一个小的错误图标。在IE 4.0和IE 5.0中,正常的错误对话框可以通过Internet Options对话框的Advanced页进行设置来激活,如图7-14所示:
处理脚本程序代码中的客户端错误和在服务器端相似,并且通常会更容易些,因为经常可以直接从服务器目录中通过双击来下载网页。一般不需要通过Web服务器和HTTP获得网页来观察浏览器中的结果,其中的唯一不同是一些服务器交互由客户端脚本来完成,如使用RDS的数据绑定或者动态装入。
2. 运行期或语义错误
在客户端脚本中,通常可能会遇到语法错误,也会经常遇到运行期或语义错误。事实上,在客户端,这种现象是很普遍的。因为在客户端不能像服务器端那样对脚本的环境进行控制,不能肯定用户在他们的机器上正运行什么,实际上在服务器上仅能从一些组件如Browser Capabilities中得到大概情况。
所以,使用客户端对象或特殊版本的脚本语言和属性的脚本程序很可能不能正常工作。尽管如此,处理客户端错误和处理服务器端错误是差不多的。
3. 在服务器上创建的客户端程序代码
在错误发生时,作为“客户端对话框对应于ASP错误页面”规则(关于出错的地方)的一个特别的例外是,使用ASP程序代码在服务器上动态地创建客户端程序代码。例如,可能想在ASP中进行求值运算,然后把数据传给运行在客户端的脚本代码,可能最容易的方法是把数据作为一个变量插入脚本代码中:
<%
' get the name of our server from the ServerVariables collection
strServerNameInASP = Request.ServerVariables("SERVER_NAME")
%>
<SCRIPT LANGUAGE="JScript" RUNAT="CLIENT">
<!-- hide code from older browsers
var strServerName = "<% = strServerNameInASP %>";
…
alert('Server name is: ' + strServerName);
…
// stop hiding code
-->
</SCRIPT>
在客户端,在ASP处理这个页面之后,将得到的是:
<SCRIPT LANGUAGE="JScript" RUNAT="CLIENT">
<!-- hide code from older browsers
var strServerName = "WROXBOX";
…
alert('Server name is: ' + strServerName);
…
// stop hiding code
-->
</SCRIPT>
可以忽略RUNAT="CLIENT"属性,但是加上这一项可以使得在查看运行代码的ASP网页时更加清楚。
这样,如果在某个位置想把服务器端数据库中的数据加入到一个客户端数组中,可以采用下面的程序实现:
<SCRIPT LANGUAGE="JScript" RUNAT="CLIENT">
<!-- hide code from older browsers
var arrBooks = new Array(10) //highest available index will be
<% ' start of ASP processing
intIndex = 0
Do While { not at the end of some recordset }
strTitle = { get title from database record }
Response.Write "arrBooks[" & CInt(intIndex) & "] = '" _
& strTitle & "'; " & vbCrlf
intIndex = intIndex +1
{ move to next record in database }
Loop
…
do something here on the client with the array of book titles
…
// stop hiding code
-->
</SCRIPT>
这段服务器端ASP程序代码产生的客户端代码,在客户端运行时创建书名标题数组。同时产生的客户端脚本错误出现在浏览器的错误对话框中。错误的原因是以arrBooks命名的数组是由JavaScript代码运行在客户端时创建的,仅能接受9个书名;而服务器端代码能很可能产生多于9个的书名,具体多少由源数据库中的记录数来决定。这相当于如下客户端代码:
<SCRIPT LANGUAGE="JScript" RUNAT="CLIENT">
<!-- hide code from older browsers
var arrBooks = new Array(10) //highest available index will be
arrBooks[0] = 'Instant JavaScript';
arrBooks[1] = 'Professional ASP 3.0 Programming';
arrBooks[2] = 'ADO 2.5 Programmers Reference';
…
etc
…
arrBooks[9] = 'ASP Techniques for Webmasters';
arrBooks[10] = 'ASP Programmers Reference'; // <- client-side error occurs here
arrBooks[11] = 'ADSI CDO Programming';
arrBooks[12] = 'Professional MTS and MSMQ Programming';
…
do something here on the client with the array of book titles
…
// stop hiding code
-->
</SCRIPT>
这个页面只有经过修正之后才能正常工作,可以通过增加数组大小,也可以通过控制来自数据库的记录数使其正常工作。