Skip to content

Latest commit

 

History

History
113 lines (76 loc) · 9.7 KB

软件设计的哲学-01:命名.md

File metadata and controls

113 lines (76 loc) · 9.7 KB

《软件设计哲学》读书笔记(一):命名

前言

俗话说,编码五分钟,命名俩小时。可见在软件工程中命名是多么重要和困难。良好的命名是另一种文档形式:它们使代码更易于理解,减少了对其他文档的需求,并使得检测错误更加容易。相反,名称选择不当会增加代码的复杂性,并造成可能导致错误的歧义和误解。为特定的变量选择一个一般的而非最好的名称,这可能不会对系统的整体复杂性产生太大影响。但是,软件系统具有数千个变量。为所有这些变量选择一个好名字将对整体的复杂性和可管理性产生重大影响。

不幸的是,大多数开发人员并没有花太多时间在思考命名。他们倾向于使用临时想到的名字,只要它与匹配的名字相当接近即可。很少有人去辨别其中细微的差别(比如,磁盘中的物理块,命名为“block”,文件内的逻辑块也命名为“block”,在各自的上下文里是没问题的),虽然并不会造成严重的后果,但是一旦出现问题,则需要花费大量时间来查找一个细微的错误。因此,你不应该只选择“合理接近”的名称,而要去花一些时间来选择精准,明确且直观的好名字。下面是一些建议。

描述一幅图像

选择名称时,目标是在读者的脑海中创建一幅关于被命名事物的性质的图像。一个好名字传达了很多有关底层实体“是什么”,以及同样重要的是,“(它) 不是什么”的信息。在考虑特定名称时,请问自己:“如果有人孤立地看到该名称,而没有看到其声明、文档或使用该名称的任何代码,他们将能够猜到该名称指的是什么吗?还有其它名称可以使画面更清晰吗?” 当然,一个名字可以输入多少信息是有限制的。如果名称包含两个或三个以上的单词,则会变得笨拙。因此,面临的挑战是仅找到捕获实体最重要方面的几个单词。

名称是一种抽象形式:名称提供了一种简化的方式来考虑更复杂的基础实体。像其他形式的抽象一样,最好的名字是那些将注意力集中在对底层实体最重要的东西上,而忽略那些次要的细节。

名称应该精准

良好的命名具有两个属性:精度和一致性。让我们从精度开始。名称最常见的问题是名称太笼统或含糊不清。结果,读者很难说出这个名字指的是什么。

如果变量或方法的名称足够广泛,可以引用许多不同的事物,那么它便不会向开发人员传达太多信息,因此底层实体很可能会被滥用。

像所有规则一样,有关选择精确名称的规则也有一些例外。例如,只要循环仅跨越几行代码,就可以将通用名称(如 i 和 j)用作循环迭代变量。如果您可以看到一个变量的整个用法范围,那么该变量的含义在代码中就很明显了,因此您不需要长名称。

名称也可能太具体,例如在此声明中删除一个文本范围的方法:

void delete(Range selection){...}

参数名称的选择过于具体,因为它建议始终在用户界面中选择要删除的文本。但是,可以在任意范围的文本(无论是否选中)上调用此方法。因此,参数名称应更通用,例如 range

如果你发现很难为一些特定变量选择精确且直观的名称,那么这会是一个危险的信号。表明该变量可能没有明确的定义或目的。发生这种情况时,请考虑其他因素。例如,也许你正在尝试使用单个变量来表示几件事;如果是这样,将表示形式分成多个变量可能会导致每个变量的定义更简单。选择好名字的过程可以通过识别缺点来改善您的设计。

如果很难为基础对象的变量或方法找到简单的名称,则表明基础对象可能没有拥有简洁的设计。

命名的一致性

良好命名的第二个重要属性是一致性。在任何程序中,都会反复使用某些变量。例如,文件系统反复操作块号。对于每种常见用法,请选择一个用于该目的的名称,并在各处使用相同的名称。例如,文件系统可能总是使用 fileBlock 来保存文件中块的索引。一致的命名方式与重用普通类的方式一样,可以减轻认知负担:一旦读者在一个上下文中看到了该名称,他们就可以重用其知识并在不同上下文中看到该名称时立即做出假设。

一致性具有三个要求:首先,始终将通用名称用于给定目的;第二,除了给定目的外,切勿使用通用名称;第三,确保目的范围足够小,以使所有具有名称的变量都具有相同的行为。在本章开头的文件系统错误中违反了此第三项要求。文件系统使用块来表示具有两种不同行为的变量(文件块和磁盘块);这导致对变量含义的错误假设,进而导致错误。

有时您将需要多个变量来引用相同的一般事物。例如,一种复制文件数据的方法将需要两个块号,一个为源,一个为目标。发生这种情况时,请对每个变量使用通用名称,但要添加一个可区分的前缀,例如 srcFileBlock dstFileBlock

循环是一致性命名起作用的另一个领域。如果将诸如 i 和 j 之类的名称用于循环变量,则始终在最外层循环中使用 i,而在嵌套循环中始终使用 j。这使读者可以在看到给定名称时对代码中发生的事情做出即时(安全)假设。

不同的意见:Go 风格指南

并非所有人都同意书中对命名的看法。一些使用 Go 语言的开发人员认为,名称应该非常简短,通常只能是一个字符。在关于 Go 的名称选择的演示中,Andrew Gerrand 指出“长名称模糊了代码的作用。” 他介绍了此代码示例,该示例使用单字母变量名:

func RuneCount(b []byte) int {
    i, n := 0, 0
    for i < len(b) {
        if b[i] < RuneSelf {
            i++
        } else {
            _, size := DecodeRune(b[i:])
            i += size
        }
        n++
    }
    return n
}

并认为它比以下使用更长名称的版本更具有可读性:

func RuneCount(buffer []byte) int {
    index, count := 0, 0
    for index < len(buffer) {
        if buffer[index] < RuneSelf {
            index++
        } else {
            _, size := DecodeRune(buffer[index:])
            index += size
        }
        count++
    }
    return count
}

就个人而言,我不觉得第二版比第一版更难读。如果有的话,与 n 相比,名称计数为变量的行为提供了更好的线索。在第一个版本中,我最终通读了代码,试图弄清楚 n 的含义,而第二个版本中我并没有这种需要。但是,如果在整个系统中一致地使用 n 来引用计数(而没有其他内容),那么其他开发人员可能会清楚知道该短名称。

Go 文化鼓励在多个不同的事物上使用相同的短名称:ch 用于字符或通道,d 用于数据,差异或距离,等等。对我来说,像这样的模棱两可的名称很可能导致混乱和错误,就像在示例中一样。

总的来说,我认为(代码)可读性必须由读者而不是作者来决定。如果您使用简短的变量名编写代码,并且阅读该代码的人很容易理解,那么很好。如果您开始抱怨代码很含糊,那么您应该考虑使用更长的名称(在网络上搜索“ go language short name”(使用语言简称)会识别出几种此类抱怨)。同样,如果我开始抱怨长变量名使我的代码难以阅读,那么我会考虑使用较短的变量名。

Gerrand 发表一个我很赞同的评论:“名称声明与使用之间的距离越大,名称就应该越长。” 前面有关使用名为 i 和 j 的循环变量的讨论是此规则的示例。

结论

精心选择的名称有助于使代码更直观。当某人第一次遇到该变量时,他们对行为的第一次猜测是正确的。如果花一些额外的时间来选择好名字,那么将来你将更容易处理代码。此外,你也不太可能引入错误。培养命名技巧也是一项投资。当第一次决定停止选择一些不那么好的名称时,你会发现想出好名字的过程既令人沮丧又耗时。但是,随着你获得更多的经验,便会发现这件事正变得越来越容易。最终,你几乎不需要花费额外的时间来选择好名字,而是随手拈来。

《软件设计哲学》中关于文件、方法或者变量的命名只是简单给出了几点建议,其目的是为来让整个软件更清晰和便于纠错。除了要选择一些精确的名称之外,同时我们也要注意类似于命名风格,是“驼峰”命名还是“蛇形”命名。

另外,尽量要做到入乡随俗,假如已经有了虽说不是那么合适,但是大家都在用的命名,就上下文保持一致。比如“性别”,已有代码中都使用的“sex”,那么你没必要去采用“gender”来命名了。

还有,不要过度精确,有一些命名整个行业是约定俗成的,那么就没必要去精确。比如我们表示一个坐标点的对象,Point {x, y} 直接使用 (x, y) 即可,就没有必要去采用更精确的英文名称 (longitude, latitude) 经纬度来表示。

如果你在你们的项目代码中发现一些不是那么合适的命名,可以参考这些意见进行一些调整和优化,也欢迎评论给出你优化前后的名称,或者关于命名选择更好的见解。

链接