有意义的命名与函数

整洁代码

对于软件而言,百分之八十或更多的工作量集中在“维护”上。

认真对待每个变量名,当用为自己第一个孩子命名般的谨慎来给变量命名。

抽象即恶;代码除恶。

离开时要比发现时整洁。

习艺之要有二:知和行。你应当习得有关原则、模式和实践的知识,穷尽应知之事,并且要对其了如指掌,通过刻苦实践掌握它。

下次写代码时,记得自己是作者,要为评判你工作的读者写代码。

有意义的命名

变量、函数或类的名称应该已经答复了所有的大问题。它该告诉你,它为什么会存在,它做什么事,应该怎么用。如果名称需要注释来补充,那就不算是名副其实。

使用读的出来的名称,最好给新人看的时候不需要单独解释。

做有意义的区分,以数字系列命名(a1、a2、a3……)是依义命名的对立面。

使用可搜索的名称,找 MAX_CLASSES_PER_STUDENT 很容易,但是找出数字 7 就麻烦了。采用能表达意图的名称。

类名和对象名应该是名词或名词短语,类名不应当是动词。

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

添加有意义的语境,需要用有良好命名的类、函数或名称空间来放置名称,给读者提供语境。否则,给名称添加前缀就是最后一招了。

不要添加没用的语境,只要短名称足够清楚,就要比长名称好。别给名称添加不必要的语境。

函数

怎么才能让函数表达其意图?该给函数赋予哪些属性,好比读者一看就明白函数是属于怎样的程序?

函数的第一规则是要短小。第二条规则是还要更短小。每个函数都只做一件事,每个函数都依序把你带到下一个函数。

if语句、else 语句、while 语句等,其中的代码块应该只有一行。该行大抵应该是一个函数调用语句,这样不但能保持函数短小,而且,因为块内调用的函数拥有较具说明性的名称,从而增加了文档上的价值。

函数应该做一件事,做好这件事,只做这一件事。

如果函数只是做了该函数名下同一抽象层上的步骤,则函数还是只做了一件事。编写函数毕竟是为了把大一些的概念拆分为另一抽象层上的一系列步骤。

要确保函数只做一件事,函数中的语句都要处于同一抽象层级上。

自顶向下读代码:向下规则。我们想要让代码拥有自顶向下的阅读顺序,想要让每个函数后面都跟着位于下一个抽象层级的函数。

给函数取个好名字,即使用描述性的名字。函数越短,功能越集中,就越便于取个好名字。

别害怕长名称,长而具有描述性的名称,比短而令人费解的名称好,要比描述性的长注释要好,

命名方式要保持统一,使用与模块名一脉相承的短语、名词和动词给函数命名。

函数参数:最理想的参数数量是零,其次是一个,再次是二,尽量避免使用三参数。

参数不宜对付,参数与函数名处在不同的抽象层级。从测试的角度来看,参数更叫人为难,要编写确保参数的各种组合运行正常的测试用例是很难的一件事。

如果函数看来需要两个、三个或更多的参数,就说明也许其中一些参数应该封装为类了。当有一组参数被共同传递,就像下面的 x 和 y,往往就是该有自己名称的某个概念的一部分。比如:

1
2
Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Pointer center, double radius);

给函数取个好名字,能较好的解释函数的意图,以及参数的顺序和意图。对于一元函数,函数和参数应该形成一种非常良好的动词/名词对形式。

副作用,就是函数承诺只做一件事,但还是会做其他被隐藏起来的事。要确保函数无副作用。

参数多数被自然而然地看作是函数的输入,有时会被用作输出而非输入的参数迷惑。在面向对象语言中对输出参数的大部分需求都消失了,因为 this 也有输出函数的意味在内。普遍而言,应避免使用输出参数。如果函数必须修改某种状态,就修改所属对象的状态吧。

1
2
3
4

public void appendFooter(StringBuffer report);

report.appendFooter();

分隔指令与询问,函数要么做什么事,要么回答什么事,但两者不可兼得。函数应该修改某对象的状态或者,返回对象的有关信息,两者都干常会导致混乱。

使用异常替代返回错误码:从指令式函数返回错误码,导致了更深层次的嵌套结构。当返回错误码时,就是在要求调用者立即处理错误。另一方面,如果使用异常替代返回错误码,错误处理代码就能从主路径代码中分离出来,得到简化。

Try/Catch 代码块丑陋不堪,它们搞乱了代码结构,把错误处理与正常流程混为一谈。最好把 try 和 catch 代码块的主体部分抽离出来,另外形成函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

public void delete(Page page) {

try {

deletePageAndAllReferences(page);

} catch(Exception e) {

logError(e);

}

}

private void deletePageAndAllReferences(Page page) throws Exception {

deletePage(page);

registry.deleteReference(page.name);

configKeys.deleteKey(page.name.makeKey());

}

private void logError(Exception e) {

logger.log(e.getMessage());

}

函数应该只做一件事,错误处理就是一件事。

使用错误码,通常暗示某处有个类或枚举,定义了所有的错误码。其他很多类都得导入和使用它。当 Error 枚举修改时,所有引入它的其他类都得重新编译和部署。使用异常替代错误码,新异常就可以从异常类派生出来,无需重新编译和部署。这也是开放闭合原则的一个范例。

别重复自己,重复可能是软件中一切邪恶的根源。

写函数时,一开始都冗长而复杂,有太多的缩进和循环嵌套,有过长的参数列表,名称是随意取得,也有重复代码。然后打磨这些函数,分解函数、修改名称、消除重复。缩短和重新安置这些方法,甚至拆散类。

大师级程序员把系统当做故事来讲,而不是程序来写。

我在实习的时候,开发前两个 feature 基本就是一个或两个函数,feature 比较简单,但是代码也有几十行。整个函数描述了这个 feature 包含的所有功能,简直和整洁代码对函数的要求完全相悖。

导师反复给 cr 了好几次,拆分函数,消除重复代码,重新给函数命名,减少嵌套,尽量让每个函数只做一件事,最后一个函数被拆为了5个函数。再去 review 代码,就会发现思路很清晰流畅,每个函数的功能一句话甚至几个字可以描述清楚,另外就是和 QA 过代码的时候,也比较简单,不需要过多解释代码的逻辑,通过函数命名,一个函数引出下一个函数,很快就过完代码了。作为对比,我自己试着过一下最开始写的一个函数的代码,发现真的很冗杂,不易解释也不易理解,嵌套层次深,时不时要返回前面的代码看下判断条件。

Donate comment here