想把一个东西写好很难。为什么呢?因为只要写好了,才会有很好的阅读体验。我们往往关注了前者而忽略了后者。我们忘记了代码只写一次,但要读很多次。
写得好是指写出来的东西读起来容易,而不是指写作本身,这一过程会产生大量的共鸣。它是指,退后一步,从读者的角度来理解所写的东西。人们必须以人的思维来理解问题,然后用其它人能够理解的方式表达出来。在我看来,软件属于社会科学的一部分。我们要搞清楚代码写出来是给谁看的,不是给人看的吗?
因此,理解如何将思想和过程传达给我们的同行甚至我们自己,这就是编程的核心。
为组件命名
为了说清楚第一个概念,我们来玩一个叫“我们在哪个房间?”的游戏。我会给出一张图,然后你告诉我这是哪个房间。
3个问题中的第1个

从这个图很容易判断出来是在客厅。我们从一个组件就能知道所处的房间。这非常容易,我们继续。
3个问题中的第2个

从这个物体很清楚的知道这是在卫生间。
发现什么规律了吗?房间的名称是一个标签,它定义了这个房间里有什么。有了这个标签,我们不知道进去看也知道里面有些什么东西。这足以建立我们的第一个推论:
推论 1: 容器的名称包含了其功能元素
注意这是最基本的“鸭子类型”[译者注:如果它的动作像一只鸭子,那它就是鸭子]。如果有一张床,那这里就是卧室。
反过来也是如此:基于容器的名称,我们可以推断出它的组成部分。如果我们谈论一个卧室,很可能它有一张床。这样产生了我们的第二个推论:
推论 2: 可以根据容器的名称推断其中的组件
显然我们已经有了一些规则,让我们把这些规则应用到下一个房间。
3个问题中的最后一个

哇,床和马桶怎么会在同一个房间?这个房间的定义很模糊,朦朦胧胧,如果一定要用前面的两个推论来为这个房间命名,它只能称为怪物房间。
这里的问题不在于房间里物体的数量,而在于完全无关的事物被看作有同样的功能。在家里,我们会把相关的有类似作用或意图的物品放在一起。如果把作用不同的东西胡乱放在一起,就让人搞不明白架构师到底想怎么来使用这些东西。由于混乱,我们在这里不知所措。
推论 3: 容器定义的明确程度与其内部组件的紧密程度成正比。
这似乎不容易理解,那来看看图示:

如果组件相关,就很容易找到一个好名字[译者注:指容器的名字]。如果事务各不相干,找个合适的名字就会变得困难。这里提到的关系,可能是指它们的功能、目的、策略、类型等。在我们谈到标准之前,关系本身并不包含太多意思。现在先不要急,我们很快就会讲到。
这对于软件同样适用。我们有组件、类、函数、服务、应用程序和其它一些东西。Robert Delaunay 曾经说过“我们的理解与我们的感知相关。”在当前的技术背景下,我们的代码是否能让读者以最简单的方式感知到业务需求呢?
示例 1: HTTP 领域和汽车 domain and a car
HTTP 是一个领域,它有请求和响应。如果我们我们在其中放入一个汽车组件,那就不能再称这为 HTTP。这种情况下它就已经变得混乱了。
public interface WhatIsAGoodNameForThis {
/* methods for a car */
public void gas(); public void brake(); /* methods for an HTTP client */
public Response makeGetRequest(String param);
}

示例 2: 通过词语来耦合
在类名中添加 Builder 或者其它以 er 结尾的单词是种常见的模式。SomethingBuilder、UserBuilder、AccountBuilder、AccountCreator、UserHelper、JobPerformer。

通过名称,我们可以了解三件事情。首先,在类名中使用 Build 这个动词意味着它是穿着类这件外衣的程序。第二,它有两个隐藏在内部的元素,User 和 Builder,这意味着可能违反了封闭性原则。第三,这意味着 Builder 可以访问到 User 的内部工作,毕竟它们彼此纠缠。
这类似于工厂模块。我们的示例代码在整个代码库中滥用时,它就会成为一个问题。此外,我得提醒你,在工厂模式中不需要什么类。应用程序的 createUser() 就能完成工厂的工作。
[译者注:Builder 也是一种模式,所以关于作者的这个观点,请慎思]
示例 3: Base
来看一点实际项目中的例子。第一个例子是 I18n(国际化)的 Ruby Gem (为了简便起见,只列出了类和方法的名称):
class Base def config def translate def locale_available?(locale) def transliterate end
这里的 Base 并不能表达什么意思。它可以进行配置和翻译,也可以描述一个位置是否可用。它做了一些各不相同,毫不相干的事情。
示例 4: 名称引导设计
我们在谈论名称如何引导我们的设计时,提到了好几个例子,让我们感兴趣的例子中,有一个如下:
class PostAlerter def notify_post_users def notify_group_summary def notify_non_pm_users def create_notification def unread_posts def unread_count def group_stats end
PostAlerter 这个名称暗示我们它的功能是在提交的时候提醒某人。然而,unread_posts、unread_count 和 group_stats 却很明显在干别的事情,这就使得类名称不太理想。如果把这三个方法改到名为 PostsStatistics 的类中,表达出来就更清晰,让新接触的人一看就能明白。
class PostAlerter def notify_post_users def notify_group_summary def notify_non_pm_users def create_notification end class PostsStatistics def unread_posts def unread_count def group_stats end
示例 5: 奇怪的名称
Spring 框架中有一些例子说明组件做的事情太多,其名称类似于我们的怪物房间。这里就有一个 (因为这个就太多了点):
class SimpleBeanFactoryAwareAspectInstanceFactory { public ClassLoader getAspectClassLoader() public Object getAspectInstance() public int getOrder()
public void setAspectBeanName(String aspectBeanName)
public void setBeanFactory(BeanFactory beanFactory)
}
示例 6: 改变一下,说说好名称
我们讲了太多不好的名称。D3 的 arc 中就定义了不错的名称,比如:
export default function() { /* ... */
arc.centroid = function() { /* ... */ }
arc.innerRadius = function() { /* ... */ }
arc.outerRadius = function() { /* ... */ }
arc.cornerRadius = function() { /* ... */ }
arc.padRadius = function() { /* ... */ }
arc.startAngle = function() { /* ... */ }
arc.endAngle = function() { /* ... */ }
arc.padAngle = function() { /* ... */ } return arc;
}
这些方法每一个意义都很有完整的意义:它们都是基于弧所拥有的属性来命名的。下图中我非常喜欢的一点在于它真的很简单。

方法 1: 拆解

应用场景:你不能为类或组件找到好名称,但你知道如何拆解它们,而且期望给它们的组合找到一个好名称。
这包含两个步骤:
1. 确认我们拥有的概念
2. 拆解它们
在马桶 + 床的情况下,我们把不同的事物拉开,床在左,马桶在中。好了,我们终于把事物拆分成两个部分,使它们不再那么别扭了。
如果你不能为某个事物找到好名称的时候,也许是因为你面前不只一件事物。不过你现在已经知道对多个事物命名是件困难的事情。当遇到麻烦的时候,不妨确认一下构成这个事物的部分和动作。
示例
我们有一个尚未命名的类,包含 request、response、headers、URLs、body、caching 和 timeout。把所有这些从主类中拉出来,我们剩下了这样一些组件:Request、Response、Headers、URLs、ResponseBody、Cache、Timeout 等。如果我们已知这些类的名称,就会相当确定我们正在处理一个 Web 请求。HTTPClient 会是个不错的 Web 请求组件名称。
遇到困难的代码时,不要一开始就想着整体。考虑一下部分。
方法 2: 发现新概念

应用场景:某个类并不简单或者不清晰时
发现新的概念需要业务领域的知识。如果在软件中使用业务术语,因为专业语言已经建立了而且到处都在使用(Evans, 2003),不同专业领域的专业人士就会使用同样的习语。
翻译得不错哦!