第 8 章 下沉复杂性
本章从另一个角度探讨如何创建更深的类。假设你正在开发一个新模块,发现有些复杂性不可避免。那么,是应该让模块的使用者处理这些复杂性,还是应该在模块内部处理?如果复杂性与模块提供的功能有关,通常应该选择后者。大多数模块的使用者都比开发人员多,因此让开发人员多费些力气,比让使用者费力更好。作为模块开发人员,你应该尽可能让模块易于使用,即使这意味着自己要做额外的工作。换句话说,让模块的接口简单比让其实现简单更为重要。
本章概览绕不开的复杂性,该由谁来处理?
开发人员很容易反其道而行之:解决简单的问题,然后将困难的问题推给其他人。如果出现不确定如何处理的情况,最简单的方法是抛出异常,让调用者处理。如果不确定要采用什么策略,就可以定义一些配置参数来控制该策略,然后让系统管理员自行确定这些参数的最佳值。
这些做法短期内会让你更轻松,但会加剧复杂性,因为原本只需一个人处理的问题,现在许多人都得处理。例如,如果一个类抛出异常,该类的每个调用者都必须处理它。如果一个类暴露了配置参数,每个部署实例的系统管理员都必须学习如何设置这些参数。
8.1 示例:编辑器文本类
考虑 第 6 章 和 第 7 章 中讨论过的文本类,它为图形界面文本编辑器管理文件中的文本。该类提供了将文件从磁盘读入内存、查询和修改文件在内存中的副本,以及将修改后的版本写回磁盘的方法。学生们在实现这个类时,许多人选择了面向行的接口,提供读取、插入和删除整行文本的方法。这让类的实现很简单,但也为更高层级的软件带来了复杂性。用户界面很少执行整行操作。例如,按键输入会在现有行中插入单个字符;复制或删除所选区域可能修改几个不同行中的部分内容。使用面向行的文本接口时,更高层级的用户界面必须自行拆分和连接行。
面向字符的接口(如 第 6.3 节 中所述)下沉了复杂性。用户界面软件现在可以插入和删除任意范围的文本,而无需拆分和连接行,因此变得更加简单。不过,文本类的实现很可能会变得更加复杂:如果内部将文本表示为行的集合,就必须拆分和连接行,才能实现面向字符的操作。这种方法更好,因为它将拆分和连接行的复杂性封装在文本类中,从而降低了系统的整体复杂性。
译者注(术语说明):原文在此将支持任意字符范围的接口称为“面向字符”,而第 11 章用同一名称表示逐字符操作的接口。两处操作粒度不同,译文保留各处原文名称,请结合相应示例理解。
译者图解拆行与合并行应该由谁负责?
8.2 示例:配置参数
配置参数是将复杂性上推而不是下沉的一个示例。类可以不在内部确定具体行为,而是暴露一些参数来控制行为,例如高速缓存的大小,或放弃请求前的重试次数。该类的使用者必须为这些参数指定适当的值。在当今的系统中,配置参数已变得非常流行,有些系统有数百个配置参数。
配置参数的拥护者认为,它们允许用户根据特定需求和工作负载调整系统。在某些情况下,低层级的基础结构代码很难确定最佳策略,而用户更加熟悉自己的应用领域。例如,用户可能知道某些请求比其他请求更紧迫,因此由用户为这些请求指定更高的优先级是有意义的。在这种情况下,配置参数可以让系统在更广泛的应用领域中获得更好的性能。
但是,暴露配置参数也提供了“偷懒的机会”:回避重要问题,把它们推给别人。在许多情况下,用户或管理员很难确定正确的参数值,甚至根本无法确定。在另一些情况下,只要在系统实现中多做一些工作,就可以自动确定正确的值。设想一个必须处理数据包丢失的网络协议。如果它发送了请求,但在一定时间内未收到响应,就重新发送该请求。确定重试间隔的一种方法是引入配置参数。不过,传输协议也可以自行计算一个合理的值:测量成功请求的响应时间,再将这个时间的某个倍数用作重试间隔。这种方法下沉了复杂性,使用者不必自行找出合适的重试间隔。动态计算重试间隔还有一个优点:运行条件变化时,参数值也会自动调整。相比之下,配置参数的值很容易过时。
因此,你应尽可能避免使用配置参数。在暴露配置参数之前,请问自己:“用户(或更高层级的模块)能确定比我们更好的参数值吗?” 创建配置参数时,请考虑能否提供合理的默认值,让用户只需在特殊情况下指定参数值。理想情况下,每个模块都应彻底解决问题,而配置参数使解决方案不完整,从而增加了系统的复杂性。
译者图解重试间隔的知识在哪里?
8.3 做过头了
下沉复杂性时要谨慎,因为很容易做过头。极端的做法是将整个应用程序的所有功能放进一个类,这显然没有意义。下沉复杂性在以下情况下最有意义:(a)下沉的复杂性与该类的现有功能密切相关,(b)能简化应用程序中的其他部分,(c)能简化类的接口。请记住,目标是最大程度地降低系统的整体复杂性。
第 6 章介绍了学生们如何在文本类中定义一些反映用户界面的方法,例如实现退格键功能的方法。这似乎很好,因为它可以下沉复杂性。但是,将用户界面的知识添加到文本类中并不会大大简化高层级的代码,并且用户界面的知识与文本类的核心功能无关。在这种情况下,下沉复杂性只会导致信息泄露。
译者图解下沉文本机制,不下沉按键含义
8.4 结论
开发模块时,要寻找机会,自己多费一点力气,让使用者少费一些力气。