通过《算法导论》学习《代码整洁之道》——有意义的命名

1、名副其实

说明

变量、函数或类的名称应该已经答复了所有的问题。它应该告诉你它为什么存在,它做什么事,应怎么用。

反面举例

《第20章 斐波那契堆》中node结点有一个域为bool型的mark。
mark是标记的意思。下文中,花费了大段的文字说明结点在什么情况下会把打上mark,打上的目的又是什么。
但是因为P300的一句错误的翻译,还是导致了mark的作用难以理解。
其实,mark的作用就是为了区分,在删除一个结点的孩子时,是第一次删除它的孩子还是第二次删除它的孩子。
如果是第一次删除,就给它打上mark,如果是第二次删除,就把mark清掉
因此,将mark重命名为isOneChildDeleted,能省去许多文字和不必要的误解

2、避免误导

说明

专有名称、常见缩写不要使用,以免与它原有的含义混淆
不要让两个名称外形相似、区别较小,容易引起错误。
小写字母l、大写字母O与数字0、1相似,使用时要注意

反而教材

《第20章 斐波那契堆》中有两个内部函数
addNodeToRootList:把一个结点加到根结点组成的链表中
moveNodeToRootList:把一个结点先从原来的结构中移除,再加入到根结点组成的链表中
我本意如此,但是它们的名字相似,还是容易引起误解

3、做有意义的区分

说明

为了避免同一个作用域的命令冲突,而把同一类的两个对象作相似的命令

反面示例

这种情况很常见。比如类似于setData()这样的函数,传入一个data,用于给成员data赋值。
虽然可以通过this->来区分传参和成员变量,但是如果能用命名来区分更好

4、使用读得出来的名字

说明

编程本就是一种社会活动。 如果名称读不出来,讨论的时候就会像个傻鸟

正面示例

《算法导论》中的函数名大多是单词或者单词的组合

5、使用可搜索的名称

说明

单字母和数字常量有个问题,就是很难在一大篇文字中找出来。
名称长短应与其作用域大小相对应。

反正示例

《算法导论》中大部分的局部变量和参数都是简单的x, y, z, n, p, q, r, s, t, A, u, v这样简单的变量命名。
对于本来就晦涩难懂的算法来说,再使用这些没有意义的命名,使这些算法更加难以理解。
但是这里要说的是,过短的、由单个字母或数字组成的命名难以被搜索,增加了编码时候的困难。
书上所给的只是伪代码,不涉及编码的问题,且伪代码比较短,写成这样也无伤大雅。
我们在转换成代码的时候,要把这些字符改成有意义的命名。

6、避免使用编码
说明

把类型或作用域编进名称里面,徒然增加了解码的负担。
带编码的名称通常也不便发音

我的理解:

我认为书中观点过于绝对,有时候把作用域或类型放到变量名中用于相似变量的区分是有必要的。

反面示例

《算法导论》中的大部分外部接口都使用了前缀,比如《第6章 堆排序》中的HEAP-,《第12章 二叉查找树》中的TREE-,《第13章 红黑树》中的RB-
作者写前缀可以基于以下几个原因:
(1)区分内部接口与外部接口,书中只有外部接口使用了前缀,而内部接口没有
(2)为了说明函数所作用的数据结构。文中的伪代码是基于C语言的语法写的,C语言中,没有功能与功能所作用的对象之间的关系这样的概念,只有函数接口与数据结构。
但是在将这些伪代码转换成C++代码时,这些前缀就没有必要保留了,原因如下:
(1)C++中内部函数是private,外部接口是public,不需要通过函数名来区分
(2)C++中的成员函数只需要这个类,也只为这个类的对象服务。究竟是HEAP还是TREE或者RB,只需要在类名中体现就可以了。
(3)大量的冗余信息不便于阅读代码

7、接口与实现

说明

不太理解

8、避免思维映射

说明

不应当让读者在脑中把你的名称翻译为他们熟知的名称

正面示例

《算法导论》虽然喜欢用单个字母作为变量名,但它的变量名也不是随意选择的。比如:
用于数组循环:i, j, k
表示距离:d
表示图:G
表示边:E
表示点:D
表示权值:w
表示未知数:x
表示关键字:k

9、类名

说明

类名不应该是动词。

正面示例

《算法导论》中的伪代码是以C语言为语法基础的。没有类的概念。 我通常取外部接口的前缀作为类名。如CHeap, CTree

10、方法名

说明

方法应当时动词或动名词短语。

正面示例

动词例子:《第18章 B树》,B-TREE-CREATE
动名词短语例子:《第18章 B树》,B-TREE-SPLIT-CHILD

11、别扮可爱

说明

宁可明确,不为好玩

正面示例

《算法导论》中的伪代码都是比较严谨的,没有发现这种情况

12、每个概念对应一个词

说明

给每个抽象概念选一个词,并且一以贯之

反面示例

《第20章 斐波那契堆》中有两个内部函数 deleteChildFromParent:把父结点的某个孩子删掉
removeNodeFromRootList:把根结点中的某个结点删掉
这两个函数都是要删掉结点,但一个是delete,另一个是remove

12、别用双关语

说明

避免将同一单词用于不同的目的,同一术语用于不同的概念。

反面示例

《第10章 基本数据结构》中使用左孩子右兄弟的方法来表示。
虽然是二叉树演变过来的,但是一个指针就是用来指孩子,另一个指针就是用来指兄弟,直接叫child或者sibling就可以了,left和right不需要了
二叉树的后序遍历中也存在类似的问题,当右孩子不存在时,右孩子指针指向父结点。虽然从算法的角度讲,确实是节省的空间。但个人认为,节省的只是O(1)的空间,不多,但是牺牲了可读性,得不偿失。

12、使用解决方案领域名称

说明

尽量使用计算机科学术语、算法名、模式名、数学术语,好过使用务领域的术语。

正面示例

暂时没有找到比较鲜明的例子,但《算法导论》上的命名是比较严谨的。 比如图论中和算法可以用在地图上,但是边仍然用edge而不是road

13、使用源自所涉及问题领域的名称

说明

如果不能使用专业术语命名,就使用业务术语命名。

14、添加有意义的语境

说明

如果名称不能自我说明,可以使用这些方式添加语境:
(1)将它置于良好命名的类、函数、命名空间中
(2)前缀

15、不要添加没用的语境

说明

如果所有命名都包含中一前缀,那么前缀没有必要
语境与当前语境无关,也是多余
命名要精确

反面示例

《算法导论》的许多外部接口都有共同的前缀,就是多余的。 这一点在《避免使用编码》中已经读讲过了