编写高质量的VB代码

什么是一个高效的软件?一个高效的软件不仅应该比实现同样功能的软件 运行得更快,还应该消耗更少的系统资源。这篇文章汇集了作者在使用VB进行软件开发时积累下来的一些经验,通过一些简单的例子来向你展示如何写出高效的 VB代码。其中包含了一些可能对VB程序员非常有帮助的技术。在开始之前,先让我陈清几个概念。


让代码一次成型:在我接触到的程序员中,有很多人喜欢先根据功能需求把代码写出来,然后在此基础上优化代码。最后发现为了达到优化的目的,他们不得不把代码再重新写一遍。所以我建议你在编写代码之前就需要考虑优化问题。
把握好优化的结果和需要花费的工作之间的关系:通常当完成了一段代码,你需要检查和修改它。在检查代码的过程中,也许你会发现某些循环中的代码效率还可 以得到进一步的改进。在这种情况下,很多追求完美的程序员也许会立马修改代码。我的建议是,如果修改这段代码会使程序的运行时间缩短一秒,你可以修改它。 如果只能带来10毫秒的性能改进,则不做任何改动。这是因为重写一段代码必定会引入新的错误,而调试新的代码必定会花掉你一定的时间。程序员应该在软件性 能和开发软件需要的工作量之间找一个平衡点,而且10毫秒对于用户来说也是一个不能体会到的差异。
在需要使用面向对象方法的时候尽量使用它:VB提供的机制不完全支持面向对象的设计和编码,但是VB提供了简单的类。大多数人认为使用对象将导致代码的效率降低。对于这一点我个人有些不 同的意见;考察代码的效率不能纯粹从运行速度的角度出发,软件占用的资源也是需要考虑的因素之一。使用类可以帮助你在整体上提升软件的性能,这一点我会在后面的例子中详细说明。
当你编写VB代码的时候,希望你能把上面几点作为指导你编码的原则。我把文章分为两个部分:如何提高代码的运行速度和编译优化。

一、如何提高代码的运行速度
下面的这些方法可以帮助你提高代码的运行速度:
1. 使用整数(Integer)和长整数(Long)
提高代码运行速度最简单的方法莫过于使用正确的数据类型了。也许你不相信,但是正确地选择数据类型可以大幅度提升代码的性能。在大多数情况下,程序员可 以将Single,Double和Currency类型的变量替换为Integer或Long类型的变量,因为VB处理Integer和Long的能力远 远高于处理其它几种数据类型。
在大多数情况下,程序员选择使用Single或Double的原因是因为它们能够保存小数。但是小数 也可以保存在Integer类型的变量中。例如程序中约定有三位小数,那么只需要将保存在Integer变量中的数值除以1000就可以得到结果。根据我 的经验,使用Integer和Long替代Single,Double和Currency后,代码的运行速度可以提高将近10倍。
2. 避免使用变体
对于一个VB程序员来说,这是再明显不过的事情了。变体类型的变量需要16个字节的空间来保存数据,而一个整数(Integer)只需要2个字节。通常 使用变体类型的目的是为了减少设计的工4作量和代码量,也有的程序员图个省事而使用它。但是如果一个软件经过了严格设计和按照规范编码的话,完全可以避免 使用变体类型。
在这里顺带提一句,对于Object对象也存在同样的问题。请看下面的代码:

    Dim FSO

    Set FSO = New Scripting.FileSystemObject
    或

    Dim FSO as object

    Set FSO = New Scripting.FileSystemObject

上面的代码由于在申明的时候没有指定数据类型,在赋值时将浪费内存和CPU时间。正确的代码应该象下面这样:

Dim FSO as New FileSystemObject

3. 尽量避免使用属性

在平时的代码中,最常见的比较低效的代码就是在可以使用变量的情况下,反复使用属性(Property),尤其是在循环中。要知道存取变量的速度是存取属性的速度的20倍左右。下面这段代码是很多程序员在程序中会使用到的:

    Dim intCon as Integer

    For intCon = 0 to Ubound(SomVar())

    Text1.Text = Text1.Text & vbcrlf & SomeVar(intCon)

    Next intCon

    下面这段代码的执行速度是上面代码的20倍。

    Dim intCon as Integer

    Dim sOutput as String

    For intCon = 0 to Ubound(SomeVar())

    sOutput = sOutput & vbCrlf &

    SomeVar(intCon)

    Next

    Text1.Text = sOutput

4. 尽量使用数组,避免使用集合

除非你必须使用集合(Collection),否则你应该尽量使用数组。据测试,数组的存取速度可以达到集合的100倍。这个数字听起来有点骇人听闻,但是如果你考虑到集合是一个对象,你就会明白为什么差异会这么大。

5. 展开小的循环体

在编码的时候,有可能遇到这种情况:一个循环体只会循环2到3次,而且循环体由几行代码组成。在这种情况下,你可以把循环展开。原因是循环会占用额外的CPU时间。但是如果循环比较复杂,你就没有必要这样做了。

6. 避免使用很短的函数

和使用小的循环体相同,调用只有几行代码的函数也是不经济的--调用函数所花费的时间或许比执行函数中的代码需要更长的时间。在这种情况下,你可以把函数中的代码拷贝到原来调用函数的地方。

7. 减少对子对象的引用

在VB中,通过使用.来实现对象的引用。例如:

Form1.Text1.Text

在上面的例子中,程序引用了两个对象:Form1和Text1。利用这种方法引用效率很低。但遗憾的是,有办法可以避免它。程序员唯一可以做就是使用With或者将用另一个对象保存子对象(Text1)。

注释: 使用With

    With frmMain.Text1

    .Text = "Learn VB"

    .Alignment = 0

    .Tag = "Its my life"

    .BackColor = vbBlack

    .ForeColor = vbWhite

    End With

或者

注释: 使用另一个对象保存子对象

    Dim txtTextBox as TextBox

    Set txtTextBox = frmMain.Text1

    TxtTextBox.Text = "Learn VB"

    TxtTextBox.Alignment = 0

    TxtTextBox.Tag = "Its my life"

    TxtTextBox.BackColor = vbBlack

    TxtTextBox.ForeColor = vbWhite

注意,上面提到的方法只适用于需要对一个对象的子对象进行操作的时候,下面这段代码是不正确的:

     With Text1

    .Text = "Learn VB"

    .Alignment = 0

    .Tag = "Its my life"

    .BackColor = vbBlack

    .ForeColor = vbWhite

    End With

很不幸的是,我们常常可以在实际的代码中发现类似于上面的代码。这样做只会使代码的执行速度更慢。原因是With块编译后会形成一个分枝,会增加了额外的处理工作。

8. 检查字符串是否为空

大多数程序员在检查字符串是否为空时会使用下面的方法:

If Text1.Text = "" then
'注释: 执行操作
End if

很不幸,进行字符串比较需要的处理量甚至比读取属性还要大。因此我建议大家使用下面的方法:

If Len(Text1.Text) = 0 then
'注释: 执行操作
End if

9. 去除Next关键字后的变量名

在Next关键字后加上变量名会导致代码的效率下降。我也不知道为什么会这样,只是一个经验而已。不过我想很少有程序员会这样画蛇添足,毕竟大多数程序员都是惜字如金的人。

  '注释: 错误的代码

    For iCount = 1 to 10

  '注释: 执行操作

    Next iCount

  '注释: 正确的代码

    For iCount = 1 to 10

  '注释: 执行操作
    Next

10. 使用数组,而不是多个变量

当你有多个保存类似数据的变量时,可以考虑将他们用一个数组代替。在VB中,数组是最高效的数据结构之一。

11. 使用动态数组,而不是静态数组

使用动态数组对代码的执行速度不会产生太大的影响,但是在某些情况下可以节约大量的资源。

12. 销毁对象
无论编写的是什么软件,程序员都需要考虑在用户决定终止软件运行后释放软件占用的内存空间。但遗憾的是很多程序员对这一点好像并不是很在意。正确的做法是在退出程序前需要销毁程序中使用的对象。例如:

Dim FSO as New FileSystemObject
'注释: 执行操作
'注释: 销毁对象
Set FSO = Nothing

对于窗体,可以进行卸载:

Unload frmMain

Set frmMain = Nothing

13. 变长和定长字符串
从技术上来说,与变长字符串相比,定长字符串需要较少的处理时间和空间。但是定长字符串的缺点在于在很多情况下,你都需要调用Trim函数以去除字符串末的空字符,这样反而会降低代码效率。所以除非是字符串的长度不会变化,否则还是使用变长字符串。
14. 使用类模块,而不是ActiveX控件
除非ActiveX控件涉及到用户界面,否则尽量使用轻量的对象,例如类。这两者之间的效率有很大差异。
15. 使用内部对象
在涉及到使用ActiveX控件和DLL的时候,很多程序员喜欢将它们编译好,然后再加入工程中。我建议你最好不要这样做,因为从VB连接到一个外部对象需要耗费大量的CPU处理能力。每当你调用方法或存取属性的时候,都会浪费大量的系统资源。如果你有ActiveX控件或DLL的源代码,将它们作为工程的私有对象。
16. 减少模块的数量
有些人喜欢将通用的函数保存在模块中,对于这一点我表示赞同。但是在一个模块中只写上二三十行代码就有些可笑了。如果你不是非常需要模块,尽量不要使用它。这样做的原因是因为只有在模块中的函数或变量被调用时,VB才将模块加载到内存中;当VB应用程序退出时,才会从内存中卸载这些模块。如果代码中只有一个模块,VB就只会进行一次加载操作,这样代码的效率就得到了提高;反之如果代码中有多个模块,VB会进行多次加载操作,代码的效率会降低。
17. 使用对象数组
当设计用户界面时,对于同样类型的控件,程序员应该尽量使用对象数组。你可以做一个实验:在窗口上添加100个PictureBox,每个PictureBox都有不同的名称,运行程序。然后创建一个新的工程,同样在窗口上添加100个PictureBox,不过这一次使用对象数组,运行程序,你可以注意到两个程序加载时间上的差别。
18. 使用Move方法
在改变对象的位置时,有些程序员喜欢使用Width,Height,Top和Left属性。例如:

Image1.Width = 100
Image1.Height = 100
Image1.Top = 0
Image1.Left = 0

实际上这样做效率很低,因为程序修改了四个属性,而且每次修改之后,窗口都会被重绘。正确的做法是使用Move方法:

Image1.Move 0,0,100,100

19. 减少图片的使用
图片将占用大量内存,而且处理图片也需要占用很多CPU资源。在软件中,如果可能的话,可以考虑用背景色来替代图片--当然这只是从技术人员的角度出发看这个问题。
20. 使用ActiveX DLL,而不是ActiveX控件
如果你设计的ActiveX对象不涉及到用户界面,使用ActiveX DLL。

二、编译优化
我所见过的很多VB程序员从来没有使用过编译选项,也没有试图搞清楚各个选项之间的差别。下面让我们来看一下各个选项的具体含义。
1. P-代码(伪代码)和本机代码
你可以选择将软件编译为P-代码或是本机代码。缺省选项是本机代码。那什么是P-代码和本机代码呢?
P-代码:当在VB中执行代码时,VB首先是将代码编译为P-代码,然后再解释执行编译好的P-代码。在编译环境下,使用这种代码要比本机代码快。选择P-代码后,编译时VB将伪代码放入一个EXE文件中。
本机代码:本机代码是VB6以后才推出的选项。当编译为EXE文件后,本机代码的执行速度比P-代码快。选择本机代码后,编译时VB使用机器指令生成EXE文件。
在使用本机代码进行编译时,我发现有时候会引入一些莫名其妙的错误。在编译环境中我的代码完全正确地被执行了,但是用本机代码选项生成的EXE文件却不能正确执行。通常这种情况是在卸载窗口或弹出打印窗口时发生的。我通过在代码中加入DoEvent语句解决了这个问题。当然出现这种情况的几率非常少,也许有些VB程序员从来没有遇到过,但是它的确存在。
在本机代码中还有几个选项:
a) 代码速度优化:该选项可以编译出速度较快的执行文件,但执行文件比较大。推荐使用
b) 代码大小优化:该选项可以编译出比较小的执行文件,但是以牺牲速度为代价的,不推荐使用。
c) 无优化:该选项只是将P-代码转化为本机代码,没有做任何优化。在调试代码时可以使用。
d) 针对Pentium Pro优化:虽然该项不是本机代码中的缺省选项,但是我通常会使用该选项。该选项编译出的可执行程序在Pentium Pro和Pentium2以上的机器上可以运行得更快,而在比较老的机器上要稍稍慢一些。考虑到现在用Pentium2都是落伍,所以推荐大家使用该选项。
e) 产生符号化调试信息:该项在编译过程中生成一些调试信息,使用户可以利用Visual C++一类的工具来调试编译好的代码。使用该选项会生成一个.pdf文件,该文件记录了可执行文件中的标志信息。当程序拥有API函数或DLL调用时,该选项还是比较有帮助的。
2. 高级优化
高级优化中的设置可以帮助你提高软件的速度,但是有时候也会引入一些错误,因此我建议大家尽量小心地使用它们。如果在代码中有比较大的循环体或者复杂的数学运算时,选中高级优化中的某些项会大幅度提升代码的性能。如果你使用了高级优化功能,我建议你严格测试编译好的文件。
程序的执行效率和模块化从来就是一对矛盾,模块化(包括现在的对象化)意味着你对代码的重复利用程度更高,但是同时也意味这你的程序的执行效率更低,忽略语言本身的优缺点,使用“内嵌式”代码的执行速度要高于使用过程和函数。这一点是没有疑问的。但是与此同时则带来了程序本身的体积的扩大和代码维护的困难。因为当你的程序中有数个地方采用了相同的代码时,使用模块和类无疑是最有效率的做法。但是抛开这些不谈,很多人毕竟不是对于程序涉及非常熟练,因为他们的首要目的是解决问题而要优化一个程序则是一个可大可小的事。要小,我们可以从数据类型的转换,一个算法的实现来体现这种效率,而要大,则可能从头写这个程序会比修改来得更有效率。(相信这是很多高手的切身体会)那么要如何才能避免这样的麻烦呢?
首先,在VB这中,我们可以在很多地方避免效率的低下,因为VB本身就不是一种以速度见长的语言,我们就必须在写程序的时候更多得考虑到这个缺点。比如,当你的界面中出现的TEXTBOX多过10个的时候你会怎么处理呢?哈哈,你想到了控件数组,非常好,使用控件数组要比使用十个单独的控件的资源占用小。
并且在处理类似的事件的时候(比如要格式化输入内容等等)你可以使用控件的INDEX和一个循环来处理,这个做法要比使用十个TEXT控件更有效率,并且速度也更快,代码的修改也更容易。但是当你的一个窗体中出现了数十个甚至上百个TEXT控件的时候,你就需要想一下了,你是否因该换一个控件呢?或许你使用一个GRID控件会让你的工作更顺利一些呢?
其二,不要以为VB什么也不能做,很多东西并不需要高深的API,使用VB自己带的功能也能够完成同样事,并且代码也更简洁,只是你没有去好好想而已。我这里可以举非常多的例子来说明这个问题:比如无边框窗体的移动,比如多个VB程序之间(甚至和不是VB的程序之间)的数据交换,比如一些图像处理的问题等等,其实使用VB自带的方法和控件完全可以解决,根本不需要一个API,并且速度并不慢,而程序的可读性也不错。不相信吗?我就写过不少。
其三,VB毕竟是一种被大众所承认的语言,它的优点有很多,或许只是你不知道而已,很多人说它的速度慢,但是我要说的是;“你的程序真的写好了吗?”,虽然使用控件是VB的一个非常方便的地方,但我们也不能光依靠控件。特别是在控件板块中经常看到诸如:“某某东西有没有什么控件控件可以实现啊”之类的问题,老实说,我并不认为一个使用控件非常熟练的人就一定是一个VB的高手,在很多时候我并不依赖控件,我宁可自己写一个比较“土”的算法来实现我的方法也不想用一个控件,因为使用一个控件就意味着你的依赖性。我在网络上看到类似什么“MP3播放器”之类的程序不下几十个,但是从来也没有看到过一个使用代码来解码的程序,这不能不说是一个遗憾。控件是个好东西,但是只会使用控件的话,我想你永远也成不了一个合格的程序员。
关于这个问题,可能有很多人会和我争论,我并不害怕争论。我是一个从C过来的人,我经历过很多现在的程序员没有经历过的痛苦例程:不知道你是否体验过在DOS下写一个图形化并且支持鼠标的程序是多么烦恼的事。如果你干过,那你就会同意我的观点。哪怕你对“面对对象”的概念再熟,请不要忘记,在这个“对象”的内部,还是需要“面对过程”的。所以学一些“古老”的东西对你没有坏处。
其四:语言真的是一样的,到了一定的程度你就会发出这样感慨。前一阵子,我看了点关于C51单片机的编程,我真的发现这是多么简单的一种东西。它只要求你有一点记忆力,能够记住那些拗口的命令而已,其他的东西我都可以写出来。程序就是这么一个简单的东西,只要你了解了它的原理,任何语言都一样。区别只在于它们实现的方法有些不同而已。
过一段时间我想公布一个我写的控件的代码,是关于图像处理的。其中既有可以在网上找到的算法,也有我自己想出来的方法。我只是想通过这个东西来说明一件事:VB不是一个“玩具语言”它能作的比你能想到的要多的多,只是你以为不能做到,或是没有想到而已。
关于代码的优化有很多很多可说的,翻一下程序设计的书,你可以找到很多例子,各种语言也有各种语言的特点。我这里先举一个非常小的例子希望可以说明一下问题:一个交换两个变量内容的过程,如何实现(假设是整数型的)?
当然这是一个非常非常简单的问题,几乎每一个写程序的人都能写出代码实现。但是效率呢?通过这个例子我只是想说明一个问题而已。
我们以VB来举例吧:
一般的:

SUB EXCHANGE(A AS LONG,B AS LONG)
DIM C AS LONG
C=A
A=B
B=C
END SUB


简单吧?使用了一个中间变量C就解决问题了。那么再看看另一种不常见的方法:

SUB EXCHANGE(A AS LONG, B AS LONG)
B = B XOR A
A = B XOR A
B = B XOR A
END SUB


是否有点难以理解?老实说,同样是交换了内容,但是效率不同,或许交换一次你感觉不出来,但是有兴趣的话你可以写一个循环,比如交换个一百万次,再使用TIMEGETTIME这个API来看一下各自所花费的时间,保证不一样,哪个快,你自己试验吧。
VB处理计算并不慢,不信的话你可以写一个四则运算的循环来检测一下,VB慢的地方在于GDI,不信的话你试试加载很多窗体和可见控件。这个是没有办法的,是VB本身的局限,但是很多时候我们需要的并不是一个“更快的”控件,而是“更少的”控件,就象我前面说的,能使用控件数组的时候就用控件数组,控件数组太多的时候就看看有没有可替代的单个控件,当使用相同功能的时候尽量使用资源少的控件,比如在不需要输入的时候我们可以用LABEL来代替TEXT,在不需要编辑和保存图片的时候我们可以使用IMAGE来代替PICTUREBOX,等等。甚至很多时候根本不需要使用控件或对象,通过代码就可以实现相同的算法比加载一个控件要快很多。
补充第四点:不要迷信API,VB并非离开API就什么也干不了。很多问题没有API也能干得很好,我不是学计算机专业的,很多“专业”的知识我并不具备,就象上次那个DIB的东西就让我费了好长的时间,后来还是多亏了这里的朋友帮忙才解决的,但是并非就说离开了API就什么也做不了了。至少不会DIB的时候我也使用VB自己的方法实现相同的功能(当然是比较慢),但是毕竟也是解决问题的。要知道解决一个问题本身才是最重要的,实现的方法其实并不是最重要的。说到这里或许又有很多人来反驳我,当然,我也知道很多时候必须使用更快的方法来解决问题,但是我的方法是:先找到一个“可以”解决问题的方法,当问题可以解决之后,我再会去研究如何改进算法使它更有效。呵呵,这个世界还是以成败论英雄的,不是吗?
记得我以前用TOUBOR C的时候,老是搞不懂那个INT怎么弄,结果呢,对面计算机系的朋友用INT解决了一个“简单”问题,我缺按照我的方法老老实实的用C语言自己带的功能实现了。这还要感谢TC编译器的强大,我的程序居然不比他的慢多少。呵呵,但是这个结果很让我兴奋,因为我觉得我这样一个“半路和尚”也能做计算机专业的事。呵呵,给大家一个鼓励。

猜你喜欢

转载自blog.csdn.net/luohaitao/article/details/127724944