(给
伯乐在线
加星标,看经典文章
)
英文:hackernoon 译文:开源中国
https://www.oschina.net/translate/the-art-of-software-naming
想把一个东西写好很难。为什么呢?因为只要写好了,才会有很好的阅读体验。我们往往关注了前者而忽略了后者。我们忘记了代码只写一次,但要读很多次。
写得好是指写出来的东西读起来容易,而不是指写作本身,这一过程会产生大量的共鸣。它是指,退后一步,从读者的角度来理解所写的东西。人们必须以人的思维来理解问题,然后用其它人能够理解的方式表达出来。在我看来,软件属于社会科学的一部分。我们要搞清楚代码写出来是给谁看的,不是给人看的吗?
因此,理解如何将思想和过程传达给我们的同行甚至我们自己,这就是编程的核心。
为组件命名
为了说清楚第一个概念,我们来玩一个叫“我们在哪个房间?”的游戏。我会给出一张图,然后你告诉我这是哪个房间。
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