瑞星卡卡安全论坛
TinKingTZW - 2006-8-25 16:45:00
为在书中专门用一节来论述这个问题是小题大做。但不管怎样,你毕竟在建立变量上花费了许
多时间,因此,养成这方面的良好习惯以避免不必要的失败和徒劳的努力是非常必要的。
8.4.1 使用模框( template e)进行变量说明)
在一个文件中存储一个变量说明模框。需要说明新的变量时,你可以把这个模框调入程序,
对其进行编辑以说明新变量。下面是用C 写成的一个模框:
extern * *; /* */
static * *; /* */
* *; /* */
这个模框有几个优点。首先,很容易从中选择出与你要求最接近的行,然后删掉其余各行;
第二,每行中“*”的作用是占有位置,这使得进入每行的编辑位置都非常容易;第三,如果你
忘记了更改“*”,那么一定会产生语法错误,从而起到了提醒的作用;第四,使用模框可以保
持说明形式的一致性;最后,预留的注释空格将提醒你在说明变量时进行注释,这简化了以后
的程序注释工作。
不过,不要以为你必须采用与上例完全相同的模框,尽管按自己的想法去建立自己的模框,
只要它能降低变量说明工作量、增强代码可读性并且使调试更容易就可以了。我的一个朋发给
自己找出了一个注释变量的理由。她名叫Chris,她的模框是这样的:
* * { Chris is a jerk! }
8.4.2 隐式说明
有些语言具有隐含变量说明功能。例如,如果在Basic或Fortran 中你没有说明就使用了某
些变量,那么编译程序将自动说明。
隐式说明是所有语言特性中的最具危害性的特性之一。
如果你用Fortran 或 Basic编过程序,那你一定知道要找出 ACCTNO的值不正确是多么的
困难。而你最后却发现这是由于ACCTNUM被重新初始化为零的原因,如果你所用的语言不要
求对变量做出说明,那么,这种错误是很常见的。
而如果你所用的程序语言要求对变量作出说明,那么在出现上例中的错误之前,你必须首
先先犯两个另外的错误才行。首先,你要错误地同时把ACCTNUM和ACCTNO 放入了子程序
中;其次,你要错误地在子程序的说明段中同时说明了这两个变量。第二个错误是很难出现的,
因此,要再犯上例中的错误几乎是不可能的。要求你对变量进行显式说明的语言其主要优点之
一,便是可以使你在使用变量时更加谨慎。但如果你用的是具备隐式说明功能的语言,那你该
怎么办呢?以下给出了一些指导原则:
关闭隐式说明功能。有些编译程序允许关闭这一功能。比如,在Fortran 中,可以使用一个
叫IMPLICIT NONE的语句,这并不是 ANSI FORTRAN77 中的标准语句,但许多Fortran 中都
有这一扩展。
说明所有变量。即使编译程序不要求,但你每次使用新变量时,都要坚持对它做出说明。
这样并不能避免所有的错误,但是至少可以避免某些错误。
使用命名约定。建立一个关于常用后缀(如NUM和NO)的命名约定,这样,当你想要
TinKingTZW - 2006-8-25 16:45:00
用一个变量时就不会误了两个。
检查变量名。利用你的编译程序或其它工具软件产生的参考表。许多编译程序都会列出你
在子程序中使用的所有变量,从而使你发现ACCTNUM和ACCTNO中的错误。同时,它也会
指出说明但并未使用的变量。
8.5 初始化数据的准则
在编程中,最大的错误原因之一便是对数据不恰当的初始化。开发有效地避免初始化错误
的技术,可以节约大量的调试时间。
不恰当初始化产生的问题,是由某一变量带有你没有预料的初值引起的。这可能是由下述
原因中的任何一种引起的:
· 这个变量未被赋过任何值。其值是在程序开始运行时,由在它的内存中的位置偶然的
值决定的。
· 变量的值已经过时了。变量在某点中被赋过值,但是这个值已不再有效了。
· 变量的一部分被赋了值,而另一个部分没有被赋值。如果你使用的是指针变量,那么
常见的错误是用指针分配了内存,却忘记了对指针所指的变量进行初始化。这与对变
量根本不赋值的效果是一样的。
这种情况往往有几种表现形式。你可能初始化了结构的某一部分而没有初始化另一部分;
也可能会分配内存,然后初始化变量,但指向变量的指针却没有被初始化,这意味着你是随意
地拿一段内存并赋予它们一些值,这段内存可能是存储数据的,也可能是存储代码的,也可能
是被操作系统所占用的。这种错误的表现形式是多种多样的,而且它们之间往往每次都有着天
壤之别。这使得调试指针错误要比调试其它错误困难得多。
下面是如何避免初始化错误的一些准则:
检查输入参数的有效性。初始化的另一种有价值的形式是检查输入参数的有效性。在赋给
输入参数任何值之前,要首先确认它是合理的。
在使用变量的位置附近对其进行初始化。有些程序员喜欢在子程序开始的一个地方对所有
变量进行初始化,以下是用Basic语言写成的例子:
' initialize all variables
Idx = 0
Total = 0
Done = False
...
'lots of code using Idx and Total
...
'code using Done
while not Done
...
TinKingTZW - 2006-8-25 16:45:00
另外一些程序员则尽可能在每一次用到变量的位置附近对其进行初始化,下面也是用Basic
写成的例子:
Idx = 0
'code using Idx
...
Total = 0 ── Total 在使用位置附近初始化
'code using Total
...
Done = False ── Done在使用位置附近初始化
'code using Done
while not Done
...
第二个例子要好于第一个,其中有几个原因。在第一个例子中,当运行到使用Done 的代
码时,Done 很有可能已经被改变过了。即使在初次写程序时不会出现这种情况的话,那么以后
你对其进行修改时,很可能会出现这种情况。第一种方法的另一个问题是,把所有变量集中进
行初始化,会给人以这些参数将在程序中随处都被用到的印象,而事实上Done 只在程序结束
前才被用到;最后,当对程序进行改动时(这是很可能的,起码在调试时要修改),可能会把
Done包含在循环中,从而需要对Done重新进行初始化,在这种情况下,第二个例子中的代码
不会有什么太大的变化。而第一个例子中的代码则可能会产生讨厌的初始化错误。
这也是邻近原则的一个例子:把相关的操作放在一起。这一原则也适用于把对代码的注释
放在相应的代码附近,把循环设定代码放在循环附近,把注释放在非循环代码中。
要特别注意计数器和累加器。变量i、j、k和Sum、Total等往往代表计数器和累加器。常
见的错误是在下次用到它们时,忘记了对其进行清零操作。
查找需要重新进行初始化的地方。首先问自己一下有哪些地方需要重新进行初始化。重新
初始化的原因可能是由于变量在循环中被用过多次,也可能是由于变量在对子程序的调用中间
要保持原值且需清零。如果需要重新初始化的话,要确保初始化语句是在被重复的代码段中。
对命名常量只初始化一次,用可执行代码初始化变量。如果要用变量来模仿命名常量,那
么在程序的开始对它们进行一次初始化是可以的,在Fortran 中可以用Data 语句来做到这点。
在其它语言中,可以在Startup()子程序中初始化它们。
应在使用变量的位置附近的可执行代码对其进行初始化。最常见的改动之一是改动某一子
程序,变一次调用它为多次调用它。在DATA 语句或Startup()子程序中被初始化的变量,在程
序中不能被重新初始化。
按照所说明的对每个变量进行初始化。虽然其地位无法与在变量使用位置附近对其初始化
相比,但是按照所说明的初始化变量,仍然是防错性编程中的一件有力工具。如果你养成习惯,
那就可以有效地防止初始化错误。下例就保证了student_name 在每次调用含有它的子程序时,
都将被重新初始化。
TinKingTZW - 2006-8-25 16:46:00
char student_name[NAME_LENGTH+1] = {'\0'}; /* full name of student */
利用编译程序的警告信息。许多编译程序都会对使用未初始化的变量进行警告。
设置编译程序使其自动初始化所有变量。如果你的编译程序支持这种选择项,那么让它对
所有变量进行初始化是非常简单的。但是,由于对编译程序的依赖性。当把程序移到另一台编
译程序不同的机器上时,则有可能产生问题。这时要确保你对所用的编译程序进行了注释,否
则是很难发现程序对编译程序有依赖性。
使用内存存取检查程序来查找无效的指针。在某些操作系统中,操作系统代码会自动查找
无效指针,在其它情况下,就只有依靠自己了。不过,你也不一定非得靠自己。可以买一个内
存存取检查程序来检查程序中的指针操作。
在程序开始初始化工作内存。把工作内存初始化到某一个值可以帮助发现初始化错误。可
以采取以下任何一种方法:
· 可以用程序内存填充程序来赋予内存某一已知值,这个值用0比较好,因为它保证未
初始化指针指向低内存,比较容易发现它们,在80X86 处理器中,16 进制0CCH 比
较好,因为它是机器的断点中断码。如果是在调试中运行数据而不是代码,你就会进
入断点。使用值0CCH 的另一个优点是在内存转储中它很容易辨认。
· 如果你使用的是内存填充程序,可以每次改变一个填充值,用这种方法来检查一下运
行环境下隐藏的错误。
· 可以用程序在软件运行时初始化工作内存。虽然使用内存填充程序的目的是把错误暴
露出来,但这种技术的目的则是隐藏它们。通过每次把工作内存由相同的值充满,可
以保证在每次运行时程序不会被启动时的随机因素干扰。
8.5.l 检查表
数据生成
生成类型
· 程序中是否对每种可能变动的数据都使用了不同的类型?
· 类型名称是面向客观世界中的实体类型而不是面向程序语言中的类型吗?
· 类型名称是否有足够的表现力来帮助说明数据?
· 避免重新定义已定义的类型了吗?
说明数据
· 是否使用了模框为简化数据说明工作?并用其来统一说明形式?
· 如果所用的语言支持隐式说明,是否采取了补救措施?
初始化
· 是否每个子程序都对输入参数进行了检查以保证其有效性?
· 是否在使用变量的位置附近对其进行初始化的?
· 是否恰当地对计数器和指针进行了初始化?是否在必要时对其进行了重新初始化?
· 在反复执行的代码段中,是否对变量进行了恰当地重新初始化?
· 用编译程序编译代码时是否是无警告的?
TinKingTZW - 2006-8-25 16:46:00
8.6 小结
· 在你的工具箱中需要一张全部数据结构的清单,以便用最合适的方法处理每一种问题。
· 建立自己的数据类型,以增加程序的可变动性,并使其成为自说明的。
· 数据初始化很容易产生错误,因此应采用本章推荐的技术来避免由意外初始值所产生的错
误。
TinKingTZW - 2006-8-25 16:46:00
第九章 数据名称
目录
9.1 选择名称
9.2 特定数据类型命名
9.3 命名约定
9.4 非正式命名约定
9.5 匈牙利命名约定
9.6 短名称
9.7 要避免的名称
9.8 小结
相关章节
生成数据:见第8 章
使用变量时要考虑的问题:见第10 章
说明数据:见19.5 节
格式化数据说明:见 18.5 节
本章论述的是如何对数据进行恰当命名。这个主题对有效编程是非常重要的,但是在进行
恰当命名时要考虑的几十个问题中,我却只读过对其中两到三个的讨论。大多数编程课本只用
几段来论述名称缩写的选择问题,讲的也都是些关于这方面的陈词滥调,而完全寄希望于你自
己去解决问题。对于另一个极端,使用过多关于命名的信息将你淹没,我认为简直就是犯罪。
9.1 选择名称
在给变量命名时,你不能像给小狗起名时那样仅仅挑有趣或好听的名字。但除了实体不同
之外,变量与变量名和狗与狗名实际是同一回事。
因此,一个变量的好坏在很大程度上是由其名字决定的。所以在选择变量名时一定要谨慎。
以下是一段使用不恰当变量名的例子(用C 写成);
X = X - XX;
XXX = Aretha + SalesTax( Aretha );
X = X + LateFee(X1,X)+ XXX;
X = X + Interest(X1,X);
这段代码是干什么的?X1,XX 和XXX 代表的是什么呢?Aretha 的意思是什么?如果某
人告诉你,这段程序是根据收支平衡和新的购买情况来计算顾客和全部账单的,那么你将使用
什么变量来代表那些新购买活动呢?
TinKingTZW - 2006-8-25 16:47:00
以下是一个同样内容的新程序,这里,上述问题就很容易回答了。
Balance = Balance - LastPayment;
MonthlyTotal = NewPurchases + SalesTax( NewPurchases );
Balance = Balance + LateFee( CustomerID, Balance ) + MonthlyTotal;
Balance = Balance + Interest( CustomerID,Balance );
通过比较两段代码,我们发现好的变量名是易读、易记而且是恰当的。可以使用几条通用
原则来达到这些目的。
9.1.1 命名时要考虑的最重要问题
在给变量命名时,考虑的问题是变量名称是否完全而又准确地描述了变量所代表的实体。
一个有效的方法是用自然语言(如英语)将变量所代表的实体描述出来。往往这一描述本身便
是最好的名称,因其不含缩写它很容易读懂,又由于它是对实体的全面描述。因此不会与其它
实体相混淆,而且因为它与概念相近,所以也很容易记。
比 如 要 用 一 个 变 量 来 代 表 美 国 奥 林 匹 克 队 的人数,你可以对其命名为
NumberofPeople0nTheUSOlympicTeam。对代表自1896 年以来国家队在奥林匹克运动会上最多
得分的变量可以用MaximumNumberofPointsSince1986作为其名称。而用InterestRate 或Rate作
为代表目前利率的变量名要比用r或x 好得多。
你应该可以发现这些名字的两个特点。首先,它们很容易解释。事实上,你根本不需要解
释,它们的意思是一目了然的;第二条则是其中有些名字很长,长得根本不实用。稍后我们将
讨论这一问题。
下面是一些变量名的例子,同时列出了好的和坏的。
变量代表的实体 恰当的名称 不恰当的名称
火车速度 Velocity、TrainVelocity、VolocityInMPH VELT,V,TV,X,X1
今天日期 CurrentDate、CrntDate CD,Current,C,X
每页行数 LinesPerPage LPP , Lines , X , X1
CurrentDate 和CrntDate是恰当的名字,因为它们全面准确地描述了“今天日期”这一含义。
而且,它们用的是明显的单词。程序员们往往忽视使用平常的词,而事实上这是最简单的解决
办法;CD 和C 太简短了,说明不了任何问题;Current 并没有说明现在的什么?是总统还是赌
马的结果?Date像是一个不错的名字,但究竟是什么时候的Date?是基督出生那天吗?X,X1
在任何情况下几乎都是不好的名字,因为它们通常都是代表未知量的,如果你要它代表其它实
体时,往往会引起误会。
9.1.2 面向问题
一个好记的名字通常是面向问题而不是解决问题的。一个恰当的名字往往说明是“什么”
而不是“怎样”。通常,如果一个名称指向计算的一方面而不是指向问题,那么可以认为之是个
“怎样”而不是“什么”的名称。要避免使用这种名称而要使用面向问题的名称。
雇员数据的记录可称为InputRec或EmPloyeeData,InputRec 是一个计算机术语,指的是输
入和记录;EmPloyeeData指的是问题域而不是计算方面。同样,对一个表示打印机状态的变量
TinKingTZW - 2006-8-25 16:47:00
来说,BitFlag 要比PrinterReady专业化得多。在计帐系统中,CalcVal要比Sum更加专业化。
9.1.3 最佳名称长度
名称的最佳长度应介于MaximumNumberOfPointsSincel896 和x 之间。太短 的名字 往 往
表示不出完整的意思,而用x1和x2来表示的问题,即使你能找出x1 代表的是什么,也很难发
现x1 和x2 之间有什么联系。太长的名字难以输入,而且会对软件的可视结构产生破坏作用。
Gorla 和Benander 在1990 年发现当变量名长度在10 到16 个字母时,COBOL程序最容易
调试。而变量名称长度在8 到20 个字母时,程序调试容易几乎是一样的。这一准则并不是说你
必须把所有变量名长度都限制在9到15或10到16个字母之间。但这确实要求你检查一下,程
序中变量名长于这个标准的那些变量,确保清楚程度符合需求。
以下是对一些变量名的评价,或许会给你一些启迪:
名称太长的:
NumberofPeopleOnTheUS0lympicTeam
NumberOfSeatsInTheSaddleDome
MaximumNumberOfPointsSince1896
名称太短的:
N,NP,NTM
N,NS,NSISD
M,MP,Max,Points
合适的:
NumTeamNumbers,TeamMbrCount,cTeamMbrs
NumSeatslnDome,SeatCount,cSeat
MaxTeamPoints,Record Points,cPoints
9.1.4 变量名的作用域
短的变量名总是不好的吗?当然不总是。当你用i 作为一个变量名时,这个较短的长度就
可以说明某些问题,比如,这个变量是一个临时变量,只在有限的操作范围内才是有效的。
当程序员读到这样一个变量时,他应该能猜到这个变量只在几行内使用。如果你把某个变
量称为“i”,你就等于在说“这个变量仅作为循环计数器或数组索引数使用,在这几行代码外
没有任何意义”。
由W.J Hansen 进行的一项调查表明,较长的名字适于较常使用的变量或全局变量。而较
短的名字则适于局部变量或循环变量(1980)。但短名字会产生许多问题,有些程序员把避免使
用它们作为防错性编程的准则。
9.1.5 变量名中的计算值限定词
许多程序中含有带有计算值的变量:totals,averages,maximums等等。如果你用限定词诸
如(Ttl,Sum,Avg 等)来改动变量,那要把它们放在最后。
这种方法有几个优点。第一,变量名中最有意义的部分——表达变量名大部分意义的部分,
TinKingTZW - 2006-8-25 16:47:00
被放在最前面;第二,通过建立这种约定,在同一程序中同时使用TtlRevenue 和 RevenueTtl
会避免因此而引起的混淆;第三,像RevenueTtl,ExpenseTtl,RevenueAvg 和ExpenseAvg 这
样的一系列名字有一种令人愉快的相似感。而像TtlRevenue,ExpenseTtl,RevenueAvg 和
AvgExpense则不具备这种相似性。最后,一致性可以改善可读性并简化了维护。
这个准则的例外是Num放在变量名前面时表示全部,如NumSales表示的全部商品的数额;
当Num 放在变量名后面时,它表示下标,SaleNum 表示现在标出的商品是第几个。Numsales
末尾的S也是表示两者意义区别的一个标志。但是,由于过分频繁地使用Num会产生混淆,所
以往往用Count来表示总数,而用Index 来表示下标。这样,SalesCount表示的是卖出的总数,
而Saleslndex 则指的是卖出的某一种特定商品。
9.1.6 变量名中的反义词
应恰当使用反义词。使用关于反义词的命名约定可以帮助保持连续性,同时也可以提高可
读性。像first/last这样一组反义词是很容易理解的,但first/end就有些让人摸不着头脑了,以下
是一些比较容易理解的反义词。
add/remove begin/end create/destroy
insert/delete first/last get/release
increment/decrement put/get up/down
lock/unlock min/max next/previous
old/new open/close show/hide
source/destination source/target start/stop
9.2 特定数据类型命名
除了对数据命名通常需要考虑的一些问题之外,对特殊数据类型必须给予特殊的考虑。本
书将论述循环变量、状态变量、临时变量、逻辑变量、枚举变量和命名常量的命名问题。
9.2.l 循环变量命名
由于几乎每个程序中都含有循环,因此,对循环变量的命名问题加以专门考虑是十分必要
的。
简单循环中的循环控制变量的名字往往也是十分简单的,常用的是i、j、k 等。下面是一个
Pascal循环的例子:
for i:=FirstItem to LastItem do
Data := 0;
如果这个变量还要在循环外使用,那么应该用比i、j、k 更能说明问题的名称。比如,你正
从文件中读取记录,并且要知道已经读取了多少个记录,那么用 RecordCount 作为其名称似乎
更合适些,请看下面的这个Pascal程序:
RecordCount:= 0
while not eof(InputFile) do
TinKingTZW - 2006-8-25 16:47:00
begin
RecordCount := RecordCount + 1;
ReadLn ( Score [ Recordcount ] )
end;
{ lines using RecoudCount }
…
如果循环体长度较长的话,那就很容易使人忘记它代表的是什么,因此最好给循环控制变
量一个富有意义的名字。由于经常进行更改,扩展和拷贝等代码到另一个程序中,因此,大多
数有经验的程序员都避免用i、j、k 这类的名字。
使循环体变长的一个常见原因是嵌套。因此,对于一个有多重嵌套的循环,最好给循环控
制变量以较长的名字以便改善其可读性:
for TeamIndex := i to TeamCount to begin
for EventIndex := 1 to EventCount[ TeamIndex ] do
Score[ TeamIndex, EventIndex ] := 0
end;
通过精心对循环控制变量进行命名,可以避免它们的交叉:当你想用i 时误用了j,或者
想用j 时却又误用了i。这样做也可以使对数组的存取操作更为明了。Score [ TeamIndex,
EvenIndex ]显然要Score[i,j]更能说明问题。总之,应尽量避免使用 i、j、k 来命名。如果不得
不使用它们的话,那除了把它们用作循环控制变量之外,最好不再用作别的变量名。这一约定
是众所周知的,如果不遵守它只会引起别人的困惑。
9.2.2 状态变量命名
状态变量描述的是程序所处的状态。下面论述了它们的命名原则。
用比flag 更好的名称来命名变量,最好不用flag 作为状态变量的名字。之所以要避免使用
flag 作为标志名称,是因为它不能告诉你关于这个标志的任何信息。为了清楚起见,应该给标
志赋值,并且用枚举类型、命名常量或当作命名常量使用的全局变量对其进行测试。下面是一
个在C 语言中不恰当命名标志的例子。
if( Flag )...
if( StatusFlag & 0x0F )...
if( PrintFlag == 16 )...
if( ComputerFlag == 0 )...
Flag = 0x1;
StatusFlag = 0x80;
PrintFlag = 16;
ComputerFlag = 0;
如果这个程序不是你写的,而且也没有注释告诉你的话,你是无法知道类似statusFlag =
0x80 之类的语句到底是要干什么的,而且你也无法知道Statusflag和0x80 到底是什么意思。以
TinKingTZW - 2006-8-25 16:48:00
下是功能与内容相同但清楚得多的程序:
if ( DataReady )...
if ( CharacterType & PRINTABLE_CHAR )...
if ( ReportType == AnnualRpt )...
if ( RecalcNeeded == TRUE )...
DataReady = TRUE;
CharacterType = CONTROL_CHARACTER;
ReportType = AnnualRpt;
RecalcNeeded = FALSE;
显然,第二个例子中CharacterType = CONTROL_CHARACTER 的意义要比第一个中
StatusFlag = 0x80 的意义要清楚得多。同样,第二个例子中的条件语句if ( ReportType ==
AnnualRpt ) 也显然要比第一个中的if ( PrintFlag == 16 )清楚得多。第二个例子表明,你可借
助预先命名的常量或枚举类型来使用这种方法。以下是如何利用枚举类型和命名常量来设置上
例中用到的值,仍用C 来实现:
/* values for DataReady and RecalcNeeded */
#define TRUE 1
#define FALSE 0
/* values for CharacterType */
#define LETTER 0x01
#define DIGIT 0x02
#define PUNCTUATION 0x04
#define LINE_DRAW 0x08
#define PRINTABLE_CHAR ( LETTER | DIGIT | PUNCTUATION | LINE_DRAW )
#define CONTROL_CHARACTER 0x80
/* values for ReportType */
Typedef enum { DailyRpt,MonthlyRpt,,QuarterlyRpt,
AnnualRpt,AllRpts}REPORT_TYPE_T;
当你发现自己“侦破”了一段代码时,应该考虑对变量重新命名。侦破一桩凶杀案是可以
的,但你不应该去“侦破”一段代码。代码应该是具有良好可读性的。
9.2.3 临时变量命名
临时变量用来保存中间运算结果,如用作暂时保留某个位置或保留内务操作值。通常把它
们叫做TEMP 或X 等没有什么意义的名字。临时变量的使用往往标志着程序员还没有完全理解
TinKingTZW - 2006-8-25 16:48:00
程序。而且,由于名义上给了它一个“临时”的状态,因而程序员们在处理它们时往往会采取
漫不经心的态度,从而增大了出错机会。
要警惕“临时”变量。通常,暂时保留某些值是完全必要的。但如果在你的程序中出现很
多临时变量的话,则说明你对它们在程序中作用和使用它们的目的还不清楚。先让我们看一下
下面用C 语言写成一个例子:
/* Compute root of a quadratic equation.
This assumes that ( b^2 - 4 * a * c ) is positive. */
Temp = sqrt ( b^ 2 - 4 * a * c );
root[0] = ( -b + Temp )/( 2 * a );
root[1] = ( -b – Temp )/( 2 * a );
把由公式sqrt ( b^2 - 4 * a * c ) 计算出来的值储存起来是个不错的想法,尤其是在后面还有
两处用到了它的情况下。但是用TEMP作为它的名称不能告诉你关于这个变量意义的任何信息。
一个较好的作法是下面这个例子:
/* Compute roots of a quadratic equation.
This assumes that ( b^2 – 4 * a * c )is positive */
Discriminant = sqrt ( b^2 - 4 * a *c );
root[0] = ( -b + Discriminant )/( 2 * a );
root[1] = ( -b - Discriminant )/( 2 * a );
事实上两段代码是完全一样的,但是通过采用更准确也更能说明问题的变量名,大大改善
了其可读性。
9.2.4 逻辑变量命名
以下命名逻辑变量的几条准则:
记住一些典型的逻辑变量名。以下是些非常有用的逻辑变量名:
Done。用Done 来表示某项工作已经完成了。这个变量可以表示子程序或循环是否已经完
成。当某项工作没有完成时,把Done 的值赋为False;当某项工作已经完成时,把Done 的值
赋为True。
Error。用Error 来表示发生了错误。当没有错误时将Error 的值赋为False;当有错误时将
其
赋为True。
Found。用Found 来表示是否找取了某个值。当搜寻数组来查找某一值或搜寻某一文件表
查找某一雇员的识别卡时,没有找到某一值时将其值赋为False,而一旦找到这个值。则把Found
值赋为True。
Success。用Success来表示某一操作是否已成功地完成。当某一程序无法完成时,将Success
的值置为False;而当某一操作已经完成时,将Success的值置为True。如果可能的话,可以用
比Success 更准确更具有表达力的名称来代替它,用这个新名称应可以精确地表达出到底是什
么已 成功地完成了。比如当某一程序完成处理后就可以认为是成功时,就可以用
Processingcomplete 来代替Success。如果当找到某一值就可以为程序是成功的时,可以用Found
来代替它。
TinKingTZW - 2006-8-25 16:48:00
用旧含非真即假的名字来给逻辑变量命名。像Done或Success等之所以是恰当地逻辑变量
名,是因为它们的状态是非真(True) 即假 (False)的。某项工作或者完成了或者没完成;或者
是成功的或者不成功,不会有第三种状态。而类似Status 或SourceFile 等则是不恰当的名字,
因为看不出它们的状态是非真即假的。如果Status 的值是True,那它是否意味着某种物质有状
态呢?任何物质都有状态。或者说当其值是True 时,意味着某种物质的状态很好,而为False
时则意味着状态不好?仅从Status这个名称是无法回答这些问题的。
为清楚起见,最好用Error 或Status_OK 等来代替Status;用SourceFileAvailable 或
SourceFileFound来代替Source。
有些程序员喜欢用Is 用为逻辑变量名的前缀,于是变量名就成了一个问句:IsDone?
IsError?IsSuccess?当用True 或False 来回答上述问题时,就等于给变量赋了值。这样做的好
处是可以避免那些不恰当的名称。如IsStatus显然没有任何意义。
使用肯定的逻辑变量名。否定式的变量名如NotFound、NotDone和Notsuccessful 等在“非”
运算中是很难读懂的,如:
if not NotFound
这类名字应该用Found,Done,Successful等来代替,以方便“非”运算。
9.2.5 枚举类型命名
当使用枚举型变量时,可以通过使用相同的前缀或后缀表示某一类型的元素都是属于同一
组的,如下面的这段Ada 程序所示:
type COLOR is ( COLOR_RED, COLOR_GREEN,COLOR_BLUE );
type PLANET is ( PLANET_ERATH, PLANET_MARS, PLANET_VENUS );
9.2.6 常量命名
对常量来说,应该用它所代表的抽象实体而不是数值来命名。FIVE 是一个很不恰当的常量
名称(不管它代表的数值是否是5.0);CYCLES_NEEDED 则是个恰当的名称,
CYCLES_NEEDED 可以等于5.0也可以等于6.0,而Five = 6.0则是个荒唐的语句。由于同样的
原因,BAKERS_DOZEN 也是很不恰当的变量名,而MAX_DONUTS 则要恰当得多。
9.3 命名约定
有些程序员往往坚持标准和约定,这是有其原因的。然而,某些原则和约定过于刻板而且
往往是无效的,这只会破坏你的创造力和程序的质量,这实在是个不幸,因为有效的标准和约
定是你的工具箱中最为有效的工具之一。这一节将讨论为什么及什么时候和怎样建立你自己的
命名标准。
9.3.1 为什么要建立约定
约定可以带来如下好处:
· 它们可以使更多的东西成为独立的。通过做出一个总体决定而不是许多局部决定,你
TinKingTZW - 2006-8-25 16:48:00
可以把精力放到更重要的程序特性上。
· 它们可以帮助借鉴其它项目的经验并移植自己的经验。相似的名字可以使你更容易并且更
自信地猜测陌生变量的功用。
· 它们可以使你更快地熟悉新项目的代码。与一套连贯的而不是各式各样、东拼西凑的 代
码打交道显然要容易得多。
· 防止一变量多名。如果没有命名约定,你很可能给一个变量取两个或更多的名字。例如,
你可以把所有点的个数称作PointTl又称作Ttl_Points。这对于你来说可能没什么,因为
你是程序的作者。但对以后要读这个程序的程序员来说,这很可能会使人困惑。
· 弥补语言的缺陷。你可以利用命名约定来仿效命名常量或枚举类型,这一约定可以区分局
部、模块和全局变量,也可以并入编译程序所不支持类型的信息。
· 命名约定还可以强化相关项之间的联系。如果你使用的是结构化数据,那么编译程序会自
动考虑到这一点;如果所用的语言并不支持结构化数据,可以通过命名约定来补充它。像
AddrmPhone 和Name 等名称并不表示变量是相联系的。但如果你决定所有的雇员数据变量
名都要用EmP 作为前缀,那么毫无疑问,EmpAddr、EmpPhme 和EmpName就是联系的变量了。
通过建立伪结构化数据,弥补语言的缺陷。
关键是有约定总比没有约定好,哪怕约定是随意的也罢,约定的效力并不是由某一确定的
约定,而是由约定存在决定的,它可以增加代码的结构并减少你的担心。
9.3.2 什么时候使用命名约定
关于这个问题并没有一成不变的答案。但在以下几种情况下,使用命名约定我认为还是值
得的。
· 当同时有几个程序员从事一个项目时。
· 计划把程序交给另一个程序员进行修改和维护时(这时命名约定是必不可少的)。
· 当你的程序将由其它程序员来评审时。
· 当程序规模过大,需要按部分来考虑它时。
· 当一个项目中要频繁使用某些不常见的词汇,而又想开始编码时。
命名约定总是有益的,上述准则可以帮助你确定在某一项目中命名约定使用的广泛程度。
9.3.3 正式程度
不同约定的正式程度是不同的。一个简单的约定可能只有一句话,“使用有意义的名称”。
略微正式的约定将在下节讨论。更正式些的约定将9.5 节中论述。一般来说,约定正式程度是
由从事一个程序的程序员人数、程序的规模和程序的预测生存期决定的,在小型程序中,严格
的约定往往是不必要的。如果是需要几个人协作(可能是在开始,也可能是在程序生存期内的
某个时间)的程序,那么可读性往往要依赖正式的命名约定来保证。
9.4 非正式命名约定
绝大多数项目都采用如下所述的非正式命名约定。
TinKingTZW - 2006-8-25 16:49:00
9.4.1 与语言无关的约定准则
以下是一些与语言无关的约定准则:
标识全局变量。常见的编程问题之一是误用全局变量。可以在所有的全局变量前面都加
上g_作为前缀来解决。比如看到g_Running Total时,程序员就会知道这是一个全局变量,从而
把它作为全局变量对待。
标识模块变量。模块变量是在模块内部供几个子程序使用的变量。要能清楚地表明它既不
是全局变量也不是局部变量。这可以用在变量前加m_作为前缀来解决。在C 中,你可以通过在
任何子程序外说明Static 变量来建立模块层次的数据。这样的变量对文件中子程序来说都是可
用的,但文件外的子程序则无法使用。
标识类型定义。类型的命名约定需要有两个功能:它们要明确地指出某一名称是类型名称,
同时要可以避免类型名称与变量名称相冲突。为了达到这两个要求,使用前缀或后缀不失为一
个好办法。在 Pascal 中,可以使用小写的_t 来表示类型名称,例如 Color_t 或 Menu_t。在C
中用这种办法稍有些困难,常见的办法是用大写字母组合如COLOR或MENU来表示类型名,但这
可能与命名预处理程序常量相混淆。而“_t”这一约定已经被标准类型size_t 所采用了,因此可
以用“_T"表示类型名称,如COLOR_T 和MENU_T。
标识命名常量。需要对命名常量加以标识,以便可以使你知道是在用一个变量(其值可能
变动)还是在用一个命名常量给某个变量赋值。在Pascal或C 中,你还可能是在用函数给某一
变量赋值。这些语言,并不要求函数使用括号。而在C中,即使是不带参数的函数也必须使用
括号。
给命名常量命名的一种办法是“_C”作为其名称的后缀。在Pascal中,用这种方法可以产
生出像MaxRecs_C或MaxlinesPerPage_ C之类的名字。在C 中,你可以用“_C”作为其后缀。
标识枚举类型。与命名常量同样的原因,枚举类型也需要被标识出来。即将其与变量、命
名常量和函数加以区别。常用的方式是用“_e”或“_E”作为后缀。
标识输入参数。有时输入参数会被错误改动。在像C 或Pascal这样的语言中,当一个被改
变过的值返回调用子程序时,必须予以明确地说明。在C 中,这是用“*”说明的,在Pascal
中用的是VAR,Ada语言中使用的是out限定词。在其它语言如Fortran 中,如果你改变了一个
输入值,那么不管你是否愿意,它都将被返回,但假如你建立了命名约定,在约定中规定只用
于输入的参数前面要采用IP 作为前缀,那么当发现在等号左边出现了带有IP 前缀的变量时,
你就可以知道发生了错误。比如在程序中看到IPMAX = IPMAX+l语句,就可以立刻认定它是
错误的,因为前缀IP 表明IPMAX 的值是不允许变动的。
对名字作格式化以增强可读性。增强可读性常见的两项技术是用分隔字符或大写字母将单
词分隔开来。例如,GymnasticsPointTotal 或Gymnastic_Point_Total 的可读性显然是要强于
GYMNASTICSPOINTTOTAL。C、PASCAL、Ada 和其它语言都允许大小写混用的方式。C、
Pascal和其它语言都允许使用的下划线“_”作为分隔符。
尽量不要混合使用这两项技术,那样会降低可读性。如果坚持采用其中任何一种,那么你的
代码的可读性将大为改观,关于变量名第一个字母是否应该大写的问题,激烈的争论已经持续
了很长时间(是TotalPoints 好还是totalPoints好?)。但是只要保持一致性,我认为二者区
别实际上并不大。
TinKingTZW - 2006-8-25 16:49:00
9.4.2 与语言有关的命名约定
应当遵守所使用语言的标准命名约定。
你可以发现许多书中都讲述了语言的形式准则。关于C、Pascal 和Fortran 的准则将在下
面予以论述。
C 约定
有些命名的约定是只适用于C的,可以在C中使用这些约定,也可以改变它以使其适应其
它语言。
· c 和ch 是字符变量
· i 和j 整型下标
· n 是数量
· p 是指针
· s 是字符串
· 预处理程序宏指令是以全部大写来表示的,这通常扩展到包含typedef
· 变量和子程序名称都是小写的
· 下划线“_”用做分隔符
这些是在UNIX 操作系统下用C 语言编程的一些通用约定。在不同的环境下,这些约定是
有区别的。在Microsoft Windows环境下,C程序员们往往愿意使用匈牙利命名约定,并且对变
量名称是大小写混用的。在Macintosh 环境下,C 程序员们往往乐于使用类Pascal 的命名约定
给子程序命名。因为Macintosh工具箱和操作系统子程序都是为Pascal接口设计的。
Pascal 约定
Pascal 中只有几条特殊的约定,你可以在Pascal 中使用它们,也可以改进它们以适应其它
语言需要。
· i、j和k 是整型下标
· 变量和子程序名称是以大小写混用的形式出现的
Fortran 约定
Fortran 有一些语言本身固有的命名约定,在Fortran 中可以使用它们。但我想如果不把它们
扩展到其它语言的话,全世界都会因此而感激你的。
· 以字母I到N 开头的变量名是整型的
· I、J 和K只能作为循环控制变量
· X、Y 和 Z是浮点数
9.4 .3 命名约定举例
当命名约定准则长达几页时,它们看起来是非常复杂的。事实上,命名约定是完全不必要
复杂到可怕程度的,你可以改进它们以适应你的需要。变量名应包括三个方面的信息:
· 变量内容(它代表的是什么)
TinKingTZW - 2006-8-25 16:49:00
· 变量数据类型(整型、浮点等)
· 变量在程序结构中的位置——例如,定义变量的模块名称或者一个表示变量是全局的
前缀。
以下是C和Pascal中采用前面讲过的命名原则进行命名的一些例子。我并不是想把这些约
定推荐给你,而是想给你一些变量名应包括哪些信息的想法。
Pascal 命名约定示例
约定类别 约定内容
LocalVariable 局部变量名是大小写混用的。这个名称应与潜在的数据类型无关,且应
指出这个变量代表的到底是什么
RoutineName() 子程序也是大小混用的(具体讨论见5.2 节)
m_ModuleVariable 仅供某一模块子程序使用的变量以m_作为前缀
g_GlobalVariable 全局变是用g_为前缀
Constant_C 命名常量以_C 作为后缀
Type_t 类型以_t作为后缀
Base_EnumeratedType 枚举类型是以其基本类型的助记符作为前缀的,如Color_Red,
Color_Blue
C 命名约定示例
约定类别 约定内容
GlobalRoutineName() 公用程序名称是大小写混合使用的
_FileRoutineName() 供某一个模块专用的子程序以下划线作为前缀
LocalVariable 局部变量名是大小写混用的。其名称应与数据类型无关且应指明变
量代表的究竟是什么
_FileStaticVariable 模块(文件)变量以一个下划线作前缀
GLOBAL_GlobalVariable 全局变量是以定义它的模块(文件)名称的全大写形式助记符作为
前缀的,如SCR_Dimension
LOCAL_CONSTANT 仅供某一子程序或模块 (文件) 专用的命名常量是全大写的
GLOBAL_CONSTATNT 全局命名常量名字是全大写的,且以它的模块(文件)名称
助记符的大写形式来作前缀的,如SCR_MAXROWS
TYPE 类型定义是全大写的
LOCAL_MACRO() 仅供某一子程序或模块(文件)专用的宏定义是大写的
GLOBAL_MACRO() 全局宏定义是大写的且以它的模块(文件)名称助记符大写形式为
前缀
9.5 匈牙利命名约定
匈牙利命名约定是一整套对子程序和变量进行命名的详细约定。这一约定广泛应用在C 语
言中,特别是在Microsoft Windows环境下用C编程时,之所以称其为“匈牙利”是因为遵循这
一 约定的命名看起来像是外文。而且其发明者Charles Simonyi原来是匈牙利人。
匈牙利命名主要包括三个部分:基本类型、一个或更多的前缀、一个限定词。在下面的例
子是在C中采用匈牙利命名约定写成的,你很容易就可以对其加以改进以扩展到其它语言中。
TinKingTZW - 2006-8-25 16:50:00
9.5.1 基本类型
基本类型是指待命名变量的数据类型。基本类型名称通常不指向程序语言中任何一种已定
义的标准数据类型。基本类型是一种更抽象的数据类型,基本类型名称指向的可能是窗口、屏
幕区和字体等实体。在匈牙利名称中只能使用一种基本类型。
基本类型是用你为某一特定程序建立的简写代码来表示的,然后对其作标准化以便在这一
程序中继续使用。以下是在一个文字处理程序中你可能用到的基本类型:
基本类型 含义
wn 窗口
scr 屏幕区
fon 字体
ch 字符(不是C意义上的字符,而是这个文字处理程序
将用来在文件中代表字符数据结构意义上的)
pa 段落
使用匈牙利基本类型时,同时也定义了与基本类型使用同样缩写的数据类型。因此,如果
使用了如上表列出的基本类型,就会看到像这样的数据说明:
WN wnMain;
SCR scrUserWorkspace;
CH chCursorPosition;
9.5.2 前缀
前缀放在基本类型前面并描述了变量的使用。与基本类型不同的是前缀在某种程度上是标
准化的。表9-1 中示出了一级标准匈牙利前缀:
表9-1 匈牙利变量名称前缀
前缀 含义
a 数组
c 数目(如记录、字符等等的个数等)
e 数组的元素
g 全局变量
h 处理
i 数组下标
m 模块层次上的变量
p(lp,np) 指针(长指针、近指针——对于Intel 机器)
前缀是小写的,并且放在变量名中基本类型的前面。如果需要的话,可以把它与基本类型
或与其自身组合使用。例如,一个表示窗口的数组,可以用“a”来表示它是个数组,用“wn”
表示窗口,因此可以用“awn”来作这个数组的名称;而对窗口的操作可称为hwn;cwn是窗口
的数目;cfon是字体的种类等等。
9.5.3 限定记号
匈牙利名称的最后一个要素是限定词。限定调是名称的描述部分。没有使用匈牙利约定
TinKingTZW - 2006-8-25 16:50:00
时也可利用这部分来补偿。在上前面给出的例子——wnMain、scrUserWorkspace 和
chCursorPosition 中,Main、UserWorkspace和CursorPosition 都是限定词。在本章前面给出的关
于变量命名的准则也适用于限定词。
除了自己生成的限定词以外,匈牙利约定中还对容易在处理时产生混淆的概念给出了标准
化限定词。在表9-2 中,表示了些标准限定词。
表9-2 匈牙利约定中的标准限定词
限定词 含义
Min 数组或其它表中绝对的第一个元素
First 数组中需要进行处理的第一个元素。First含义与Min 相近,但First是相
对操作的第一个,而Min 则是指数组本身的第一个元素
Last 在一个数组中需要最后进行处理的元素。Last是First的反义词
Lim 在一个数组中需要处理的元素上界。与Last一样,Lim也是First的反义
词,但与Last 不同的是,Lim 代表的是数组的不完全上界;而Last 代表
的则是最后一个元素。通常,Lim等于Last 加1
Max Max 指的是数组或表列中的绝对最后一个元素而不是相对操作的最后一
个元素
限定词能够而且应该与基本类型和前缀联合使用。例如,PaReformat 指的是要重新格式化
的段落;apaRefotmat 指的是要重新格式化的段落数组。而ipaReformat 则指的是需要重新格式
化的段落的数量。一个for循环来重新格式化段落的C 程序如下:
for (
theReformat = paFirstReformat;
ipaReformat <= paLastReformat;
ipaReformat++;
)…
可以用PaLimReformat 来代替上例中的变量名PaLastReformat 重写相同的循环,这时,由
于Lim和Last的区别,在确定循环是否结束的检查中,将用“<”来代替“<=”。即用:
ipaReformat < PaLimReformat
来代替
ipaReformat <= PaLastReformat
9.5.4 匈牙利名称举例
以下是利用匈牙利约定产生的变量名。其中变量名的基本类型部分采用的是前面文字处理
程序示例中的基本类型。
变量名 意义
ch 字符变量(并不是C 语言意义上字符,而是指文字处理程序中用来
在文件中代替字符的数据结构)
achDelete 要删去的一个字符数组
ich 字符数组的下标
ichMin 字符数组中绝对第一个元素的下标
TinKingTZW - 2006-8-25 16:50:00
ichFirst 字符数组中第一个需要进行某种操作的元素的下标
echDelete 字符数组中产生的某一元素,如ecbDelete = acbDelete
[icbFirst]的结果
pachInsert 要插入字符数组中的指针
ppach 指向某一字符数组指针的指针
cchInser 要插入字符的数量
cscrMenu 用作菜单屏幕区的数量
hscrMenu 对用作菜单屏幕区的操作
mhserUserInput 用户输入屏幕区的模块层次上的操作(所有在这一模块中的子程序
都可对这一变量进行存取操作)
ghscrMessages 为获得信息而对屏幕区进行的全局操作
注:上表中某些变量名不含限定词。虽然省略限定词是很常见的,但我们并不提倡这样做,
应尽可能使用限定同。
9.5.5 匈牙利约定优点
匈牙利约定与其它命名约定一样,拥有由命名约定所带来的一切共同优点。由于有这样多
的标准名称,因此在任何一个单个子程序或程序中要特殊记忆的名字是非常少的。匈牙利约定
完全可以在不同项目中采用。
匈牙利约定可以使得在命名中容易产生定义的区域变得准确清楚。特别是约定中对First,
Min,Last,Max和Lim的准确区分在实际中是尤其有帮助的。匈牙利约定可以使人对编译程序
无法检查的抽象数据类型进行检查:cpaReformat很可能是错误的,因为cpaReformat 不是数
组,而apaReformat则可能是正确的,因为apaReformat是数组。
匈牙利约定可以在类型不严格的语言或环境中对类型进行说明。例如,在Windows 环境下
编程时,需要你放弃许多类型,这极大地限制了编译程序进行严格类型检查的能力。而建立约
定则可以对环境的这一弱点作出补偿,匈牙利约定还可以使名称更简洁,可以用CMedals 而不
用TotalMedals来代表奖牌的数量,使用pNewScore,而不是用NewScorePtr命名一个新分数指
针。
9.5.6 匈牙利约定缺点
一些版本的匈牙利约定事实上忽视了用抽象数据类型作为基本类型。它们以程序语言中整
型、长整型、浮点数和字符串为基础来建立基本类型。匈牙利约定基本类型事实上是没有什么
价值的,因为它使得程序员陷入对类型进行人工检查的困扰之中,而不是让编译程序对类型进
行更加快速而又准确的检查。
这种形式匈牙利约定的另一个问题是它把数据的意义与其表现联系在一起。比如,说明某
一变量是整型的,把它改为长整型的时,不得不改动这一变量的名称。
匈牙利约定的最后一个问题是它鼓励了懒惰、不含什么信息的变量名的出现。当程序员用
hwnd 来命名对窗口的操作时,往往忽视了他所指的到底是哪种窗口、对话框、菜单还是帮助区
的屏幕?显然用hwndmenu 要比hwnd 清楚得多。以变量的意义为代价来获得对其类型的精确
描述显然是愚蠢的。不过好在可以用加限定词的办法来同时获得完整的意义和精确的类型。
TinKingTZW - 2006-8-25 16:50:00
9.6 短名称
从某种程度上来说,使用短名称的意向是早期语言的遗留物。较老的语言如汇编、Basic和
Fortran 语言把变量名长度限制在七到八个字母之间,从而迫使程序员们不得不使用短名称。在
现代语言如C、Pascal 和Ada 中,事实上可以使用任意长度的名称,因此,此时已没有任何必
要再使用短名称。
如果确实不得不使用短名称的话,要注意某些使用短名称的方法要好于其它的。可以通过
去掉不必要的单词、使用短符号或者使用其它缩写技术来建立恰当的变量短名称。可以使用任
意一种缩写技术。但最好多熟悉几种缩写技术因为没有任何一种方法是万能的。
9.6.1 缩写使用的总体准则
以下是使用缩写的几项准则,其中有某些准则是与其它准则相密的,因此不要试图一次使
用其中所有的技术。
· 使用标准的缩写(常用缩写,如列在字典缩写表中的)。
· 去掉所有的非大写元音字母(如Computer写成Cmptr,Screen写成Scrn,Integer写
成Inter等)。
· 使用每个单词的头一个或头几个字母。
· 截掉每个单词头一至三个字母后面的其余字母。
· 使用变量名中每一个有典型意义的单词,最多可用三个单词。
· 每个单词的第一个和最后一个字母。
· 去掉无用的后缀——ing,ed等等。
· 保留每个音节中最易引起注意的发音。
· 反复交替地使用上述技术,直到变量名长度缩短至8 到20 个字母为止,或者到你所
用语言规定的长度为止。
9.6.2 语音缩写
有些人喜欢根据单词的发音而不是拼写来进行缩写。如把skating 写成sk8ting,brightlight
写成bilite,before 写成b4等等。这更像是在让人们破译密码,因此我不主张采用这种方法,但
作为一种技术,你可以尝试一下“破译”出如下语音缩写的含义:
ILV2SK8 XMEQWK S2DTM8O NXTCd TRMN8R
9.6.3 关于缩写的建议
进行缩写时很容易陷入误区。以下是避免出现这种情况的几条准则:
不要通过拿掉单词中一个字母进行缩写。多敲一个字母费不了多少精力,而由此损失的可
读性却往往是巨大的。如把June写成“Jun”或“July”、“Jul”等都是不值得的。而且如果总是
使用这种只省略一个字母的缩写,很容易使人忘记你是否省略掉了一个字母。因此,或者多省
略几个字母,或者使用全称。
缩写应保持一致性。应坚持使用同一缩写。例如,是使用Num或者No,但不要两者混用。
TinKingTZW - 2006-8-25 16:51:00
同样,也不要时而缩写某一名称,又时而不缩写。比如,假设已经用了Num就不要再同时使用
Number了。
使用容易发音的缩写。如应使用xPos 而不是Xpsn,用CurTotal 而不是ntTtl。可以用能否
在电话中让对方明白的方法来检验缩写名称,如果不能的话,最好换一个比较容易说的缩写。
避免会引起错误发音的组合。如为表示B的结束,应使用ENDB 而不要使用BEND。如果
使用了良好的分隔技术,则不必理会这条准则。如B_END,BEnd 或b_end 都不会导致错误发
音。
近义词来避免命名冲突。在使用短名称常碰到的一个问题是命名冲突——对不同的名称使
用了同一缩写。如fired 和fullrevenuedisbursal,假设对缩写的要求是限定在三个字母的话,那
么很可能两者都会被缩写成fri,从而产生冲突。
避免这个问题的方法之一是使用近义词。如可以用dismissed 来代替fired,用complete
revenuedisturb 来代替fullrevenuedistural便可以解决上面的问题。
用注解表来说明短名称。在只允许使用简短名称语言中,可以通过加入一个注解表来对变
量名称的缩写加以说明,可以把注解表作为代码开始的注释块,以下是一个Fortran 中使用注解
表的例子:
C***************************************************************
C Translation Table
C
C Variable Meaning
C -------- -------
C XPOS X-Coordinate Position ( in meters )
C YPOS Y-Coordinate Position ( in meters )
C NDSCMP Needs Computing ( =0 if no computation is needed;
C =1 if computation is needed )
C PTGTTL Point Grand Total
C PTVLMX Point Value Maximum
C PSCRMX Possible Score Maximum
C***************************************************************
多从读程序者而不是写程序者的角度去考虑变量名称。你可以通过隔一段时间再阅读一下
程序的方法来检查一下是否需要费很大精力才能弄清变量的含义。如果是这样的话,应改进命
名技术来解决这一问题。
9.7 要避免的名称
以下是应该避免的几种名称:
避免容易产生误会的名称或缩写。要确认变量名称是清楚的。FALSE 通常是指TRUE的反
义词,如果用它当“Fig and Almond Season”的缩写显然是不合适的。
避免含义相同或相近的名字。如果可以交换两个变量的名称而不致影响程序,那说明这两
个变量都应重新命名。如Input和InVal,RecNum和NumRecs等每两个的意义都很相近,如果
TinKingTZW - 2006-8-25 16:51:00
在同一程序中同时使用它们来命名两个变量,就非常容易引入某些难以察觉的错误。
避免使用含义不同但拼写相似的名称。如果发现两个变量名称拼写相似但含义不同,那应
对其中一个重新命名或改变缩写技术,比如ClientsRecs和ClientsReps这样的名称就应避免。因
为它们之间只有一个字母不同,而且这一字母很难辨别,两个名称之间至少应有两个字母不同,
或者把不同的字母放在词首或词尾。如用ClientRecords 和ClientsReports 来分别代替上述两个
名称显然要好得多。
避免使用发音相同或相近的名称。如Wrap和rap。因为这将使你在与同事讨论问题时遇到
很多麻烦。
避免在名称中使用数字。如果变量名中的数字的确很有意义的话,应使用数组而不应使用
单个变量。如果使用数组不合适的话,那么使用含有数字的变量名更不合适。比如,应避免使
用FILE1、FILE2和Total1、Total2 这类名字。可以用很多办法来区分两个变量,但不要采用在
变量名末尾加数字的方法。我不敢说应绝对禁止在变量名中使用数字,但起码你应尽全力避免
这种用法。
避免在名称中改写字母。记住单词的拼写是一件困难的事情,而记住改了字母的单词则更
困难。例如,通过改写字母把highlight 写成hilite 以节省三个字母,将使得读者很难记住这个
单词被改写成什么样了,是Hilite,还是Hai-a-lai-t?谁知道呢?
避免常见的容易拼写错的单词。Absense、acummulate、acsend、calender、conceive、defferred、
definate、independance、occassionally、prefered、reciept、superseed等是英语中经常容易拼写错
误的,绝大多数英语词典中都列有常见的容易拼写错的单词。应避免在变量名中使用这些单词,
以避免因拼写错造成程序中的错误。
不要单纯通过大写来区分变量名。如果使用的是可以区分大小的语言,可能会试图用Frd
来代替fired,用FRD来代替final review duty,用frd 来代替full revenue disbursal,应放弃这种
做法。尽管每个名字都是唯一的,但其中每个名称所代替的意义则是任意且容易混淆的。谁能
知道Frd,FRD和frd 分别对应的是fired,final review duty和full revenue disbursal而不是按其
它顺序来对应的呢?
避免使用标准子程序名和已定义的变量名。所有的语言都要求保留其标准子程序名和已
定义变量名,应注意避免使用这些子程序和变量。比如,下面这段代码在PL/I中是合法的,但
若你真的这样的话,那你一定是一个十足的傻瓜:
if if = then then
then = else;
else else = if;
不要使用与变量所代表的实体没有任何联系的名字。像Margaret或Coolie之类的变量名事
实上保证了除你之外没有其它任何人能理解它的。不要用你的女朋友、妻子或朋友的名字作为
变量名,除非这个程序是关于你的男朋友、妻子或朋友的。即使真的是这样的话,你也应该意
识到他们是有可能变化的,因此用通用些的名字如:BoyFriend、wife或FavoriteBeer会更好。
避免使用含有难以辨认字符的变量名称。要知道有些字符是非常相象的,很难把它们区分
开来。如果两个变量名的唯一区分便是一个或两个这种字符,那么你区分这些变量时就会感到
TinKingTZW - 2006-8-25 16:51:00
十分困难。例如,请尝试一下把下表每一组中与其它两个变量名不同的一个找出来。
变量名表
EyeChart1 EyeChartI EyeChart1
TTLCONFUSION TTLC0NFUSION TTLCONFUSION
Hard2Read HardzRead Hard2Read
GRANDTOTAL GRANDTOTAL 6RANDTOTAL
Ttl5 TtlS TtlS
如上表所示,难以区分的字符有"l"和"1"、"1"和"I"、"."和","、"0"和"o";"S"和"5" 、"G"
和"6"等。
9.8 小结
· 恰当的变量名是可读性好的必要条件之一。特殊的变量如循环变量和状态变量要予以
特殊考虑。
· 命名约定可以区分局部、模块和全局变量。同时它还可以区分类型名称,比如可以对
命名常量、枚举类型和变量加以区分。
· 不管你从事的是哪种项目,都应该采用命名约定。所采用的命名约定取决于程序的规
模和从事这一程序的程序员的人数。
· 匈牙利约定是一种非常有效的命名约定,比较适于大规模项目和程序。
· 在现代编程语言中几乎不需要采用缩写技术。
9.8.1 检查表
通用命名约定
· 变量名称是否完全准确地描述了变量代表的是什么?
· 变量名是否指向是客观世界中的问题,而不是关于这问题的用程序语言表达解决方案?
· 变量名称是否是够长,使得你不必破译它?
· 变量名中如果含有计算限定词的话,是否将其放在最后?
· 是否在名称中用Count或Index来代替了Num?
对特殊类型数据的命名
· 循环变量的名称是有意义的吗?(如果循环体较长是嵌套循环的话,应用有含义的名
称来代替i、j、k 之类的名称)
· 是否用更富于含义的名称来代替了被叫作"tempotarg"的临时变量?
· 当逻辑变量的值是"True"时,它的名称是否充分表达了其含义?
· 是否用前缀或后缀来表明了某些枚举类型是一类的?如用Color 来作ColorRed,
ColorGreen,ColorBlue等枚举类型的前缀。
· 命名常量的名称是否是指向它们代表的实体而不是它们所代表的数值的?
命名约定
· 命名约定是否区分了局部、模块和全局数据?
TinKingTZW - 2006-8-25 16:51:00
· 命名约定是否对类型名称、命名常量、枚举类型和变量进行了区分?
· 在不支持强化仅供子程序输入参数的语言中,命名约定是否对这类参数进行了标识?
· 命名约定是不是与程序语言的标准约定尽可能地相容?
· 对于语言中没有强制的子程序中仅做输入的参数,是否约定将它标识了?
· 是否对名称进行了格式化以增强程序的可读性?
短名称
· 代码是否使用了长名称?(除非有必要使用短名称)
· 是否避免了只省略一个字母的缩写?
· 所有单词保持缩写的连续性了吗?
· 所有的名称都是容易发音的吗?
· 是否避免了会引起错误发音的名称?
· 是否在注释表中对短变量名进行了注释?
避免如下这些常见的命名错误了吗
· 易引起误会的名称
· 含义相似的名称
· 仅有一或两个字母不同的名称
· 发音相似的名称
· 使用数字的名称
· 对单词作改写以使其比较短的名称
· 英语中常拼写错的名称
· 标准库子程序或已定义的变量名又定义了
· 完全是随意的名称
· 含有难以辨识字母的名称
TinKingTZW - 2006-8-25 17:01:00
第十章 变 量
目录
10.1 作用域
10.2 持久性
10.3 赋值时间
10.4 数据结构与控制结构的关系
10.5 变量功能单一性
10.6 全局变量
10.7 小结
相关章节
生成数据:见第8 章
数据命名:见第9 章
使用基本数据类型:见第11 章
使用复杂数据类型:见第12 章
格式化数据说明:见18.5 节
说明数据:见19.5 节
由于前面一章叙述的都是数据名称问题,你可能会认为恰当地命名变量之后便完事大吉了。
绝非如此!命名仅仅是开始,你使用变量的方法也是非常重要的。
如果你是个富有经验的程序员的话,那么本章的内容对你尤其有用。在你完全清楚替代方
案之前,很容易在开始时使用有害的技术。然后,即使在你知道如何避免它们时,出于习惯仍
会继续使用它们。有经验的程序员们将会发现10.5 节“变量功能单一性”和10.6 节“全局变
量”的讨论对他们来说是非常有趣的。
10.1 作用域
作用域指的是变量名称的影响范围,也可称之为可见性,即在程序中某一变量被知道和提
及的范围。作用域有限或很小的变量只在程序的一小部分中被知道,如:一个只有在一个小循
环中用到的循环变量。作用域大的变量则在程序中的许多地方被知道,如:在一个程序中被到
处使用的雇员信息表。
不同的语言处理作用域的方式是不同的。在Basic 某些实现中,所有变量都是全局的。因
此在这种情况下你无法对变量作用域进行任何控制,这也是Basic 的主要缺点之一。在C 中,
变量可以是对块(用大括号括起来的部分)可见的,也可以是分别对子程序、源文件或整个程
序可见的。在Ada中,变量可以分别是对块、亚程序、包、任务、单元或整个程序中可见的。
以下是一些关于作用域(可见性)的常用准则:
TinKingTZW - 2006-8-25 17:01:00
尽可能减小作用域。你所采取的方法往往取决于你“方便性”和“可管理性”这两个问题
的看法。许多程序员喜欢用全局变量。因为全局变量存取很方便而且程序员们不必围着参数表
和模块命名规则转。事实上,这种存取方便性是与由全局变量引入的危险共存的。
另一些程序员则尽可能使用局部变量,因为局部变量可以提高可管理性。你所隐含的信息
越多,那么需要记在心中的东西就越少,而需要记住的东西越少,那么犯错误的机会也就越少,
因为许多细节都不需要再进行记忆了。
“方便性”和“可管理性”之间的区别可以理解为是强调写程序还是强调读程序。扩大变
量作用域事实上的确可以方便写程序,但是一个任意子程序都可以在任意时刻访问任一个变量
的程序,往往要比使用模块化子程序的程序难懂得多。在这种程序中,你无法单纯理解一个子
程序,你必须同时也理解与这个子程序分享全局变量的其它子程序。这样的程序不仅难读,而
且也很难调试和修改。
因此,你必须尽可能地减小变量的作用域。如果能将变量的作用域限制在一个子程序之内
的话,那是再好不过的了,如果你无法把它限制在一个模块中的话,那就利用存取子程序来使
几个模块分享这一数据。总之,应尽量避免使用全局变量以减小作用域。
把对某一变量的引用集中放置。某些研究人员认为把对某一变量的访问放得越近,那么对
程序阅读者的精神压力也就越小。这一想法有很大的直觉吸引力——你每次只需注意比较少的
变量。以下是由这一想法产生的几项准则:
应恰好在某一循环前初始化循环中用到的变量,而不是在含有这个循环的子程序开头对其
中用到的变量进行初始化。这样作可以使你在修改循环时,同时想起对相应的变量的初始化进
行修改;或者在这一循环外再嵌套一个循环时,此时外部循环每执行一次时,都会对内部循环
用到的变量进行初始化,而不会出现只初始化一次的错误。
要在用到某一变量时才对它进行赋值。你可能有过费尽心机地寻找某一变量到底是在哪被
赋值的体验。因此,越清楚地表示出变量赋值的地方越好。
下例指出了在一个计算日收入的子程序中,是怎样把对同一变量的引用集中放置,以便方
便地寻找它们的。第一个例子是违反这一原则的一个C语言子程序:
void SummarizeData(...)
{
...
...
GetOldData(OldData, &NumOldData);
GetNewData(NewData, &NumNewData);
TtlOldData = Sum(OldData,NumOldData);
TtlNewData = Sum(NewData,NumNewData); 语句使用两组变量
PrintOldDataSummary(OldData,TtlOldData,NumO1dData);
PirntNewDataSummary(NewData,TtlOldData,NumNewData);
SaveOldDataSummary(TtlOldData,NumOldData);
SaveNewDataSummary(TtlNewData,NumNewData);
...
...
}
在上例中,你不得不同时注意 OldData、NewData、NumOldData、NumNewData、TtlOldData
TinKingTZW - 2006-8-25 17:01:00
和TtlNewData六个变量,而且又是在这样短的一段程序中。下面的例子指出了如何把这一数量
减少到只有三个:
void SummariseDaily( ... )
{
GetOldData(OldData, &NumOldData);
TtlOldData = Sum(OldData, NumOldData);
PrintOldDataSummary(OldData,TtlOldData,NumOldData);
SaveOldDataSummary (TtlOldDataNumOldData);
...
GetNewData( NewData, &NumNewData);
TtlNewData = Sum( NewData, NumNewData);
PrintNewDataSummary( NewData,TtlNewData,NumNewData);
SaveNewDataSummary( TtlNewData, NumNewData );
...
}
如果像上例这样把程序分用两块,那么每一块都要比原来的块要短,而且其中的变量也要
少得多。这两个块都很容易理解,而且如果需要把这段程序分成几个子程序的话,两个具有较
少变量的块本身就是定义得很好的子程序。
10.2 持久性
“持久性”指的是某一数据的使用寿命。持久性有几种表现形式。如下所示:
· 与某一个块或子程序的生存期一样长。C中的auto变量或Pascal中的局部变量就属
于这种情况。
· 其生存期取决于你的意愿。Pascal中用New()产生的变量直到dispose()它们时才失效。
在C中用malloc()产生的变量也将持续到free()它们时才失效。
· 与程序的生存期一样长。绝大多数语言中的全局变量都属于这种情况。比如c中的static
变量和 Turbo Pascal中“类型化的常量”(类型化常量是对Pascal的非标准推广)。
· 永远有效。这些变量可能包括你在程序再次执行之间存储在数据库中的变量。例如,
在一个交互式的程序中用户可以定义屏幕的颜色,可以把这些颜色存在一个文件中,
在每次程序加载时再将其调出,现在有少数几种语言支持这种持久性。
假定的某个变量的持久性要长于其实际持久性时,就会出现问题。变量就像放在冰箱中的
牛奶,你认为它可以保存一星期,但有时可以保存一个月,有时则五天就坏了。变量的持久性
也是同样不可测的。当变量的有效生存期已经结束时,还试图重新使用它,它还会保持原值吗?
有些情况下变量中的值已经被改变了。这可以使你意识到自己的错误,而在另一些情况下,计
算机还让变量保持原值,从而让你认为自己正确地使用了变量。
以下是可以使你避免这种错误的几个步骤;
· 在程序中加入调试代码来检查变量的值是否合理。如果不合理的话。产生一个警告
使用OldData的语句
使用NewData 的语
TinKingTZW - 2006-8-25 17:03:00
信息来提示检查不恰当的变量初始化。
· 在写代码时假定变量已经失效。比如,退出子程序时某一变量等于某个值,当再次进
入子程序时不要假定这个变量仍保持原值。当然,当你用语言的特定功能来使变量保
持原值时这一原则不适用,比如用C 中的static如来实现这一功能。
· 养成在恰好使用某一变量之前对其进行初始化的习惯。如果发现使用的变量附近没有
对它的初始化,那么你就要小心了。
10.3 赋值时间
一个对程序的维护性和可读性有深远影响的主题是“赋值时间”——把变量的值和变量联
系在一起的时间。是在写程序时把它们联系在一起?还是在编译、加载或者程序运行时把它们
联系在一起?
应该尽可能地晚一些将它们联系在一起。通常,越是晚一些给变量赋值,代码的灵活性便
越大。下面是在可能的最早时间——程序写成时对变量进行赋值例子(用C写成):
TestID=47;
由于47是一个程序的常数值,因此在代码写好时47便与TestID联系在一起了。像上例那样
编码赋值的想法是很有害的,因为如果当这里的47改变时,程序其余用到47且必须与TestID的
值相同的地方很可能会出现语法错误。
以下是一个稍晚些赋值的例子,即在编译时进行赋值:
#define MAX_ID 47
...
TestID = MAX_ID;
MAX_ID是一个宏或是命名常量,当编译时编译程序会用一个值来代替它。如果所用的语
言支持这种用法的话,应尽量这样用,因为这种方法要好于前面的用47来硬性赋值。它使得改
变MAX_ID值变得很容易,因为你只需要在一个地方作出改动就可以了,而且不会影响程序性
能。
下面是在最后时刻赋值的例子,即在运行时赋值。
TestID = MaxID;
程序中MaxID是一个在程序运行时被赋值的变量。这样做的灵活性和可读性也要好于前面
的硬性编译赋值。下面是另一个在运行时赋值的例子:
TestID = ReadFileForMaxID();
上例中的ReadFileForMaxID()是一个在程序运行时从一个文件中读取数值的子程序。这一
例子假定在程序开始运行之前要用到的值已经被放在文件中了。显然这样作的灵活性和可读性
也要好于前述的硬性编码赋值的例子。不必通过改动程序来改变TestID的值,而只要改动一下
存储该值的文件就可以了。这种方法常用于用户可以定义应用环境的交互式系统中,用户的定
义被存储在一个文件中,当程序运行时从文件中读取定义。
下面是在程序运行时进行赋值的最后一种形式:
TestID = GetMaxIDFromUser();
上例中GetMaxIDFromUser()是一个采用交互方式从用户那里读取数值的子程序。这种方
TinKingTZW - 2006-8-25 17:04:00
法的可读性和灵活性要远远好于硬性编码赋值。为改变TestID值根本不需作出任何改动,只要
在程序运行时由用户输入另外一个值就可以了。从上述在运行时赋值的例子可以看出,即使同
样是在运行时赋值的方式,变量与其值联系到一起的具体时间也是不同的。最后一个例子中的
赋值可以在程序运行中任一时刻进行,它取决于用户被要求输入TestID值的时间。
10.4 数据结构与控制结构的关系
许多研究者都曾试图努力找出数据结构与控制结构之间的通用关系,这其中最成功的是英
国计算机学家 Michael Jackson。Jackson的技术,主要是通过一种系统的方法把数据结构变换为
控制结构。他的方法在欧洲得到了充分发展并被广泛应用。本书无法详细论述Jackson的理论,
但可以对这种理论所基于的数据和控制流之间的调节关系作一概述。
从可用的数据和输出该是什么样子的想法开始
然后对程序进行定义,使其把输入转化为输出
Jackson勾画出了三种类型数据与相应的控制结构之间的关系。
程序中顺序性数据可以转化为顺序性语句。数列是由一组按某一特定顺序使用的数据组成
的。如果用排成一列的五条语句来处理五个不同的数值,那么它们就是顺序性语句,如果需要
从某一文件只读取雇员的名字、社会保险号码、地址、电话号码和年龄等五个数据,那你将在
程序中使用顺序性语句来从文件中读取这些顺序性数据。
程序中的选择性数据可以转换为if和case语句。通常,选择性数据指的是在任一特定时刻,
几个数据中的某一个会出现——将选定其中的某一个数据。相应的程序必须用If-Then-Else语句
或Case语句进行选择操作。比如在一个工资发放系统中,你可能需要按某一雇员是按小时计酬
附件:
7341152006825165652.bmp
© 2000 - 2026 Rising Corp. Ltd.