变量名的力量(一)

第十一章 变量名的力量
要点:
· 好的变量名是提高程序可读性的一项关键要素。对特殊种类的变量,比如循环下标和状态变量,需要加以特殊的考虑。
· 名字要尽可能地具体。那些太模糊或者太通用以至于能够用于多种目的的名字通常都是很不好的。
· 命名规则应该能够区分局部数据,类数据和全局数据。它们还应当可以区分类型名、具名常量、枚举类型名字和变量名。
· 无论做哪种类型项目,你都应该采用某种变量命名规则。你所采用的规则的种类取决于你的程序的规模,以及项目成员的人数。
·  现代编程语言很少需要用到缩写。如果你真的要使用缩写,请使用项目缩写词典或者标准前缀来帮助理解缩写。
· 代码阅读的次数远远多于编写的次数。确保你所取得名字更侧重于阅读方便而不是编写方便。

11.1 选择好的变量名的注意事项
    也许,每一个人都有给变量随便命名的经历,那就是在我们刚刚开始学习一门语言对着编程工具写下我们的最开始的程序的时候。我们根本就没有注意需要给变量取个什么名字,基本上都是怎么方便怎么来。现在想起来都觉得那时候真是太机(ruo)智了。现在随着学习的深入,越加觉得一个好的变量名所带来的信息真的是非常的重要的。我们不可能像给我们的宠物(也许你连一只宠物也没有)取名字一样给我们的变量取名字,觉得可爱就行了。归根结底,宠物和宠物的名字是两个不同的事物,而变量和变量名实质上是同一事物。这样一来,一个变量的好坏在很大程度上取决于它的命名的好坏。因此,我们在给变量命名的时候需要特别的注意。
    先来看看下面的例子
//C++示例: 糟糕的变量名
double x;
int xx;
int xxx;
    这三个变量到底是用来做什么的?它们表示什么意思?如果我不告诉你这三个变量分别表示一本书的价格、页数和章节数,那么你是决对不会这么想的。
    下面这种写法,就能很好的回答上面提到的问题:
C++示例:良好的变量名
dobule nPriceOfBook;
int nPagesOfBook;
int nChaptersOfBook;

    从上面两段代码的比较中可以看出,一个好的变量名是可读的、易记和恰如其分的。


最重要的命名注意事项:

    为变量命名时最重要的考虑事项是,该名字要完全、准确地描述出该变量所代表的事物。例如,对于一个表示汽车的最高时速的变量,我们可以把它命名为maximumVelocityOfCar,表示当前利率的变量最好可以命名为rate或interestRate,而不是r或x之类的。
    一个好的变量名,完全可以让我们一看就懂它表示什么意思,不需要进行额外的注释。例如表示当前日期的变量,如果将它命名为date,显然这还不够好,因为date可以表示很多日期,也许是今天的,也许是十年前。那么将它命名为currentDate或todaysDate都是很好的命名,一目了然,很好的表达了“当前日期”的概念。

以问题为导向:
    一个好的变量名通常反映的都是问题,而不是解决方案。一个好名字通常表达的是“什么”(what),而不是“如何”(how)。一般而言,如果一个名字反映了计算的某些方面而不是问题本身,那么它反映的就是“how”而非"what"了。

    例如,一条员工的记录可以称作inputRec或者employeeData。inputRec是一个反映输入、记录这些计算概念的计算机术语。employeeData则直指问题领域,与计算的世界无关。


最适当的名字长度:

    变量名的长度不宜太长,也不宜过短。太长的变量名不仅增加了输入的难度也使得程序在视觉结构上变得模糊不清。太短的变量名无法传达足够的信息。前人发现,当变量名在10到16个字符的时候,调试程序所花费的气力是最小的。平均名字长度在8到20个字符的程序也几乎同样容易调试。当然这项原则,不是规定你必须把变量名控制在9到15或10到16个字符长之间。它所强调的是,当你在程序中发现更短的变量名时,应该检查它所表示的含义是否足够清晰。
    下面给出一些例子:
--------------------------------------------------------------------------------
太长: numberOfPeopleOnTheUsOlympicTeam
numberOfSoatsInTheStadium
maximumNumberOfPointsInModernOlympics
--------------------------------------------------------------------------------
太短: n, np, ntm
n, ms, nsisd
m, mp, max, points
-------------------------------------------------------------------------------
正好: numTeamMembers, teamMemberCount
numSeatsInStadium, seatCount
TeamPointsMax, pointsRecord
-------------------------------------------------------------------------------

变量名的计算值限定词
    很多的程序都有表示计算结果的变量:总额、平均值、最大值,等等。如果你要用类似于Total、Sum、Average、Max、Min、record、String、Pointer这样的限定词来修改某个名字,那么请记住把限定词加到名字的最后。
    这种方法具有很多优点。首先,变量名中最重要的那部分,即为这一变量赋予主要含义的部分应当位于最前面,这样,这一部分就可以显得最为突出,并会被首先阅读到。其次,采纳了这一规则,你将避免由于同时在程序中使用totalRevenue和revenueTotal而产生的歧义。这些名字在语义上是等价的,上述规则可以避免将它们当作不同的东西使用。还有,类似revenueTotal(总收入)、expenseTotal(总支出)、revenueAverage(平均收入)、expenseAverage(平均支出)这组名字的变量具有非常优雅的对称性。而从totalRevenue、totalExpense、averageRevenue、averageExpense这组名字中看不出什么规律来。总之,一致性可以提高可读性,简化维护工作。

变量名中的常用对仗词
    对仗词的使用要准确。通过应用命名规则来提高对仗词使用的一致性,从而提高其可读性。比如像begin/end这样的一组用词非常容易理解和记忆。而那些与常用语言相去甚远的词则通常很难记忆,有时甚至会产生歧义。下面是一些常用的对仗词:
· begin/end
· first/last
· locked/unlocked
· min/max
· next/previous
· old/new
· opened/closed
· visible/invisible
· source/target
· source/destination
· up/down


11.2 为特定类型的数据命名
    在为数据命名的时候,除了通常的考虑事项之外,为一些特定类型数据的命名还要求做出一些特殊的考虑。

为循环下表命名
    循环是一种极为常见的计算机编程特征,为循环中的变量进行命名的原则也由此应运而生。i、j和k这些名字都是约定俗成的:
for(int i = first; i <= last; i++)
data[i] = 0;
    如果一个变量要在循环之外使用,那么就应该为它取一个比i、j和k更有意义的名字。举个例子,如果你在从文件中读取记录,并且需要记下所读取记录的数量,那么类似于recordvCount这样的名字就很合适:
recordCount = 0;
while(moreScores())
{
	score[recordCount] = GetNextScore();
	recordCount++;
}
// lines using recordCount
...
    如果循环不止是有几行,那么读者会很容易忘记i本来具有的含义,因此你最好给循环下标画一个更有意义的名字。由于代码会经常修改、扩充,或者复制到其他程序中去,因此,很多有经验的程序员索性不使用类似于i这样的名字。
    导致循环变长道的常见原因之一是出现循环的嵌套使用。如果你使用了多个嵌套的循环,那么就应该给循环变量赋予更长的名字以提高可读性:
for(teamIndex = 0; teamIndex <= teamCount; teamIndex++)
{
	for(eventIndex = 0; eventIndex <= eventCount[teamIndex]; eventIndex++)
	{
		score[teamIndex][eventIndex] = 0;
	}
}
    谨慎地为循环下标变量命名可以避免产生下标串话(index cross-talk)的常见问题:想用j的时候写了i,想用i的时候却写了j。同时,这也使得数据访问变得更加清晰:score[teamIndex][eventIndex]要比scroe[i][j]给出的信息要更多。
    如果你一定要用i、j和k,那么不要把它们用于简单循环的循环下标之外的任何场景——这种传统已经太深入人心了,一旦违背该原则,将这些变量用于其他用途就可能造成误解要想避免出现这样的问题,最简单的方法就是想出一个比i、、j、和k更具描述性的名字来。

为状态变量命名
    状态变量用于描述你的程序的状态。下面给出它的命名原则。
为状态变量去一个比flag更好的名字。 最好是把标记(flag)看作状态变量。标记的名字中不应该含有flag,因为你从中丝毫看不出该标记是做什么的。为了清楚起见,标记应该用枚举类型、具名常量,或用作具名常量的全局变量来对其赋值,而且其值应该与上面这些量做比较。下面例子中标记的命名都很差:
if(flag)...
if(statusFlag & 0x0f) ...
if(printFlag == 16) ...
if(computerFlag == 0) ...
...
flag = 0x1;
statusFlag = 0x80;
printFlag = 16;
computerFlag = 0;
    像statusFlag = 0x80 这样的语句是反映不出这段代码能做什么的,除非你亲自写了这段代码,或者有文档能告诉你vstatusFlag和0x80的含义。下面是作用相同但更为清晰的代码:
if(dataReady) ...
if(characterType & PRINTABLE_CHAR) ...
if(reportType == ReportType_Annual) ...
if(recalcNeeded == false) ...

dataReady = true;
characterType = CONTROL_CHARACTER;
reportType = ReportType_Annual;
recalcNeeded = false;
    显然,characterType = CONTROL_CHARACTER要比statusFlag = 0x80更有意义。与之类似,条件判断语句if(reportType == ReportType_Annual)要比if(printFlag == 16)更为清晰。第二个例子说明你可以结合枚举类型和预定义的具名常量来使用这种方法。下面例子展示了如何使用具名常量和枚举类型来组织例子中的数值:
// value for characterType
const int LETTER = 0x01;
const int DIGIT = 0x02;
const int PUNCTUATION = 0x04;
const int LINE_DRAM = 0x08;
const int PRINTABLE_CHAR = {LETTER | DIGIT | PUNCTUATION | LINE_DRAM};

const int CONTROL_CHARACTER = 0x80;

// values for ReportType
enum ReportType{
	ReportType_Daily,
	ReportType_Monthly,
	ReportType_Quarterly,
	ReportType_Annual,
	ReportType_All
};
    如果你发现自己需要猜测某段代码的含义的时候,可就应该考虑为变量重新命名。猜测谋杀案中谁是神秘凶手是可行的,但你没有必要去猜测代码。你应该直接读懂它们。

为临时变量命名
    临时变量用于存储计算的中间结果,作为临时占位符,以及存储内务管理(housekeeping)值。它们常被赋予temp、x或者其他一些模糊且缺失描述性的名字。通常,临时变量是一个信号,表明程序员还没有完全把问题弄清楚。而且由于这些变量被正式地赋予了一种“临时“状态,因此程序员会倾向于比其他变量更为随意地对待这些变量,从而增加了出错的可能。
    警惕“临时”变量。临时性地保存一些之常常是很必要的。但是无论从那种角度看,你程序中的大多数变量都是临时性的。把其中几个成为临时的,可能表明你还没有弄清它们的实际用途。请考虑下面的示例:
// compute roots of a quadratic equation
// this asumes 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 asumes 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 );
    就本质上而言,这段代码与上面一段是完全相同的,但是它却通过使用了准确而且更具描述性的变量名( discriminant, 判别式)而得到了改善。

为布尔变量命名
    下面是为布尔变量命名是要遵循的几条原则。
    谨记典型的布尔变量名 下面是一些格外有用的布尔变量名。
    · done 用done表示某件事情已经完成。这一变量可用于表示循环结束或者一些其他的操作已完成。在事情完成之前把done设为false,在事情万恒之后把它设为true。
    · error 用error表示有错误发生。在错误发生之前把变量值设为false,在错误发生时把它设为true。
    · found 用found来表明某个值已经找到了。在还没有找到该值的时候把found设为false,一旦找到该值就把found设为true。在一个数组中查找某个值,在文件中搜寻某个员工的ID,在一沓支票中寻找某张特定金额的支票等等时候,都可以用found。
    · success 或ok 用success或ok来表明一项操作是否成功。在操作失败的时候把该变量设为false,在操作成功的时候把其设为true。如果可以,请用一个更具体的名字代替success,以便更具体地描述成功的含义。如果完成处理就表示这个程序执行成功,那么或许你应该用processingComplete来取而代之。如果找到某个值就是程序执行成功,那么你也许应该换用found。
    给布尔变量赋予隐含“真/假”含义的名字 像done和success这样的名字是很不错的布尔变量名,因为其状态要么是true,要么是false;某件事情完成了或者没有完成;成功或者失败。另一方面,像status和sourceFile这样的名字确实很早的布尔变量名,因为它们没有明确的true或者false。status是true反映的是什么含义?它表明某件事情拥有一个状态吗?或者说false表明没有任何错误码?对于status这样的名字,你什么也说不出来。
    为了取得更好的效果,应该把status替换为类似于error或者statusOK这样的名字,同时把statusFile替换为sourceFileAvailable、sourceFileFound,或者其他能体现该变量所代表含义的名字。
    有些程序员喜欢在他们写的布尔变量名前加上Is。这样,变量名就变成了一个问题:isdone?isError?isFound?isProcessingComplete?用true或false回答问题也就为该变量给出了取值。这种方法的有定之一是它不能用于那些模糊不清的名字:isStatus?这毫无意义。它的缺点之一是降低了简单逻辑表达式的可读性。
    使用可定的布尔变量名 否定的名字如notFound、notDone以及notSuccessful等胶南阅读,特别是如果他们被求反:
i = !notFound;
    这样的名字应该替换为found、done或者processingComplete,然后再用适当的运算符求反。如果你找到了想找的结果,那么就可以用found而不必双重否定的!notFound了。

为枚举类型命名
    在使用枚举类型的时候,可以通过使用组前缀,如Color_、Planet_或者Month_来明确表示该类型的成员都同属于一个组。下面举一些通过前缀来确定枚举类型元素的例子:
enum Color{
	Color_Red,
	Color_Green,
	Color_Blue
};
enum Planet{
	Planet_Earth,
	Planet_Mars,
	Planet_Venus
};
enum Month{
	Month_January,
	Month_February
};
    与此同时,也有很多命名方法可用于确定枚举类型本身的名字(Color、Planet或Month),包括全部大写或者加以前缀(e_Color,e_Planet,e_Month)。有人可能会说,美剧从本质上而言是一个用户定义类型,因此枚举名字的格式应该与其他用户定义的类型如类等相同。与之相反的一种观点认为枚举是一种类型,但它也同时是常量,因此枚举类型名字的格式应该与常量相同。
    在有些编程语言里,枚举类型的处理很像类,枚举成员也总是被冠以枚举名字前缀,比如Color.Color_Red或者Planet.Planet_Earth。如果你正在使用这样的语言,那么重复上述前缀的意义就不大了,因此你可以把枚举类型自身的名字作为前缀,并把上述名字简化为Color.Red和Planet.Earth。

为常量命名
    在具名常量时,应该根据该常量所标示的含义,而不是该常量所具有的数值为该抽象事物命名。FIVE是个很早的常量名(不论它所代表的值是否为5.0)。CYCLES_NEEDED是个不错的名字。CYCLES_NEEDED可以等于5.0或者6.0.而FIVE = 6.0就显得太可笑了。出于同样原因,BAYERS_DOZEN就是个很早的常量名,而DONJTS_MAX则很不错。

11.3 命名规则的力量
命名规则可以带来以下的好处
    · 要求你更多地按规矩行事。
通过做一项全局决策而不是做许多局部决策,你可以集中精力关注代码更重要的特征。
    · 有助于在项目之间传递知识。名字的相似性能让你更容易、更自信地理解那些不熟悉的变量原本应该是做什么的。
    · 有助于你在新项目中更快速地学习代码。你无需了解张三写的代码是这样的,李四写的代码是那样的,以及王五写的代码又是另一种样子,而只需面对一组更加一致的代码。
    · 有助于减少名字增生(name proliferation)。在没有命名规则的情况下,会很容易地给同一个对象其两个不同的名字。例如,你可能会把总点数既称为pointTotal,也称为totalPoints。在写代码的时候这可能并不会让你感到迷惑,但是它却会让一位日后阅读这段代码的新程序员感到极其困惑。
    · 弥补编程语言的不足之处。你可以用规则来效仿具名常量和枚举类型。规则可以根据局部数据、类数据以及全局数据的不同而有所差别,并且可以包含编译器不直接提供的类新信息。
    · 强调相关变量之间的关系。如果你使用对象,则编译器会自动照料它们。如果你用的编程语言不支持对象,你可以用命名规则来予以补充。诸如address、phone以及name这样的名字并不能表明这些变量是否相关。但是假设你决定所有的员工数据变量都应该以Employee作为前缀,则employeeAddress、employeePhone和employeeName就会毫无疑问地表明这些变量是彼此相关的。变成的命名规则可以对你所用的编程语言的不足之处做出弥补。
    关键之处在于,采用任何一项规则都要好于没有规则。规则可能是武断的。命名规则的威力并非来源于你所采用的某个特定规则,而是来源于以下事实:规则的存在为你的代码增加了结构,减少了你需要考虑的事情。

何时采用命名规则
    没有尽可韵律表明何时应该建立命名规则,但是在下列情况下规则是很有价值的。
    · 当多个程序员合作开发一个项目时
    · 当你计划把一个程序转交给另一位程序员来修改和维护的时候
    · 当你所在组织中的其他程序员评估你写的程序的时候
    · 当你写的程序规模太大,以至于你无法在脑海里同时了解事情的全貌,而必须分而治之的时候
    · 当在一个项目中存在一些不常见的术语,并且你希望在编写代码阶段使用标准的术语或者缩写的时候

    你一定会因使用了某种命名规则而受益。

正式程度

    不同规则所要求的正式程度也有所不同。一个非正式的规则很可能会像“使用有意义的名字”这样简单。通常,你所需的正式程度取决于为同一程序而工作的人员数量、程序的规模以及程序预期的生命期。对于微小的、用完即弃的项目而言,实施严格的规则可能就太没有必要了。正式规则都是成为提高可读性的必不可少的辅助手段。



  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值