经验积累之 Asp
- 一个老程序员的ASP开发经验
-
在开发FeitecCMS过程中,积累下了很多个人独特经验。本着互联共享的原则,雨城写下了以下内容,供ASP爱好者共同参考探讨。
对于网上已有的相关经验等在本文中不会提及。以下内容仅供ASP中级及以上开发者参考之,重点在于程序执行效率及限制出现逻辑性错误及启发设计思路等方面。 1、强制声明变量: Option Explicit也许大家会说:这不是老生常谈吗? 呵呵,笔者之所以要把这一条作为程序开发者首要注意事项,是有原因的。它给程序设计者带来的好处真是太多了。 社会的进步、技术的发展,促使现在的网站程序功能越来越强大、代码越来越复杂。程序设计中必须让变量先声明才能使用,一可以防止变量未声明就使用,二可以防止跨范围使用全局变量,造成隐性错误,给以后的程序运行带来不可预知的隐患。 2、多用局部变量,少用全局变量: 即使使用全局变量,如果这些变量值是不变的,那么可以将其转换为常量(可防止变量内容由于使用不慎,被另外赋值被改变)。如下面的变量代码: Dim FtVer
FtVer="FeitecCMS Pro V3.0" 那么可以用下面的代码来替换: Const FtVer="FeitecCMS Pro V3.0" 全局变量使用不当,容易造成程序软性故障,以后不好检查。 要多使用局部变量的话,那么就必须使程序各功能模块函数化或过程化了,这样做的好处也是很多的哈,看此篇文章的都是高手,不再多说。相比Function和Sub,笔者更喜欢用前者。 3、ASP文件名、变量及数据库表名、数据库字段名必须规范: 开发者最好在这方面有一个比较良好的命名习惯,比如FeitecCMS 程序的开发就有自己的命名规范。总的原则是: (1)、见名知义;
(2)、避开系统保留字;
(3)、ASP文件中加入适当注释。 示例如下: (1)、ASP文件名命名:后台ASP文件都带Admin字样,无界面显示只起包含作用的都以Ft_打头命名,如Ft_Function.asp、Admin_Article.asp等; (2)、数据库表命名如会员表:Ft_User; 此处再多讲一句,就是程序代码的规范问题,有些朋友的ASP文件内容排列是一团乱麻,这是很不好的,要学会排列及缩进,方便其他人的阅读及以后程序的修改。发张PRO版本的截图—— 呵呵,代码看起来挺规范的吧。具体的规范可搜索本站的文章栏目,参考下相关内容。 关于代码缩进实现的个人经验:
·在文本编辑器中,采用TAB方式来实现一行的缩进是最好的,不会增加文件的大小,而且效率高;
·有些设计者采用的是输入空格的方法来实现一行的缩进,虽然有时看起来代码的排列更舒心,但那是以增加文件大小为代价的,雨城觉得很不可取。 4、SQL语句中,对数据库表名要用“[”和“]”括起来: 如: Sql="Select* From [Ft_Art] Order By Art_Id"这样可避免数据库表误取成保留字后造成的系统异常,从我测试的结果来看,好象程序运行速度上也要快一些哦。 5、尽量避免Recorder套用: 比如: Sql="Select Cat_Id From [Ft_Art_Cat] Order By Cat_Id"
Set Rs=Server.CreateObject("Adodb.RecordSet")
Rs.Open Sql,Conn,1,1
Sql2="Select Class_Name From [Ft_Art_Class] Where Cat_Id="&Rs(0)
Set Rs2=Server.CreateObject("Adodb.RecordSet")
Rs2.Open Sql2,Conn,1,1
ClassName=Rs2(0)
Rs2.Close:Set Rs2=Nothing
Rs.Close:Set Rs=Nothing这就是嵌套,很明显,效率太低了。如果有循环的话,效率更低。 可以修改成如下代码: Dim CatId
Sql="Select Cat_Id From [Ft_Art_Cat] Order By Cat_Id"
Set Rs=Server.CreateObject("Adodb.RecordSet")
Rs.Open Sql,Conn,1,1
CatId=Rs(0)
Rs.Close:Set Rs=Nothing
Sql="Select Class_Name From [Ft_Art_Class] Where Cat_Id="&CatId
Set Rs=Server.CreateObject("Adodb.RecordSet")
Rs.Open Sql,Conn,1,1
ClassName=Rs(0)
Rs.Close:Set Rs=Nothing经过笔者对比实测,去掉Recorder嵌套调用后,代码的运行效率可提高30%以上。比那个SQL语句中要列出所需要的字段的优化效果明显得多哦。 2008-10-3,在网上看到这样一段代码: <%
Set rs=server.CreateObject("Adodb.recordset")
sql="select id from A order by id asc"
rs.open sql,conn,1,1
Do Until rs.eof
response.write(conn.execute("select top 1 from B where classid="&rs("id") & " order by id desc")("title"))
rs.movenext
Loop
Set rs=Nothing
%>select * from B INNER JOIN A ON B.ClassID=A.ID也是处理嵌套的,记下备用。 6、程序代码的安全:
笔者认为,一个程序的好坏,要先看其安全性。程序代码的安全性,不能仅靠那个所谓的“SQL防注程序”就能一本万利地彻底搞定,那个只是一件稍微厚一点的腾甲而已,连铠甲都算不上哈。写到这里,顺便提一句:现在的ASP参考书籍简直垃圾啊,笔者就从没看到一本从代码设计之初就教读者编写安全代码的!要不是现在ASP技术不是主流了,笔者倒是愿意为ASP爱好者编写一本最实用的参考书籍。 “打铁还需自身硬”,任何程序只有在设计之初就充分考虑到程序的安全性,那么这个程序完工后才有可能是强健的。 网络攻防技术越来越高深,程序设计者必须充分学习、不断更新自己的相关知识,才能不断完善程序,提高程序的安全性。 7、采用模板技术时,对于模板标签功能解释函数,必须先对当前使用模板内容中的标签代码进行预判,确定有此标签再调用此函数,避免无用函数的服务器端解释工作,节省大量资源: 比如: Str=Replace(Str,"$HotArt$",HotArt())表示调用热门文章的标签解释。 可采用下面的方法进行下简单的预判,虽然增加了ASP语句的执行时间,但有可能减少读取数据库的操作,算算还是值得的。 If Instr(Str,"$HotArt$")<>0 Then Str=Replace(Str,"$HotArt$",HotArt())
使用此方法的效果如何呢?说来你可能打死都不会相信——以笔者的实测效果来看,通过此方法处理的程序运行速度比未处理的提高了近7倍(以程序页面加载时间为基准)!!! 此方法带来了两个利好—— (1)、大大提高程序的运行速度,调用函数越多越明显; (2)、大大降低了服务器的CPU资源占用。 8、尽量采用AJAX技术来做交互内容: 笔者认为,AJAX技术的出现,赋予了ASP新的生命。灵活地运用AJAX,不仅可以带来更好的用户体验,而且也可以变相地弥补速度的劣势,更可以节省服务器资源开销。大家可以尝试本站的评论、投票及会员登录等AJAX效果。 ASP程序设计者必须不断学习、更新自己的技术知识,充分掌握AJAX这个“应用王道”利器。 AJAX应用中,对于需要用户交互而且不是搜索引擎重点收录内容的比如会员登录、发表评论等,尽可放心使用,而对于主体内容、需要搜索引擎重点收录的比如文章内容等,建议慎用之。 虽然主体内容特别是带分页的采用AJAX技术后,那种一闪而现的爽劲,很是诱惑程序设计者;但笔者在此还是建议不要采用,毕竟现在的搜索引擎对于JavaScript内容的抓取并不是很好,而这对于网站的推广、SEO优化等是大忌了。 9、注意AJAX交互内容的安全性检测与设置: (1)、注意提交的参数的检测: 设计者不要以为AJAX中的JS交互内容不会在交互中直接体现在页面中,而认为参数传递是安全的。总之,无论如何,我们都要把握这样一个原则——“凡是涉及到数据库操作的参数,不论是入库还是出库,都必须进行安全检测!”。 (2)、涉及权限限制的交互内容的检测: 比如会员短信的发送等,必须在AJAX提交处理的ASP代码中对传递的数据进行权限验证。想想吧,当别人绕过JS而直接运行AJAX处理用的ASP代码时,如果没有权限验证,后果可想而知。 10、换种思路确定编写逻辑: 对于一个程序设计者而言,要使程序出色,就必须要有自己独特的思想。先来看看下面这个简单例子: ……
Case "搜索图片"
If SmallClass="按标题" Or SmallClass="按关键词" Or SmallClass="按作者" Then
SmallClass2="Pic_Title"
ElseIf SmallClass="按内容" Then
SmallClass2="Pic_Content"
Else
SmallClass2="Pic_Title"
End If
这是原来FeitecCMS中一段搜索站内图片资源的代码。很明显这是最传统的、最呆板的写法——根据原因的不同而列出结果。 如果我们采用逆向思维,从结果去分析代码逻辑,结果写法就会变得很简单了。从上面的代码段来看,无论原因有多少种,而结果却只有两种:SmallClass2的值要么是Pic_Content,要么是Pic_Title。在考虑代码逻辑时,我们完全可以从结果的推导来编写代码,而不管这个结果是由哪个具体的原因得到的。于是下面这段代码就出来了。 ……
Case "搜索图片"
If SmallClass="按内容" Then
SmallClass2="Pic_Content"
Else
SmallClass2="Pic_Title"
End If
……再来一个简单的例子: If Rs(3)<>"" Then
VtContent="<div align=left>"&Rs(3)&"</div>"
Else
VtContent=""
End If我们来精简一下: VtContent=""
If Rs(3)<>"" Then VtContent="<div align=left>"&Rs(3)&"</div>"利用上面的思想来检查下你的程序,看看是不是还有很多地方需要优化呢? 精简的原则是:
(1)、简单的判断语句,或者说结果比较单一的判断;
(2)、不涉及数据库操作的结果的可能性较大。从我目前所遇到的情况来看,最需要换位思考代码编写逻辑的地方就是权限的验证了。比如会员系统—— 如果会员系统只有一个或两个级别,那么这个判断还很好作…… 如果会员系统有三个、四个及以上呢????…… 如果按传统思路一个级别一个级别地判断,那岂不是编写者写得累死,程序运行慢得要死,漏洞补得要死…… 在权限问题的逻辑思考上,我觉得太阳光的“图表示例法”很是管用。
“所谓图表示例法”就是将会员级别与查看权限列成一张二维表格,它们作为行表头和列表头,单元格内容即为权限,有无权限通过单元格底色来体现,哪种单元格最少,我们就从这种条件出发来编写代码逻辑,其它的一句ELSE就搞定了。 11、关于函数:对于ASP而言,函数确实是个好东西。为更高效地使用函数,需要注意以下几点: (1)、尽量使用系统内置函数,实在满足不了要求,才自定义函数;
(2)、自定义带参函数时,参数的个数应该是能少则少,尽量精简;
(3)、函数内的变量建议先定义再使用,尽量避免使用全局变量;
(4)、尽量少进行函数的套用,能用一个大函数解决问题的,决不采用一堆小函数的套用来处理。12、关于使用Insert添加数据: 相关知识介绍都说Insert Into语句比Rs.AddNew来得高效,对此我没有实测过。只是提醒一句:前者对于字段中的内容是有一定的局限的,比如字段内容中不能有英文字符的单引号等等。实用中应根据实际情况来确定是否采用它。 另外,如果服务器中的时间设置为类似于“2009-2-28 下午 2:30”这样的格式,在使用Insert语句插入当前时间时便会出错,表现为写不进此条数据,所以在编写程序时要严谨,可用FormatDateTime(实际时间,vbShortTime)函数事先进行强制格式转换。 13、处理Recorder循环套用: 在前面第5条我们已经说过Recorder套用的问题,那个问题解决的只是从一个Recorder取一个固定值作为参数传递给另一个Recorder的问题,现在要阐述的是处理两个Recorder都要循环调用的问题。具体到FeitecCMS,就是栏目中的二级分类调用问题。 以FeitecCMS Pro版本为例,下面是原V3.0版本中的分类列表函数: ==================================
=函 数 名:PubContentCat(FileName)
=功能:新添内容分类列表(文章/教程/新闻/软件/影音/图片)
==================================
Function PubContentCat(FileName)
Dim i,i_Total,i_CatId,s_ClassName,s_CatName,Str,Sql2
Str=""
Sql="Select Cat_Id,Cat_Name From [Ft_"&FileName&"_Cat] Order By Cat_Id"
Set Rs=Server.CreateObject("Adodb.RecordSet")
Rs.open Sql,Conn,1,1
If Not ( Rs.Eof Or Rs.Bof ) Then
Do While Not Rs.Eof
i_CatId=Rs(0)
s_CatName="<optgroup label="""&Rs(1)&""" class=""key_font""></optgroup>"
Sql2="Select Class_Id,Class_Name From [Ft_"&FileName&"_Class] Where Cat_Id="&i_CatId
Set Rs2 = Server.CreateObject("Adodb.RecordSet")
Rs2.open Sql2,Conn,1,1
If Not ( Rs2.Eof Or Rs2.Bof ) Then
i_Total=Rs2.RecordCount
For i=1 To i_Total
s_ClassName="<option value="&Rs2(0)&">├"&Rs2(1)&"</option>"
Rs2.MoveNext
s_CatName=s_CatName+s_ClassName
Next
Else
s_CatName=s_CatName+"暂无子类"
End If
Rs2.Close:Set Rs2=Nothing