yule-前端/后台开发

经验积累之 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版本的截图——

一个老程序员的ASP开发经验

      呵呵,代码看起来挺规范的吧。具体的规范可搜索本站的文章栏目,参考下相关内容。

      关于代码缩进实现的个人经验:

      ·在文本编辑器中,采用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语句中要列出所需要的字段的优化效果明显得多哦。一个老程序员的ASP开发经验

      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爱好者编写一本最实用的参考书籍。一个老程序员的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

……

      看看是不是简单明了啊一个老程序员的ASP开发经验

      再来一个简单的例子:

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>"

      利用上面的思想来检查下你的程序,看看是不是还有很多地方需要优化呢?一个老程序员的ASP开发经验精简的原则是:

(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
                      Str=Str+s_CatName
                      Rs.MoveNext
                      s_CatName=""
                Loop
      Else
              Str="<option  class=disable_font>请先添加大类</option>"
      End If
      Rs.Close:Set Rs=Nothing
      If Instr(Str,"暂无子类")<>0 Then Str="<option  class=key_font>系统检测到某个大类下没有子类,请与管理员联系</option>"
      PubContentCat=Str
End Function

      很明显,这是Recorder循环套用,用本文前述第5条不能解决这个问题。Recorder的循环套用,多次重复读取数据,如果分类很多,那么此段代码的执行效率可想而知。

      解决此循环套用的思路,我是这样想的——先一次性读出所有大类的内容(即s_CatName和i_CatId)并保存在一个数组变量中,然后关闭一个Recorder,再分割数组,取得相关参数(i_CatId),利用此参数作为打开另一个Recorder(取得子分类)的条件,这样不是完美解决了Recorder循环调用的问题了吗,尽管比原来的多了数组内存空间的占用,但是大大节省了数据库的读取时间,执行效率肯定比上述代码高不少。下面是我用GetRows方法制作的改良函数:

==================================
=函 数 名:PubContentCat(FileName)
=功      能:新添内容分类列表(文章/教程/新闻/软件/影音/图片)
==================================
Function PubContentCat(FileName)
      Dim i,i_Total,i_CatId,s_ClassName,s_CatName,Str,s_Array_List
      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
              s_Array_List=Rs.GetRows
      Else
              Str="<option  class=disable_font>请先添加大类</option>"
      End If
      Rs.Close:Set Rs=Nothing
      If IsArray(s_Array_List) Then
              i_Total=UBound(s_Array_List,2)
              For i=0 To i_Total
                      s_CatName="<optgroup label="""&s_Array_List(1,i)&""" class=""key_font""></optgroup>"
                      Sql="Select Class_Id,Class_Name From [Ft_"&FileName&"_Class] Where Cat_Id="&s_Array_List(0,i)
                        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
                                      s_ClassName="<option value="&Rs(0)&">├"&Rs(1)&"</option>"
                                      Rs.MoveNext
                                      s_CatName=s_CatName+s_ClassName
                            Loop
                    Else
                                s_CatName=s_CatName+"暂无子类"
                      End If
                      Rs.Close:Set Rs=Nothing
                      Str=Str+s_CatName
                      s_CatName=""
              Next
      End If
      If Instr(Str,"暂无子类")<>0 Then Str="<option  class=key_font>系统检测到某个大类下没有子类,请与管理员联系</option>"
      PubContentCat=Str
End Function

      14、再谈IF语句:

      5-23日,在处理后台代码时,看见以下代码:

  If Instr(FileName,"Pic")=0 And Instr(FileName,"Dj")=0 And Instr(FileName,"Produ")=0 Then
        If sPic<>""  Then  Str=Str+"&nbsp;<span class=""current_font"">[图]</span>"
  End If
'代码含义:如果不是图片/影音/作品栏目,那么进入第一层IF语句,第二层IF词句的意思是在满足第一层条件的情况下,如果变量sPic不为空,那么添加相关代码,否则不添加。

      不禁哑然,呵呵,以前的处理方式也不彻底哇,看看修改后的代码:

  If Instr(FileName,"Pic")=0 And Instr(FileName,"Dj")=0 And Instr(FileName,"Produ")=0 And sPic<>"" Then
        Str=Str+"&nbsp;<span class=""current_font"">[图]</span>"
  End If

      少了一层嵌套,是不是简单明了?当然,为了代码的精简,还可以用一句话完成(少写一行代码,可以偷偷懒),代码为:

  If Instr(FileName,"Pic")=0 And Instr(FileName,"Dj")=0 And Instr(FileName,"Produ")=0 And sPic<>"" Then  Str=Str+"&nbsp;<span class=""current_font"">[图]</span>"

      代码写到这儿,我又想到一个问题:如果IF判断语句中有几个并列条件时,是否把出现频度最高的放在最前面时此语句执行效率会高一些呢?由于没有条件来实测,也不清楚其执行机制,所以我只能揣测应该效率会高一些。上面的代码中条件出现频度最高的应该是sPic,据此将代码修改会以下,我想效率是最高的了:

  If  sPic<>"" And Instr(FileName,"Pic")=0 And Instr(FileName,"Dj")=0 And Instr(FileName,"Produ")=0 Then
        Str=Str+"&nbsp;<span class=""current_font"">[图]</span>"
  End If

      15、数据过多时后台发布页面速度打开过慢问题的思考:

      在制作FeitecCMS V4.0版本的过程中,我的文章系统有1500多条数据了,我发现在打开后台文章发布页面时,页面加载时间是列表页面时间的3倍,嗯,这个肯定有问题,经过逐项排除,最终确定为发布页面中的已有关键词加载这个东东严重拖慢了速度,找到这个函数,添加了调用条数限制,呵呵,这样速度就恢复正常了。

      限制调用条数这只是权宜之计,比较好的方法是通过AJAX实现动态加载——只有点击关键词这个下拉列表框时,才自动取出,呵呵,这样既不影响整个页面的加载速度,也增强了用户体验,相当不错。