关于如何为变量取一个好的名字,我们已经在上一篇作了详细的讨论。其中最核心的原则是变量的名字要能够清晰直接的表达其作用或意思。在这一篇文章中我们将主要讨论一下魔术数和成员变量的命名。
大家有没有这样一个经历:在维护一个旧的系统时,发现一段代码里面有个数字(magic number),而写这段代码的同事早已离开公司。整个公司没有人知道这个数字是起什么作用的,所以没有人能够理解这段代码的意思,于是乎没人敢改这段代码。如果系统出现了一个和这段代码相关Bug,且你是那位需要修复这个Bug的人,我想你一定会泪崩。数字最大问题是它可以代表任何意思,只有写这个数字的人才知道他自己给这个数字赋予了什么意思。对于其他人来说,它就是一个不知道什么意思的数字,但它居然神奇般能够让系统工作。这就是为什么大家叫它magic number的名字的由来。
下面我们通过一个具体的例子来体会一个magic number的可怕之处。下面CalculateTax_Bad(float salary)函数的实现中有两个数字。光看函数的实现你知道这个函数起什么作用吗?还好通过函数的名字我们知道这个函数在计算税款(注意函数的名字应该为CalculateTax,后面的_Bad是为了区分函数的不同版本),这也从一个侧面反映了为函数取一个好名字的重要性。但是如果计算税款的公式需要在原来的基础上做一些修改呢?如果不理解这两个数字的意思就很难对公式做修改。另外一个麻烦的问题是如果1000这个数据在整个系统中很多个地方被使用到,如果这个数字的值改变了呢?你将需要到很多地方去修改这个数字。
private float CalculateTax_Bad(float salary)
{
return (float)((salary - 1000)* 0.20);
}
对付Magic number最为有效的办法就是定义常量。下面的CalculateTax_Good(float salary) 函数是使用常量的版本。我们定义了一个叫Constants 的类,在这个类里面我们定义了两个公共的常量,且我们为这两个常量取了能够表达其作用的名字。这两个常量将直接用来替换0.20 和 1000两个数据。如果你再来阅读CalculateTax函数,你发现非常容易理解。使用常量的另外一个好处是,当0.20或1000这两个值发生改变了,我们那只需要到Constants这个类中修改一次,所有使用这两个常量的地方就是自动使用新的值。如果你回顾一下价值篇中那张软件系统的图,你就会发现常量不仅可以使A+的代码相对容易理解,同时它可以让可能发生变化的点(常量值的变化)集中在一个地方。
private float CalculateTax_Good(float salary)
{
return (salary - Constants.TwoChildrenAllowance)* Constants.TaxRate ;
}
public class Constants
{
public const float TaxRate = (float)0.20;
public const float TwoChildrenAllowance = 1000;
}
软件系统中有多种变量:全局变量,类成员变量以及局部变量。有时候在函数体(函数的实现)内会同时出现类成员变量和局部变量,而通常我们会希望能够相对简单的区分成员变量和局部变量。因为很多时候我们会相对较关注成员变量的修改(对类状态的修改)。给成员变量和局部变量定义不同的命名风格是区分他们的有效方法。在C++中很多人通过在变量前面加上m_来表示这是一个成员变量,C#中我们一般会通过在变量名前面加上_(下划线)来表示这是一个成员变量。这样的好处就是,当你在阅读一个函数实现的时候,你很容易就知道哪个是成员变量。特别是你在维护一个旧的系统且函数都比较长的时候。如果我们不采用上述方法,那么我们就需要记录哪个变量是成员变量、哪个是局部变量,就如上一篇所讲的映射会给我们的脑子带了额外的负担。
大家可以阅读下面的代码片段来体会一下CalculateTotalCredits函数两个不同版本的区别。CalculateTotalCredits 函数其实是一个比较简单的函数,但即便是这样,你仍然能感觉到在CalculateTotalCredits_Good中,你更容易能找到哪个地方修改了Student类的状态(类的成员变量)。这种区别在复杂的函数中尤为明显。
public class Student
{
private float totalCredits = 0;
public void CalculateTotalCredits_Bad(IEnumerable enrollments)
{
foreach (var enrollment in enrollments)
{
if (enrollment.Grade != null)
{
var gradeWeight = GetGradeWeight(enrollment.Grade.Value);
var credits = enrollment.Course.Credits;
totalCredits = totalCredits + gradeWeight * credits;
}
}
}
private float _totalCredits = 0;
public void CalculateTotalCredits_Good(IEnumerable enrollments)
{
foreach (var enrollment in enrollments)
{
if (enrollment.Grade != null)
{
var gradeWeight = GetGradeWeight(enrollment.Grade.Value);
var credits = enrollment.Course.Credits;
_totalCredits = _totalCredits + gradeWeight * credits;
}
}
}
private float GetGradeWeight(Grade grade)
{
....
}
}
在这一篇我们讨论了变量命名的两个具体的技巧,如果你问Google它一定还能提供更多的技巧。技巧不是我们的目标,它是我们的手段。让代码容易理解易于维护才是我们的目标。所以我们要牢记表达意图>短名字>长名字这个公式,然后你会自然而然的运用一些技巧。下一篇我们将继续讨论命名敬请关注。