第 21 章 决定什么是重要的
良好软件设计最重要的要素之一,是区分什么重要、什么不重要。应围绕重要的事情设计软件系统,尽量减少那些没那么重要的事情对系统其余部分的影响。重要的事情应加以强调,让人更容易注意到;不重要的事情则应尽可能隐藏起来。
本章概览怎样分清什么重要、什么不重要?
前面章节中的许多想法,核心都是区分什么重要、什么不重要。设计抽象就是如此:模块接口反映对使用者重要的信息,而对使用者不重要的信息应隐藏在实现中,不让它们显露在外。选择变量名时,要挑出最能传达变量信息的几个单词来组成名称;这些单词表达的正是变量最重要的方面。如果性能对一个模块确实重要,就应围绕性能目标来设计模块。在第 20.4 节的例子中,设计既要尽量减少性能关键路径上的方法调用和特殊情况检查,又要保持整洁、简单、易理解。
21.1 如何决定什么是重要的?
有时,系统的外部约束决定了什么重要,比如第 20.4 节中的性能要求。更多时候,需要设计者自己判断什么重要。即使存在外部约束,设计者也必须弄清楚,要满足这些约束,最重要的是什么。
要决定什么重要,可以看哪些做法能带来更广泛的收益:解决一个问题的同时,也能解决许多其他问题;或者掌握一条信息,就更容易理解许多其他事情。例如,在第 6.2 节关于文本存储的讨论中,用于插入和删除一段字符的通用接口可以解决许多问题,而 backspace 这样的专用方法只能解决单个问题。通用接口能解决的问题更多。在文本类接口这一层,是否为响应退格键而调用接口并不重要,真正重要的只是需要删除文本。不变量也能带来类似的收益:一旦了解变量或结构的不变量,就可以预测它在多种情况下的行为。
有多个备选方案时,更容易判断什么最重要。例如,选择变量名时,先在脑中列出与变量相关的单词,再挑出最能传达信息的几个,用它们组成变量名。这就是“设计两次”原则的一个例子。
有时很难看出什么最重要,经验不足的年轻开发人员尤其容易遇到这种困难。这时,我建议先提出一个假设:“我认为这是最重要的事情。”然后据此构建系统,看看实际效果如何。如果假设正确,就想想为什么正确,当时有哪些线索可以供今后参考。如果假设错误,也没关系:想想为什么会错,当时是否有线索能帮助你避免这个选择。无论结果如何,你都能从经验中学习,逐渐做出更好的选择。
21.2 最小化重要的事情
尽量减少需要关注的事情,系统就会更简单。例如,尽量减少构造对象时必须指定的参数数量,或提供符合最常见用法的默认值。对于确实重要的事情,也要尽量减少需要考虑它们的地方。模块内部隐藏的信息,模块外的代码不必关心。如果异常能在系统较低层级完全处理,系统其余部分就不必关心它。如果配置参数能根据系统行为自动计算出来,而不必提供给管理员手动设置,管理员也就无需关注它。
21.3 如何强调重要的事情
确定了什么重要,就应在设计中加以强调。一种方法是让它更醒目:重要的事情应出现在容易被看到的地方,例如接口文档、名称,或经常使用的方法的参数中。另一种方法是重复,让关键思想反复出现。第三种方法是赋予它核心地位:最重要的事情应处于系统核心,决定周围部分的结构。例如,操作系统的设备驱动程序接口就处于核心地位,因为成百上千个驱动程序都依赖它。
当然,反过来也成立:如果一个想法容易被看到、反复出现,或对系统结构有显著影响,那么它就是重要的。
同样,不重要的事情应淡化处理。应尽可能将它们隐藏起来,不让开发人员频繁接触,也不让它们影响系统结构。
21.4 常见的错误
判断什么重要时,可能犯两类错误。第一类是把太多事情视为重要。这样,不重要的事情就会干扰设计,增加复杂性和认知负荷。例如,方法中的一些参数与大多数调用者无关。另一个例子是第 4.7 节讨论的 Java I/O 接口:它迫使开发人员关注缓冲与非缓冲 I/O 的区别,而这种区别几乎从来都不重要(开发人员几乎总是需要缓冲,不想浪费时间显式请求它)。把太多事情视为重要,往往会导致浅类。
第二类错误是未能认识到某件事的重要性。这会导致重要信息被隐藏,或重要功能没有提供,开发人员只能反复实现这些功能。这类错误会降低开发效率,并导致“未知的未知”问题。
译者图解隐藏什么,必须先问谁需要知道
21.5 打开思路
关注最重要的事情,这个想法也适用于软件设计之外的其他领域。在技术写作中,使文档易于阅读的最佳方法,是在开头明确几个关键概念,再围绕它们组织其余内容。讨论系统细节时,将细节与整体概念联系起来,也有助于理解。
关注重要的事情也是一种很好的生活哲学:确定对你最重要的几件事,尽可能把精力放在这些事情上。不要把时间都浪费在你认为既不重要、也无益的事情上。
“好品味”指的是区分什么重要、什么不重要的能力。良好的品味是优秀软件设计师的重要素质之一。