错误
上面已经看到了能够出现的一些不同类型的错误,并且有了一些查找错误的感觉。下面将考虑如何避免把错误引入程序中,尽管不能保证所编写的程序没有错误,但是这里概括的许多技术有助于减少错误数目。
良好的编程习惯
在编程中避免出现错误是和良好的编程习惯相关的,这里有许多工作我们要做,以减少把错误带进网页的可能性。可能有些人因采用某个技术而走向极端,甚至一定程度上在某个特殊问题上因书生气十足而引入了更多的错误。当然编程人员也不可能采用了这里列出的所有技术。
要考虑的主要内容是:
· 代码的格式化和缩进编排。
· 变量显式表明。
· 变量转换为合适的数据类型。
· 使用有意义的变量命名约定。
· 封装脚本。
· 注意潜在的错误情况。
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
' calculate commission payment
sngCommission = curSalesTotal * (sngComissionPercent /100)
%>
这时,当脚本引擎试图解释程序时将识别出一个语法错误,并且能够指出此变量没有声明,如图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 变量类型及前缀
变量类型
前 缀
布尔型(Boolean)
bln
字节型(Byte)
byt
日期/时间型(Date/Time)
dat或dtm
集合型(Collection)
col
双精度型(Double)
dbl
整型(Integer)
int
长整型(Long)
lng
对象型(Object)
obj
单精度型(Single)
sng
字符型(string)
str
对于一个包含函数和子程序的网页,指出某个变量是否已经声明或存在于任何函数和子程序之外是非常有用的。若已经声明,则该变量对网页来说是全局变量。对全局变量加上“g”前缀,所以一全局字符串变量可能被命名为gstrMystring;类似的,以“a”为前缀的变量是数组或数组元素。
程序注释
许多编程人员感觉到对程序增加注释不仅增加了不必要的开发时间,而且也减缓了网页的运行速度,因为脚本解释器每次必须先读整个程序,然后再跳过这些注释。尽管这种观点有一定的道理,但是一个月后再回过头来想读懂没有注释的程序,是非常困难的。
至少应该对常用函数和子程序进行注释以便你和其他人能重新使用这些程序。特别是,使用新的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”错误而最终停止执行吗?