13.7 跨模块设计决策
在一个完美的世界里,每一个重要的设计决策都会被封装在一个单一的类中。不幸的是,真实的系统最终不可避免地会出现影响多个类的设计决策。例如,一个网络协议的设计会影响到发送方和接收方,而这些可能会在不同的地方实现。跨模块的决策往往是复杂而微妙的,而且它们会造成许多错误,所以良好的文档对它们来说是至关重要的。
跨模块文档的最大挑战是找到一个可以能让开发者很自然地发现的地方。有时,会有一个明显的中心位置来放置这些文档。例如,RAMCloud存储系统定义了一个Status
值,每个请求都会返回这个值,表示成功或失败。为一个新的错误条件添加一个Status
需要修改许多不同的文件(一个文件将Status
值映射到异常,另一个文件为每个Status
提供一个人类可读的信息,等等)。幸运的是,在添加新的状态值时,有一个明显的地方是开发者必须去的,那就是Status
枚举的声明。我们利用了这一点,在该枚举中加入注释,以确定所有其他也必须修改的地方:
typedef enum Status {
STATUS_OK = 0,
STATUS_UNKNOWN_TABLET = 1,
STATUS_WRONG_VERSION = 2,
...
STATUS_INDEX_DOESNT_EXIST = 29,
STATUS_INVALID_PARAMETER = 30,
STATUS_MAX_VALUE = 30
// Note: if you add a new status value you must make the following
// additional updates:
// (1) Modify STATUS_MAX_VALUE to have a value equal to the
// largest defined status value, and make sure its definition
// is the last one in the list. STATUS_MAX_VALUE is used
// primarily for testing.
// (2) Add new entries in the tables "messages" and "symbols" in
// Status.cc.
// (3) Add a new exception class to ClientException.h
// (4) Add a new "case" to ClientException::throwException to map
// from the status value to a status-specific ClientException
// subclass.
// (5) In the Java bindings, add a static class for the exception
// to ClientException.java
// (6) Add a case for the status of the exception to throw the
// exception in ClientException.java
// (7) Add the exception to the Status enum in Status.java, making
// sure the status is in the correct position corresponding to
// its status code.
}
新的状态值将被添加到现有列表的末尾,所以注释也被放在末尾,因为在这里它们最有可能被看到。
不幸的是,在许多情况下,没有一个明显的中心位置来放置跨模块的文档。来自RAMCloud存储系统的一个例子是处理僵尸服务器的代码,这些服务器被系统认为已经崩溃,但实际上仍在运行。消除僵尸服务器需要几个不同模块的代码,而这些代码都是相互依赖的。这些代码片断中没有一个明显的中心位置来放置文档。一种可能性是在每个依赖它的地方复制部分文档。然而,这样做很不方便,而且随着系统的发展,很难保持这种文档的更新。另外,也可以把文档放在需要它的地方之一,但在这种情况下,开发人员不太可能看到文档或知道去哪里找它。
我最近一直在尝试一种方法,即把跨模块的问题记录在一个叫做designNotes
的中央文件中。该文件被划分为有明确标签的部分,每个主要的主题都有一个。例如,这里是该文件的一个摘录。
...
Zombies
------
A zombie is a server that is considered dead by the rest of the
cluster; any data stored on the server has been recovered and will
be managed by other servers. However, if a zombie is not actually
dead (e.g., it was just disconnected from the other servers for a
while) two forms of inconsistency can arise:
* A zombie server must not serve read requests once replacement servers
have taken over; otherwise it may return stale data that does not
reflect writes accepted by the replacement servers.
* The zombie server must not accept write requests once replacement
servers have begun replaying its log during recovery; if it does,
these writes may be lost (the new values may not be stored on the
replacement servers and thus will not be returned by reads).
RAMCloud uses two techniques to neutralize zombies. First,
...
然后,在与这些问题之一有关的任何一段代码中,都有一个简短的注释,提及designNotes
文件。
// See "Zombies" in designNotes.
采用这种方法,只需要一份文档,当开发者需要时,相对容易找到它。然而,这也有一个缺点,就是文档不在任何依赖它的代码附近,所以随着系统的发展,它可能很难保持最新。
Last updated