代码整洁之道(第2章节)--有意义的命名

目录

1:PDF上传链接

2.1 介绍

2.2 名副其实

2.3 避免误导

2.4 做有意义的区分

2.5 使用读得出来的名称

2.7避免使用编码

2.7.1 匈牙利语标记法

2.7.2 成员前缀

2.7.3 接口和实现

2.8 避免思维映射

2.9 类名

2.10 方法名

2.11 别拌可爱

2.12 每个概念对应一个词

2.13 别用双关语

2.14 使用解决方案领域名称

2.15 使用源自所涉问题领域的名称

2.16 添加有意义的语境

2.17 不要添加没用的语境

2.18 最后的话


1:PDF上传链接

【免费】代码zhengjiezhidao资源-CSDN文库

2.1 介绍

        软件中随处可见命名。我们给变量、函数、参数、类和封包命名。我们给源代码及源代码所在目录命名。我们给jar文件、war文件和ear文件命名。我们命名、命名,不断命名。
既然有这么多命名要做,不妨做好它。下文列出了取个好名字的几条简单规则。

2.2 名副其实

        名副其实说起来简单。我们想要强调,这事很严肃。选个好名字要花时间,但省下来的时间比花掉的多。注意命名,而且一旦发现有更好的名称,就换掉旧的。这么做,读你代码的人(包括你自己)都会更开心。
        变量、函数或类的名称应该已经答复了所有的大问题。它该告诉你,,它为什么会存在,它做什么事,应该怎么用。如果名称需要驻释来补充,那就不算是名副其实。

int d://消逝的时间,以日计


        名称d什么也没说明。它没有引起对时间消逝的感觉,更别说以日计了。我们应该选择
指明了计量对象和计量单位的名称:

int elapsedTimeInDays;
int dayssinceCreation;
int dayssinceModification;
int fileAgeInDays;

选择体现本意的名称能让人更容易理解和修改代码。下列代码的目的何在?
 

public List<int[]> getThem() {
    List<int[]> list1 = new ArrayList<int[]>();
        for(int[] x : theList)
            if(x[0] == 4)
                listl.add(x);
        return list1;
}

        为什么难以说明上列代码要做什么事?里面并没有复杂的表达式,空格和缩进中规中矩。
只用到三个变量和两个常量。甚至没有涉及任何其他类或多态方法,只是(或者看起来是)
一个数组的列表而已。
        问题不在于代码的简洁度,而是在于代码的模糊度:即上下文在代码中未被明确体现的
程度。上列代码要求我们了解类似以下问题的答案:
        (1)theList中是什么类型的东西?
        (2)theList零下标条目的意义是什么?
        (3)值4的意义是什么?
        (4)我怎么使用返回的列表?
        问题的答案没体现在代码段中,可那就是它们该在的地方。比方说,我们在开发一种扫雷游戏,我们发现,盘面是名为theList的单元格列表,那就将其名称改为gameBoard.。
        盘面上每个单元格都用一个简单数组表示。我们还发现,零下标条目是一种状态值,而该种状态值为4表示“已标记”。只要改为有意义的名称,代码就会得到相当程度的改进:

public List<int[]> getFlaggedcells() {
	List<int[]> flaggedcells = new ArrayList<int(]>();
	for (int[]cell : gameBoard)
		if (cell[STATUS_VALUE] == FLAGGED)
			flaggedcells.add(cell);
	return flaggedcells;
}

        注意,代码的简洁性并未被触及。运算符和常量的数量全然保持不变,嵌套数量也全然保持不变。但代码变得明确多了。
        还可以更进一步,不用t数组表示单元格,而是另写一个类。该类包括一个名副其实的函数(称为isFlagged),从而掩盖住那个魔术数'。于是得到函数的新版本:

public List<Cell>getFlaggedcells() {
	List<Cell> flaggedcells = new ArrayList<cell>();
	for (Cell cell : gameBoard)
		if (cell.isFlagged())
			flaggedcells.add(cell);
	return flaggedcells;
}

        只要简单改一下名称,就能轻易知道发生了什么。这就是选用好名称的力量。

2.3 避免误导

        程序员必须避免留下掩藏代码本意的错误线索。应当避免使用与本意相悖的词。例如,hp、ai和sco都不该用做变量名,因为它们都是UNX平台或类UNX平台的专有名称。即便你是在编写三角计算程序,p看起来是个不错的缩写,但那也可能会提供错误信息。
        别用accountList来指称一组账号,除非它真的是List类型。List一词对程序员有特殊意义。如果包纳账号的容器并非真是个List,就会引起错误的判断。所以,用accountGroup或bunchOfAccounts,甚至直接用accounts都会好一些。
        提防使用不同之处较小的名称。想区分模块中某处的XYZControllerForEfficientHandlingOfStrings和另一处的XYZControllerForEfficientStorageOfStrings,会花多长时间呢?这两个词外形实在太相似了。

2.4 做有意义的区分

        如果程序员只是为满足编译器或解释器的需要而写代码,就会制造麻烦。例如,因为同一作用范围内两样不同的东西不能重名,你可能会随手改掉其中一个的名称。有时干脆以错误的拼写充数,结果就是出现在更正拼写错误后导致编译器出错的情况。

        光是添加数字系列或是废话远远不够,即便这足以让编译器满意。如果名称必须相异,那其意思也应该不同才对。

        以数字系列命名(al、a2,…aN)是依义命名的对立面。这样的名称纯属误导一完全没有提供正确信息;没有提供导向作者意图的线索。试看:

public static void copyChars(char al[], char a2[]){
	for (int i = 0; i < al.length; i++){
		a2[i] = a1[i]:
	}
}

        如果参数名改为source和destination,这个函数就会像样许多。

        如果缺少明确约定,变量moneyAmount就与money没区别,customerInfo与customer没区别,accountData与account没区别,theMessage也与message没区别。要区分名称,就要以读者能鉴别不同之处的方式来区分。

2.5 使用读得出来的名称

        人类长于记忆和使用单词。大脑的相当一部分就是用来容纳和处理单词的。单词能读得出来。人类进化到大脑中有那么大的一块地方用来处理言语,若不善加利用,实在是种耻辱。
        如果名称读不出来,讨论的时候就会像个傻鸟。“哎,这儿,鼻涕阿三喜摁踢(bee cee arrhree cee enn tee)2上头,有个皮挨死极翘(pee ess zee kyew)3整数,看见没?”这不是小事,因为编程本就是一种社会活动。

2.7避免使用编码

        编码已经太多,无谓再自找麻烦。把类型或作用域编进名称里面,徒然增加了解码的
负担。没理由要求每位新人都在弄清要应付的代码之外(那算是正常的),还要再搞懂另一
种编码“语言”。这对于解决问题而言,纯属多余的负担。带编码的名称通常也不便发音,
容易打错。

2.7.1 匈牙利语标记法

        在往昔名称长短很要命的时代,我们毫无必要地破坏了不编码的规矩,如今后悔不迭。
Fortran语言要求首字母体现出类型,导致了编码的产生。BASIC早期版本只允许使用一个字母再加上一位数字。匈牙利语标记法(Hungarian Notation,HN)将这种态势愈演愈烈。
        在Windows的C语言API的时代,HN相当重要,那时所有名称要么是个整数句柄,要么是个长指针或者void指针,要不然就是string的几种实现(有不同的用途和属性)之一。那时候编译器并不做类型检查,程序员需要匈牙利语标记法来帮助自己记住类型。
        现代编程语言具有更丰富的类型系统,编译器也记得并强制使用类型。而且,人们趋向于使用更小的类、更短的方法,好让每个变量的定义都在视野范围之内。
        Java程序员不需要类型编码。对象是强类型的,代码编辑环境已经先进到在编译开始前就侦测到类型错误的程度!所以,如今HN和其他类型编码形式都纯属多余。它们增加了修改变量、函数或类的名称或类型的难度。它们增加了阅读代码的难度。它们制造了让编码系统误导读者的可能性。
        PhoneNumber       phoneString;

        //类型变化时,名称并不变化!

2.7.2 成员前缀

        也不必用m_前缀来标明成员变量。应当把类和函数做得足够小,消除对成员前缀的需要你应当使用某种可以高亮或用颜色标出成员的编辑环境。

        此外,人们会很快学会无视前缀(或后缀),只看到名称中有意义的部分。代码读得越多,眼中就越没有前缀。最终,前缀变作了不入法眼的废料,变作了旧代码的标志物。

2.7.3 接口和实现

        有时也会出现采用编码的特殊情形。比如,你在做一个创建形状用的抽象工厂(AbstractFactory),该工厂是个接口,要用具体类来实现。你怎么来命名工厂和具体类呢?IShapeFactory和ShapeFactory吗?我喜欢不加修饰的接口。前导字母I被滥用到了说好听点是干扰,说难听点根本就是废话的程度。我不想让用户知道我给他们的是接口。我就想让他们知道那是个ShapeFactory。如果接口和实现必须选一个来编码的话,我宁肯选择实现。ShapeFactoryImp,甚至是丑陋的CShapeFactory,都比对接口名称编码来得好。

2.8 避免思维映射

        不应当让读者在脑中把你的名称翻译为他们熟知的名称。这种问题经常出现在选择是使用问题领域术语还是解决方案领域术语时。
        单字母变量名就是个问题。在作用域较小、也没有名称冲突时,循环计数器自然有可能被命名为i或j或k。(但千万别用字母1!)这是因为传统上惯用单字母名称做循环计数器。
然而,在多数其他情况下,单字母名称不是个好选择:读者必须在脑中将它映射为真实概念。
仅仅是因为有了a和b,就要取名为c,实在并非像样的理由。
        程序员通常都是聪明人。聪明人有时会借脑筋急转弯炫耀其聪明。总而言之,假使你记得r代表不包含主机名和图式(scheme)的小写字母版url的话,那你真是太聪明了。
        聪明程序员和专业程序员之间的区别在于,专业程序员了解,明确是王道。专业程序员善用其能,编写其他人能理解的代码。

2.9 类名

        类名和对象名应该是名词或名词短语,如Customer、WikiPage、Account和AddressParser,避免使用Manager、Processor、Data或Info这样的类名。类名不应当是动词。

2.10 方法名

        方法名应当是动词或动词短语,如postPayment、deletePage或save。属性访问器、修改器和断言应该根据其值命名,并依Javabean标准'加上get、set和is前缀。

2.11 别拌可爱

        如果名称太耍宝,那就只有同作者一般有幽条感的人才能记得住,而且还是在他们记得那个笑话的时候才行。谁会知道名为HolyHandGrenade的函数是用来做什么的呢?没错,这名字挺伶俐,不过DeleteItems或许是更好的名称。宁可明确,毋为好玩。
        扮可爱的做法在代码中经常体现为使用俗话或俚语。例如,别用whack()'来表示kil()。别用eatMyShorts()这类与文化紧密相关的笑话来表示abort()。
        言到意到。意到言到。

2.12 每个概念对应一个词

        给每个抽象概念选一个词,并且一以贯之。例如,使用fetch、retrieve和get来给在多个类中的同种方法命名。你怎么记得住哪个类中是哪个方法呢?很悲哀,你总得记住编写库或类的公司、机构或个人,才能想得起来用的是哪个术语。否则,就得耗费大把时间浏览各个文件头及前面的代码。
        Eclipse和IntelliJ之类现代编程环境提供了与环境相关的线索,比如某个对象能调用的方法列表。不过要注意,列表中通常不会给出你为函数名和参数列表编写的注释。如果参数名称来自函数声明,你就太幸运了。函数名称应当独一无二,而且要保持一致,这样你才能不借助多余的浏览就找到正确的方法。
        同样,在同一堆代码中有controller,又有manager,还有driver,就会令人困惑.DeviceManager和Protocol--Controller之间有何根本区别?为什么不全用controllers或managers?他们都是Drivers吗?这种名称,让人觉得这两个对象是不同类型的,也分属不同的类。
        对于那些会用到你代码的程序员,一以贯之的命名法简直就是天降福音。

2.13 别用双关语

        避免将同一单词用于不同目的。同一术语用于不同概念,基本上就是双关语了。如果遵循“一词一义”规则,可能在好多个类里面都会有add方法。只要这些add方法的参数列表和返回值在语义上等价,就一切顺利。
        但是,可能会有人决定为“保持一致”而使用add这个词来命名,即便并非真的想表示这种意思。比如,在多个类中都有add方法,该方法通过增加或连接两个现存值来获得新值。
假设要写个新类,该类中有一个方法,把单个参数放到群集(collection)中。该把这个方法叫做add吗?这样做貌似和其他add方法保持了一致,但实际上语义却不同,应该用insert或append之类词来命名才对。把该方法命名为add,就是双关语了。
        代码作者应尽力写出易于理解的代码。我们想把代码写得让别人能一目尽览,而不必弹精竭虑地研究。我们想要那种大众化的作者尽责写清楚的平装书模式:我们不想要那种学者挖地三尺才能明白个中意义的学院派模式。

2.14 使用解决方案领域名称

        记住,只有程序员才会读你的代码。所以,尽管用那些计算机科学(Computer Science,CS)术语、算法名、模式名、数学术语吧。依据问题所涉领域来命名可不算是聪明的做法,因为不该让协作者老是跑去问客户每个名称的含义,其实他们早该通过另一名称了解这个概念了。
        对于熟悉访问者(VISITOR)模式的程序来说,名称AccountVisitor富有意义。哪个程序员会不知道JobQueue的意思呢?程序员要做太多技术性工作。给这些事取个技术性的名称,通常是最靠谱的做法。

2.15 使用源自所涉问题领域的名称

        如果不能用程序员熟悉的术语来给手头的工作命名,就采用从所涉问题领域而来的名称吧。至少,负责维护代码的程序员就能去请教领域专家了。
        优秀的程序员和设计师,其工作之一就是分离解决方案领域和问题领域的概念。与所涉问题领域更为贴近的代码,应当采用源自问题领域的名称。

2.16 添加有意义的语境

        很少有名称是能自我说明的一多数都不能。反之,你需要用有良好命名的类、函数或名称空间来放置名称,给读者提供语境。如果没这么做,给名称添加前缀就是最后一招了。
设想你有名为firstName、.lastName、street、.houseNumber、.city、state和zipcode的变量。
        当它们搁一块儿的时候,很明确是构成了一个地址。不过,假使只是在某个方法中看见孤零零一个state变量呢?你会理所当然推断那是某个地址的一部分吗?可以添加前缀addrFirstName、.addrLastName、addrState等,以此提供语境。至少,读者会明白这些变量是某个更大结构的一部分。当然,更好的方案是创建名为Address的类。这样,即便是编译器也会知道这些变量隶属某个更大的概念了。

2.17 不要添加没用的语境

        设若有一个名为“加油站豪华版”(Gas Station Deluxe)的应用,在其中给每个类添加GSD前缀就不是什么好点子。说白了,你是在和自己在用的工具过不去。输入G,按下自动完成键,结果会得到系统中全部类的列表,列表恨不得有一英里那么长。这样做聪明吗?为什么要搞得IDE没法帮助你?

        再比如,你在GSD应用程序中的记账模块创建了一个表示邮件地址的类,然后给该类命名为GSDAccountAddress。稍后,你的客户联络应用中需要用到邮件地址,你会用GSDAccountAddress吗?这名字听起来没问题吗?在这17个字母里面,有10个字母纯属多余和与当前语境毫无关联。
        只要短名称足够清楚,就要比长名称好。别给名称添加不必要的语境。
        对于Address类的实体来说,accountAddress和customerAddress都是不错的名称,不过用在类名上就不太好了。Address是个好类名。如果需要与MAC地址、端口地址和Web地址相区别,我会考虑使用PostalAddress、MAC和URI。这样的名称更为精确,而精确正是命名的要点。

2.18 最后的话

        取好名字最难的地方在于需要良好的描述技巧和共有文化背景。与其说这是一种技术、商业或管理问题,还不如说是一种教学问题。其结果是,这个领域内的许多人都没能学会做得很好。
        我们有时会怕其他开发者反对重命名。如果讨论一下就知道,如果名称改得更好,那大家真的会感激你。多数时候我们并不记忆类名和方法名。我们使用现代工具对付这些细节,好让自己集中精力于把代码写得就像词句篇章、至少像是表和数据结构(词句并非总是呈现数据的最佳手段)。改名可能会让某人吃惊,就像你做到其他代码改善工作一样。别让这种事阻碍你的前进步伐。
        不妨试试上面这些规则,看你的代码可读性是否有所提升。如果你是在维护别人写的代码,使用重构工具来解决问题。效果立竿见影,而且会持续下去。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值