设计软件是困难的,所以你对如何构建一个模块或系统的最初想法不太可能产生最好的设计。如果您为每个主要的设计决策考虑多个选项,您将得到一个更好的结果:设计两次。
假设您正在为GUI文本编辑器设计管理文件文本的类。第一步是定义类将呈现给编辑器其余部分的接口;与其选择第一个出现在脑海中的想法,不如考虑几种可能性。一种选择是面向行的接口,它具有插入、修改和删除整行文本的操作。另一个选项是基于单个字符插入和删除的接口。第三种选择是面向字符串的接口,它可以跨行操作任意范围的字符。你不需要确定每种选择的每个特性;在这一点上,勾勒出几个最重要的方法就足够了。
尝试选择那些彼此截然不同的方法,这样你会学到更多。 即使您确定只有一种合理的方法,无论您认为第二种设计有多糟糕,都要考虑采用第二种设计。思考该设计的弱点并将其与其他设计的特点进行对比将是有益的。
在你草拟出可供选择的设计方案后,列出每种方案的优缺点。对于接口最重要的考虑是对于高级软件的易用性。在上面的例子中,面向行的接口和面向字符的接口都需要在使用text类的软件中进行额外的工作。在部分行和多行操作(如剪切和粘贴选择)期间,面向行的界面将需要更高级别的软件来分割和连接行。面向字符的接口将需要循环来实现修改多个字符的操作。这也值得考虑其他因素:
- 一种替代方案的接口是否比另一种简单?在文本示例中,所有的文本接口都比较简单。
- 一个接口比另一个接口更通用吗?
- 一个接口是否能够比另一个接口更有效地实现?在文本示例中,面向字符的方法可能比其他方法慢得多,因为它需要对每个字符分别调用文本模块。
一旦你比较了不同的设计,你就能更好地确定最佳的设计。最好的选择可能是一个备选方案,或者您可能会发现可以将多个备选方案的功能组合到一个比任何原始选择更好的新设计中。
有时,没有一种选择特别有吸引力,当这种情况发生时,看看是否可以提出其他方案。使用您在最初的替代方案中确定的问题来驱动新的设计。如果您正在设计文本类,并且只考虑面向行的和面向字符的方法,那么您可能会注意到每种替代方法都很笨拙,因为它需要更高级别的软件来执行额外的文本操作。这是一个危险信号:如果有一个文本类,它应该处理所有的文本操作。为了消除额外的文本操作,文本界面需要更紧密地匹配发生在高级软件中的操作。这些操作并不总是对应于单个字符或单个行。这一行推理应该会为文本提供面向范围的API,从而消除了早期设计的问题。
两次设计原则可以应用于系统的许多层次。对于模块,您可以首先使用这种方法来选择接口,如上所述。然后您可以在设计实现时再次应用它:对于text类,您可以考虑诸如行链表、固定大小的字符块或“间隙缓冲区”之类的实现。“实现的目标和接口的目标是不同的:对于实现来说,最重要的是简单性和性能。在系统的更高层次上探索多种设计也很有用,比如何时为用户界面选择特性,或者何时将系统分解为主要模块。在每种情况下,如果您可以比较几个备选方案,就更容易确定最佳方法。
设计两次并不需要花费很多额外的时间。对于较小的模块(如类),您可能不需要超过一两个小时的时间来考虑替代方案。与您将花费几天或几周的时间来实现这个类相比,这只是一小部分时间。最初的设计实验很可能会产生一个更好的设计,这将比花两次时间来设计要好得多。对于较大的模块,您将在最初的设计探索中花费更多的时间,但是实现也会花费更长的时间,而且更好的设计的好处也会更大。
我注意到“两次设计”的原则有时很难被真正聪明的人所接受。当他们长大后,聪明的人发现他们对任何问题的第一个快速想法就足以获得一个好成绩;没有必要考虑第二种或第三种可能性。这很容易养成不好的工作习惯。然而,随着这些人年龄的增长,他们会被提升到越来越困难的环境中。最终,每个人都会到达一个临界点,你的第一个想法不再足够好;如果你想获得真正好的结果,你必须考虑第二种可能,或者第三种,不管你有多聪明。大型软件系统的设计就属于这一类:没有人有足够的能力在第一次尝试时就把它做好。
不幸的是,我经常看到聪明的人坚持执行他们脑海中出现的第一个想法,这导致他们没有发挥出他们真正的潜力(这也让他们在工作中感到沮丧)。也许他们潜意识里认为“聪明人第一次就能成功”,所以如果他们尝试多种设计,那就意味着他们根本不聪明。事实并非如此。这并不是说你不聪明,问题是真的很难。此外,这是一件好事:解决一个需要仔细思考的难题要比解决一个根本不需要思考的简单问题有趣得多。
两次设计的方法不仅提高了你的设计,也提高了你的设计能力。设计和比较多种方法的过程将教会您使设计更好或更差的因素。 随着时间的推移,这将使你更容易排除糟糕的设计,并专注于真正伟大的设计。