软件工程领域有一个从管理学中借鉴过来的名词——最佳实践(Best Practice),大致意思是:在软件的设计开发过程中,存在一些前人验证过的方法和规律,可以优化代码、提高效率、降低出错的可能性。如果我们能够充分借鉴这些经验,不仅可以把程序编出来,而且可以把它编得十分漂亮,或者说很有章法。
尽管编写一个简单的VBA程序远没有开发商业软件那么复杂,但同样存在很多“最佳实践”,凝结了无数代码先驱的血泪教训。如果能在学习编程的早期,就把这些宝贵的经验内化为自己的日常编程习惯,将会使你在未来的深入学习和实际开发中大大受益。因此,最佳实践的思想将贯穿本书各个章节,本节就先为读者介绍几个最基本的与变量使用有关的“最佳实践”。
在前面讲到,变量是一个带有标识符的内存单元,或者说是一个由开发者为之命名的“长期居所”。与注册公司一样,这个名称虽然可以随意指定,但是起一个合法又好听的名字仍然十分重要,这一点可以从案例2-2中看出来。
案例2-2: 在如图2.4所示的工作表中,C4单元格存放了某位程序员的当月收入。现在需要计算扣除各种费用后,他真正能够留下的收入并存入 D4单元格。下面列出的任何一个程序,都可以计算出该程序员的实际留存(为简化示例程序,这里没有使用真实的所得税算法)。
图2.4 案例2-2的工作表及VBA代码示意
虽然上面两段代码的结构和功能完全相同,但是给人的感觉却明显不同:左边的程序读起来如同一道数学题,让人望而生畏;而右边的程序却如同一份流程说明书,逻辑清晰可见。造成这种差异的原因就是变量命名方式的不同。下面我们就看一下在VBA中给变量命名的原则。
中国公民在登记姓名时应当使用规范汉字 ,同样,VBA的变量名也只允许使用合法的字符,具体包括 英文字母、数字 和 下画线 。如果读者使用的是中文版Office,也可以在变量名中使用汉字。此外,变量名的 第一个字符必须是英文字母 (或汉字),不可以使用下画线和数字。
所以,下图左侧列出的都是合法的 VBA 变量名;而右侧则是非法的变量名,会导致程序出错。
另外需要注意的是,VBA不会区分大小写字母,比如Salary、salary,乃至saLaRy等写法,都被认为是同一个变量。由于这种特性,VBA被归类为“大小写不敏感”的程序设计语言 。所以,如果读者在代码中使用了Salary和saLaRy这两种写法,VBE会自动把它们统一成相同的写法:Salary和saLaRy哪个先出现在程序中,就把它们统一成哪种写法。
事实上,根据Office的语言版本不同,VBA还可能会支持其他语言的符号作为变量名。比如在一些中文版Office中甚至可以使用“ 彼の給料 ”这样的日语符号作为变量名。但是,一旦把这些含有中文符号或其他语言符号的 VBA 程序复制到不支持这些语言的计算机上,这些代码很可能无法被解读,导致程序无法运行。
所以,为了让 VBA 程序具有良好的兼容性,能够随时随地在不同环境下运行,还是建议大家 尽可能只使用英文字母、数字和下画线 为变量命名(以及尚未讲到的其他元素,如子过程、函数等)。如无特殊需要,本书接下来的案例也将全部使用英文命名。比如案例2-2 中的代码,可以重新改成下面的样子:
通过案例2-2两种代码的对比还可以看出一点:之所以右侧的代码更加清晰、易懂,是因为每个变量名都能够反映出它的用途。比如“所得税”或“Tax”,看起来要比“x”“y”形象得多,让人对它们的含义一目了然。所以,给变量赋予一个有意义的名字也十分重要。
一般情况下,我们可以使用英文单词作为变量名,比如将表示工资的变量命名为“Salary”,也可以考虑使用汉语拼音(如GongZi)作为变量名(这种做法在一些多年前开发且目前仍在使用的遗留系统,或称祖传代码中比较常见)。同时,在使用单词或拼音作为变量名时,大多数VB程序员都习惯将首字母大写,在此也可遵循这一惯例。
不过有的时候,单凭一个单词仍无法完全体现变量的含义,需要使用多个单词来命名。比如“税后工资”这个变量对应的英文为“Salary After Tax”,包含三个单词。在这种情况下,可以将这三个单词连在一起作为变量名,即 SalaryAfterTax,或者使用下画线作为分隔符,即Salary_After_Tax。如果采用第一种方式(不使用下画线),建议大家将每个单词的首字母都大写,这样在阅读时可以轻松断句,比“Salaryaftertax”这种形式更清晰。
初学者需要注意的是:尽管这个变量名是由三个单词构成的,但是绝对不可以在这三个单词之间使用空格!因为空格是 VBA 的词汇分隔符。如果使用了空格,写出来就是“Salary After Tax”,VBA会认为它们代表三个变量,分别叫作“Salary”“After”和“Tax”,与我们的初衷相悖!
最后指出一点:尽管使用多个单词可以更加精确地描述变量的含义,但并不意味变量名越长越好。如果最后的代码写出来是下面这样的,恐怕比使用“x”“y”“z”更令人痛苦。(在这个例子中,由于某些语句太长,所以使用下画线将其分写到了两行)
事实上,只要能让阅读者迅速领会变量的含义即可,变量名应该尽可能简洁。在不影响理解的情况下,使用单词的缩写也是可以的。比如表示“工资”的变量名可以写成“Salary”,也可以写成“Sal”,只要不会引起歧义就可以。而对于一些没有特定含义的变量,比如下一章中讲解For循环时用到的循环变量,也可以直接使用一个字母(如“i”“j”“k”)作为变量名。
在VBA语言中,有一些词汇已经被赋予了专门的含义,因此被称为“关键字”或“保留字”。比如第1章就学过的“Sub”就是一个保留字,代表一个程序的开始位置,而“End”也是一个保留字,当二者同时出现时,代表一个程序结束。
如果把某个变量命名为某个保留字,就会导致 VBA 程序语义混乱,无法执行。比如下面这段代码执行时就会出错。
这段代码出错的原因在于,当VBA执行到“Sub=Cells(4,3)*Cells(4,4)”这一语句时,会认为是要创建一个新的VBA宏。然而VBA不允许在一个宏(Sub Payable)的内部再创建一个宏。退一步讲,即使 VBA 允许这样做,Sub 关键字的后面也应该是一个正确的宏名,但“=Cells(4,3)*Cells(4,4)”显然不是一个正常的宏名(事实上,宏名的命名规则与变量是完全一样的)。
广义地讲,VBA保留字不仅包括Sub等词汇,而且包括VBA中已经定义好的各种事件、对象、方法、属性、常量等 ,“Cells”也在此列。如果我们将一个变量命名为“Cells”,同样会导致程序出错。下面列出了VBA中最常见的一些保留字:
上面列举的并非全部 VBA 保留字,显然我们也不可能将所有保留字记下来。那么在给变量命名时怎样避开 VBA 保留字呢?这里介绍一个实用的小技巧:当第一次给某个变量命名时,比如打算将其命名为“AddressOf”,可以先将其以全部小写的形式写到代码中,即“addressof”,由于VBA中绝大多数保留字都是以首字母大写的形式命名的,所以假如“AddressOf”是一个保留字,VBA编辑器会自动将输入的“addressof”更改为“AddressOf”,以便保持大小写统一。因此,如果发现VBE将输入的全小写变量名自动转换成了首字母大写格式,就说明它是一个VBA事先定义好的保留字,不能用于为变量命名。
最后需要补充的是:上面列出的变量命名规则,如只能使用合法字符,以及禁止使用保留字等,同样适用于VBA宏的命名(Sub后面的程序名)及模块的命名,乃至本书后面将要介绍的“函数”“对象”等各种元素。因此,读者在编写VBA代码,需要为某个元素命名时,都要考虑到本节所讲的各项内容。
规范的变量名可以提高程序的可读性,但仍然无法降低拼写错误导致的风险。仍以案例2-2为例,C4单元格中存放的税前工资是10000元,那么程序运行之后的结果应该是实际留存1500元。但是假如我们在编写代码时不小心写错了一个字符,比如将最后一句代码中的“Revenue”误写成了“Revnue”(见图2.5),再次运行这个程序时会发生怎样的情况呢?
图2.5 写错变量名后的代码和运行效果(右侧代码最后
从图2.5中可以看到,虽然程序的最后一行代码写错了单词,但是VBA并没有提示“拼写有误,请检查修改”等错误信息,而是正常执行程序,但显示在D4单元格中的计算结果却是负债8500元。
为什么会导致这种结果呢?只要理解了变量的概念,即“一个带有标识符的内存单元”,就能够逐步分析出原因所在。
当我们单击“运行”按钮启动这个程序后,计算机会在内存中划分出一块专门的空间,专供这个程序使用。当执行第一行语句“Revenue=Cells(4,3)”时,VBA 意识到这要用到一个名为“Revenue”的变量,也就是一个标识符为“Revenue”的内存单元。但是这个程序刚刚开始运行,内存中还没有任何一个单元被指定为“Revenue”。所以 VBA 就会在这块内存中指定一个“长期居所”,并命名为“Revenue”。有了“房子”就可以办理入住,于是继续执行这个赋值语句,将等号右边Cells(4,3)的内容(10000)保存到“Revenue”指向的内存单元中。保存之后,这个程序的内存单元及“变量名称登记簿”如图2.6所示。
同理,在执行第二行和第三行语句时,VBA 又指定了两个内存单元,分别命名为“Tax”和“ForHer”,并执行赋值操作。在计算等号右边的数值时,还会经常用到“Revenue”等之前已经创建过的变量,这时直接到内存中找到对应“房子”中的内容并取出来使用即可。图2.7就是这两行语句执行结束后内存单元的示意图。
图2.6 创建Revenue变量后的内存空间示意图
图2.7 执行第二行和第三行代码后的内存空间
然而在执行到第四行语句,也就是“Cells(4,4)=Revnue-Tax-ForHer”时,VBA发现要用到名为“Revnue”“Tax”和“ForHer”的三个变量。查询该程序的“变量名称登记簿”发现,“Tax”和“ForHer”对应的内存单元都可以找到,数值分别是2500和6000,但是却没有任何一个内存单元的标识符与第一个变量的名字“Revnue”相同。
所以,与第一次看到“Revenue”这个变量名时一样,VBA 认为这一行代码需要用到一个名为“Revnue”的新变量。于是在内存空间里先指定一个“长期居所”,并命名为“Revnue”,然后继续执行这个赋值语句。而在这个赋值语句中,程序要求将“Revnue”“Tax”“ForHer”三个变量的内容取出来做减法运算,并将结果赋值给D4单元格。“Tax”和“ForHer”所存内容分别是2500和6000,但是“Revnue”这个变量是刚刚创建的,还没有存入数字,那要怎样取出它的内容呢?
对于这个问题,VBA语法进行了特别规定:如果一个变量从未被赋值,那么它的内容默认为“空(Empty)”,如图2.8所示。所谓“空”,就是如果该变量被当作数字使用,那么就认为它的数值是0;如果该变量被当作一个字符串(文本),则认为它的内容是空字符串 。
图2.8 创建Revnue变量后的内存空间示意
因此在执行第四行语句时,等号右边“Revnue”变量的数值被认为是0,而其他两个变量分别是2500和6000,于是最终计算结果就是-8500。
理解了上述过程,也就能够明白类似拼写错误带来的巨大隐患:一方面,程序会将写错的变量默认为空值,导致运行结果出错;另一方面,因为这个过程符合VBA规定,所以程序执行起来完全正常,不会给出任何警告。
显然,我们应该想办法避免这种笔误的发生,解决办法就是使用“强制声明”。
所谓“强制声明”,就是修改VBA的设置,让VBA对变量名加强管理、严格审查。具体来说,将VBA设置为“强制声明”模式后,所有变量都必须做到“先声明后使用”;一旦出现某个从未声明过的变量名,比如因为拼写错误产生的变量名,那么 VBA 就会中断程序的运行,并且提示程序存在错误。
将VBA设置为“强制声明”模式的常用方法,是在一个模块(包括第1章提到的“标准模块”“窗体模块”“类模块”等)的第一行写上“Option Explicit”命令 。注意:这句话并不是写在某个宏(Sub … End Sub)中,而是写在模块之中。
只要一个模块使用了这个命令,模块中的所有程序都将服从“强制声明模式”。不过该命令 仅对一个模块有效 。如果希望一个 VBA 工程中的所有模块都启动强制声明,必须在每个模块的前面都写上“Option Explicit”命令。
启动了强制声明模式之后就要对每个使用到的变量进行声明。声明的方法也很简单,就是使用Dim关键字:如果某行VBA代码中需要用到一个变量,就在这个宏的内部(Sub与End Sub之间)及这行代码之前的任意某行,加入一句形如“Dim 变量名”的代码,如图2.9所示。
图2.9中的代码使用Option Explicit 启动了强制声明,并且使用Dim语句声明了Revenue、Tax和ForHer三个变量。因此在执行前三条计算语句时一切正常,但是在执行最后一条计算语句时,VBA发现其中用到的“Revnue”变量从未使用Dim声明过。由于“强制声明”的硬性规定,VBA 程序中断运行,并弹出一个消息框提示“变量未定义”,这样就可以提醒程序开发人员程序中存在变量名拼写错误的问题。
图2.9 启动强制声明的代码及运行效果(最后一条计算
从图中的Dim语句中还可以看出:可以在一个Dim后面声明多个变量,从而把多个声明写到一行代码之中。具体的格式要求,就是在变量名之间使用半角逗号(最后一个变量名后面不需要逗号)。所以对于图2.9中的演示代码来说,完全可以把前两条Dim语句合并到一行代码中,写作:“Dim Revenue,Tax,ForHer”。
虽然 VBA 允许不使用强制声明,但是鉴于拼写错误存在的隐患,强烈建议大家在初学时就养成强制声明的习惯,以免在程序输出错误结果时摸不到头脑。
前面已经提出这个重要的原则:将每个可能重复出现的数据都抽取为变量。本节我们将结合案例2-1深入领会这个原则的意义。案例2-1的工作表及代码如图2.10所示。
图2.10 案例2-1的工作表及代码
在这个案例中,所有数据都保存在表格第4行的各个单元格中,所以在右边的 VBA 程序中也多次出现了“4”这个数字。假如用户修改了表格格式,要求把所有数据都保存到第5行的单元格中,那么程序也不得不随之修改,总共需要修改5处,如图2.11所示。
图2.11 修改工作表格式后的案例2-1及代码
前面讲过,若在开发代码的过程中大量修改相同内容,很可能因为某处的遗漏或拼写错误导致数据不一致的错误,解决的办法就是应用“把重复数据抽取为变量”这一原则。比如在本例中,既然“4”这个行号重复出现多次,那么就可以定义一个变量(如“r”)来代表第4行,如图2.12所示。
图2.12 使用变量代表行号后案例2-1的代码
这样做之后,即使用户调整了表格的格式,将第4行的所有数据改写到第5行,只需将右侧代码中的“r=4”修改成“r=5”就可以。采用这种方式的好处在于,我们一共只需要修改1处代码,不会出现遗漏和拼写错误,避免了数据不一致的风险。
能够发现重复的东西,并将其抽取成一个元素(如变量),即所谓的“抽取能力”,是从事程序设计乃至软件开发工作最重要的能力之一。本节举的例子虽然简单,但已经能够看出这种“抽取能力”所带来的好处。因此我们希望读者能够将其领会于心,在接下来的学习中时刻注意培养这种能力。