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="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
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> 这个页面只有经过修正之后才能正常工作,可以通过增加数组大小,也可以通过控制来自数据库的记录数使其正常工作。