图7-14 Advanced页面设置屏幕 处理脚本程序代码中的客户端错误和在服务器端相似,并且通常会更容易些,因为经常可以直接从服务器目录中通过双击来下载网页。一般不需要通过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> 这个页面只有经过修正之后才能正常工作,可以通过增加数组大小,也可以通过控制来自数据库的记录数使其正常工作。
良好的编程习惯 在编程中避免出现错误是和良好的编程习惯相关的,这里有许多工作我们要做,以减少把错误带进网页的可能性。可能有些人因采用某个技术而走向极端,甚至一定程度上在某个特殊问题上因书生气十足而引入了更多的错误。当然编程人员也不可能采用了这里列出的所有技术。 要考虑的主要内容是: · 代码的格式化和缩进编排。 · 变量显式表明。 · 变量转换为合适的数据类型。 · 使用有意义的变量命名约定。 · 封装脚本。 · 注意潜在的错误情况。 1. 代码的格式化和缩进编排 许多VBScript编程员懒于格式化编排其书写的程序。尽管这并不阻碍程序运行,但这使得查找何处产生了错误变得困难。例如,在前面我们看到的程序中,丢失了一个End If,由于嵌套结构的缩进,错误在哪里是相当明显的: objCounters.Remove strCounterName Response.Write "Removed counter " & strCounterName <--- missing 'End If' should be here End If End If %> 如果程序看起来像下面所示的那样,寻找错误将不是一件易事: <% if Len(Request.Form("cmdSet")) then strCounterName=Request.Form("lstSet") strNewValue=Request.Form("txtSet") if isnumeric (strnewvalue) then intNewValue =cint(strNewValue) objCounters.Set strCounterName, intNewValue Response.write "Set counter" & strCounterName &" to " & strNewValue
else Response.write strNewValue &" is not a valid number" If Len ( Request.Form ("cmdRemove")) then StrCounterName = Request.Form("lstRemove")
objCounters.Remove strCounterName Response.write "Removed counter "& strCounterName end if End IF %> 2. 显式表明变量 VBScript支持Option Explicit语句。在一个脚本页面的开头插入Option Explicit语句时,可以避免使用没有用Dim命令(或用于动态数组的ReDim)定义的变量。似乎不需要这么做,因为脚本语言允许通过给一个变量赋值来创建一个需要的变量。然而用Option Explicit进行定义有助于避免错误,特别是那些难以发现的引起脚本产生不正确结果的逻辑错误。 例如,编写如下程序: <% ' get value for calculation strSalesTotal = Request.Form("SalesTotal") curSalesTotal = CCur(strSalesTotal) sngCommissionPercent = 2.5
' calculate commission payment sngCommission = curSalesTotal * (sngComissionPercent /100) %> 运行这段程序不会产生错误(当然,除非用户给销售合计值赋了非法的值)。然而这段程序总是会产生0的结果,因为在程序的最后一行中sngCommissionPercent变量名拼写错了。脚本解释器将产生一个新的变量名(叫作sngComissionPercent),由于没有赋值,在数学计算时返回值总为0。 为了防止这种错误,仅需在程序开头增加Option Explicit语句。 <% Option Explicit Dim strSalesTotal Dim curSalesTotal Dim sngCommissionPercent
' get value for calculation strSalesTotal = Request.Form("SalesTotal") curSalesTotal = CCur(strSalesTotal) sngCommissionPercent = 2.5
图7-15 显示的错误信息 在JScript中引用一个没有声明的变量将返回一个“Undefined”信息,并且在试图使用变量之前,能够检测到这种情况。 3. 变量转换为合适的数据类型 回头看看前面的程序,可能发现用CCur函数把用户提供的数据转换成了货币型数据类型。在VBScript中,有一系列类似这样的数据变形变换函数,在第3章中有详细的描述。 blnBoolean = Cbool(varVariant) ' converts to a Variant of subtype Boolean bytByte = Cbyte(varVariant) ' converts to a Variant of subtype Byte curCurrency = CCur(varVariant) ' converts to a Variant of subtype Currency datDate = CDate(varVariant) ' converts to a Variant of subtype Date dblDouble = CDbl(varVariant) ' converts to a Variant of subtype Double intInteger = CInt(varVariant) ' converts to a Variant of subtype Integer lngLong = CLng(varVariant) ' converts to a Variant of subtype Long sngSingle = CSng(varVariant) ' converts to a Variant of subtype Single strString = CStr(varVariant) ' converts to a Variant of subtype String 如果不能完成变换,也就是说变量内容对新数据类型来说是无效的,便会出现一个运行期错误。然而,如果对数值类型进行变换,我们希望这个数值是有效的,并且能在程序中使用。因此能够检测一个均衡的值对于防止错误的出现是一件“幸事”。 如果想把输入空格作0对待,并且把任何其他无效的输入作为用户错误对待,前面程序变为: strSalesTotal = Request.Form("SalesTotal") If Len(strSalesTotal) = 0 Then ' no value entered, so assume zero curSalesTotal = 0 ElseIf Not IsNumeric(strSalesTotal) Then ' not a valid number, so report an error and stop Response.Write "The value you entered is not a valid number. " Response.Flush Resonse.End Else ' OK to conver the string value and use it curSalesTotal = CCur(strSalesTotal) End If 在JScript中,所有的变量都是对象,并且有typeOf()方法。可以使用typeOf()来确定存在变量中的数据是什么类型,见第3章中的详细论述。 也可以对“null”(VBScript中为Null)进行测试保证在程序使用各种变量之前它们已经赋了值。一个特例是从数据库中获得数据时,数据库中的字段内容经常是Null,表示没有数据。 4. 变量命名和编码约定 阅读过本章和前面几章后,读者可以看出我们对变量名使用三个字母的前缀,用以指明它所代表的数据类型。尽管在这两种脚本语言中用ASP提供的所有变量都是Variant(或JScript中的等价物)类型的,但用变量名来区分出存储在其中的数据的类型仍是非常有用的,有助于防止编写程序时出错。 有许多不同的变量命约定,经常使用的见表7-2: 表7-2 变量类型及前缀 变量类型 前 缀
程序注释 许多编程人员感觉到对程序增加注释不仅增加了不必要的开发时间,而且也减缓了网页的运行速度,因为脚本解释器每次必须先读整个程序,然后再跳过这些注释。尽管这种观点有一定的道理,但是一个月后再回过头来想读懂没有注释的程序,是非常困难的。 至少应该对常用函数和子程序进行注释以便你和其他人能重新使用这些程序。特别是,使用新的Server.Execute方法更加容易(详细情况参阅第4章)。下面是微软提供的一个例程的注释格式。 '***************************************************** 'Purpose: what the routine is designed to achieve 'Inputs: a list of all the parameters to the routine ' parameter1: description, data-type, etc. ' parameter2: description, data-type, etc. 'Returns: what data type is returned, and what it contains 'Comments: other comments about the routine, update history, etc. '***************************************************** 5. 封装脚本语言以便代码重用 刚刚看到了如何注释子程序和函数以便易于重新使用。面向对象编程的原理是建立在程序代码重用的基础上的,并且SSI的#include和新的Server.Execute方法使调用存储在程序库中的函数更容易。 例如,如果有一系列函数用于计算税收和商品的应付费用。可把包含这段程序的页面插入其他页面中: <!-- #inculde VIRTUAL="/library/code/online_sales/tax_and_delivery.inc" --> 包含文件必须含有脚本定界符,或者用<SCRIPT RUNAT="SERVER">...</SCRIPT>或者用<%...%>,每一个子程序和函数应该采用其要求的数值做参数,并且用函数值或更新的参数返回结果。不能使用全局变量,况且不同网页之间的全局变量也是不可用的。但在主网页中的程序能安全地调用所需的函数和子程序。 另外可使用Server.Execute(或者Server.Transfer)把执行转到另一个网页。如果有一段ASP代码用来为客户创建在线采购一览表,这种方法是非常有用的。它包含HTML用来创建标题、表格,用代码进行计算并用Request集合的内容填写相应值(记住不能使用Server.Execute或Server.Transfer把脚本变量传到另一个网页)。另外,运行的网页能够支持函数、类定义(在VBScript中),或者其他设计为可重新使用的内容。 6. 注意潜在的错误情况 编程时不管如何仔细,比如在使用和对变量类型转换之前对变量值进行测试,但总还是有一些情况不能避免错误的出现。明显的例子是:当使用FileSystemObject对象的方法设法访问一个用户指定的文件时,不能确定这个文件是否已移动、删除或者标记成只读型,所有这些操作都可能使程序不能工作。 其他类似的情况可能是,当访问数据库或其他数据存储时,对用户帐户而言,有时要求某一层权限。在这种情况下,可能因为需要的访问不能实现,使程序不能工作。 可以通过采取预防性措施编写程序,来测试类似的潜在错误。例如,可以使用Tools组件或者FileSystemObject对象的FileExists方法来查看,是否一个文件在访问之前就已存在了;或者使用Permission Checker组件来查看当前用户帐号是否有访问需要的文件或资源的权限;也可以通过使用FileSystemObject获得一个文件的属性设置,以便在删除或重写之前查看文件是否是只读的。 如果不考虑可能发生的错误并防止错误发生的话,这些情况和许多类似的情况都可能是潜在的运行期错误源。 7. 最后的测试 很明显,测试是对错误的最好防范方法。错误能通过应用程序影响到其他操作,如果不及时发现能引起不可估量的损失。用各种数值(如用户提交的,或者访问一个数据库得到的数据)对网页进行测试。同时,更重要的是,如果采用我们不希望的值会发生什么。程序能避免这些情况产生其他错误或者避免扰乱正在测试的子程序吗? 好的测试技术应该包括一系列值,如期望的值、边界条件值和超出边界的值。用期望传送到网页的值进行测试是应该经常做的工作,同样超出边界的值通常比较容易阻止。例如,可以限制可接受的数值范围为-100~+100,程序如下: If (intValue < -100) or (intValue > 100) Then ' not a valid value, so report an error and stop Response.Write "Value must be between –100 and +100 inclusive. " Response.Flush Response.End End If 然而要记住查看边界条件,上面的程序能处理-100和+100吗?想把数据限制在-100~+100中吗?取0时会发生什么?上面的程序会显示“Divide By Zero”错误而最终停止执行吗?