第五章:弯曲或折断(1)
在“可撤销性”中,我们谈到不可撤销的决策的危险,在本章,我们将告诉你怎样做出可撤销的决策,以使你的代码在面对你不确定的世界时保持灵活性和可适应性。
一、解耦与得墨忒耳法则
好篱笆促成好邻居。在“正交性”和“按合约设计”中,我们提出,编写“羞怯”的代码是有益的,但“羞怯”的工作方式有两种:不向别人暴露自己,不太与太多人打交道。 把你的代码组织成最小组织单位(模块),并限制它们之间的交互,如果随后出于折中必须替换某个模块,其他模块仍然能够继续工作。 使耦合减至最少 当我们要求某个对象完成特定服务时,我们想要它替我们完成该服务,我们不希望这个对象给我们一个第三方对象,我们必须对其加以处理才能获取所需服务。 对象间直接的横贯关系有可能很快带来依赖关系的组合爆炸(如果n个对象全都互相了解,那么对一个对象的改动可能导致其他n-1个对象都需要改动)。 有许多不必要的依赖关系的系统非常难以维护,往往高度地不稳定,为了使依赖关系保持最少,我们将使用得墨忒耳法则设计我们的方法和函数。 函数的得墨忒耳法则 函数的得墨忒耳法则试图使任何给定程序中的模块之间的耦合减至最少。它设法阻止你为了获得对第三方对象的方法的访问而进入某个对象。通过编写尽可能遵守得墨忒耳法则的“羞怯”代码,我们可以实现我们的目标:
使模块之间的耦合减至最少。使用得墨忒耳法则将是你的代码适应性更好、更健壮,但也有代价:作为总承包人,你的模块必须直接委托并管理全部子承包人,而且不牵涉你的模块的客户。在实践中,这意味着你将编写大量的包装方法,它们只是把请求转发给被委托者。这些包装方法既会带来运行时代价,也会带来空间开销,有些应用中这可能会有重大影响。 与任何技术一样,你必须平衡你的特定应用的正面因素和负面因素。事实上通过反转得墨忒耳法则,使若干模块紧密耦合,你可以获得重大的性能改进,只要那些被耦合在一起的模块是众所周知可以接受的,你的设计就没有问题。
二、元程序设计
再多的天才也无法胜过对细节的专注。细节会弄乱我们整洁的代码——尤其是如果它们经常变化,甚至会破环系统,引入新的bug。在我们与他们作斗争时,我们可以让我们的代码变得高度可配置和“软和”——也就是容易适应变化。 动态配置 首先我们要让我们的代码变得高度可配置。所有的选项应该作为配置选项,而不是通过集成或工程实现。
要配置,不要集成。要用元数据描述应用的配置选项:调谐参数、用户偏好、安装目录等等。 元数据是关于数据的数据。元数据是任何对应用进行描述的数据——应用应该怎么运行、应该使用什么资源,等等。通常元数据在运行时而不是编译时被访问和使用。 元数据驱动的应用 但我们不只是想把元数据用于简单的偏好,我们想要尽可能多的通过元数据配置和驱动我们的应用。我们的目标是以声明方式思考,并创建高度灵活和可适应的程序。我们通过采用一条一般准则来做到这一点:为一般情况编写程序,把具体情况放在别处——在编译的代码库之外。
将抽象放进代码,细节放进元数据。我们想要推迟大多数细节的定义,直至最后时刻,并且尽可能让细节保持“软和”——尽可能易于改动。 不要编写渡渡鸟代码 没有元数据,你的代码不可能获得它应有的适应性和灵活性。 毛里求斯岛上的渡渡鸟不能适应人类和他们的家畜的出现,很快就灭绝了,这是人类记载的第一次物种的灭绝。 不要让你的代码走上渡渡鸟的道路。
三、时间耦合
时间有两个方面对我们很重要:并发和次序。 我们需要容许并发,并考虑解除任何时间或次序上的依赖。这样做我们可以获取更多灵活性,并减少许多开发领域中的任何基于时间的依赖:工作流分析、架构、设计、还有部署。 工作流 在许多项目中,我们需要把用户的工作流作为需求分析的一部分进行建模和分析,我们想要找出在同一时间可能发生了什么,以及什么必须以严格的次序发生。要做到这一点,一种途径是使用像UML活动图这样的表示法来捕捉他们对工作流的描述。分析工作流,以改善并发性。架构
用服务进行设计为并发进行设计 首先,你需要对任何全局或静态变量加以保护,使其免于并发访问。此外,不管调用的次序是什么,你都需要确保给出的是一致的状态信息。 对并发和时序依赖进行思考还能够引导你设计更整洁的接口。
总是为并发进行设计。部署 一旦你设计了具有并发要素的架构,对许多并发服务的处理进行思考就会变得容易:模型变成了普遍的。 现在你可以灵活地处理应用的部署方式:单机、客户——服务器、或是n层,通过把你的系统架构成多个独立的服务,你也可以让配置成为动态的。通过对并发做出规划,并且解除各个操作在时间上的耦合,你可以拥有所有这些选择——包括单机、包括选择不并发。 走另外的路(设法给非并发应用增加并发)要困难得多,如果在设计时就考虑了并发,到时候我们就可以更容易的满足可伸缩性或性能需求。
No Comments