Saleri

The Essence of Software.Chapter.06.概念组合

最后一次修改于

到目前为止,我们只详细讨论了单个概念。但是,即使是最简单的应用程序也包含多个概念,因此我们需要理解概念是如何组合在一起的。

在这一章中,我们将看到如何使用一种新的组合方式将概念结合起来,在这种组合中,不同概念的操作相互关联,因此当一个概念的操作发生时,另一个概念的相关操作也会发生。

比这种相当简单的机制更令人兴奋的是你可以用它做什么。我将向你展示各种各样的组合,从一种微不足道的组合开始,在这种组合中,概念在很大程度上是并行运行的,在没有太多交互的情况下实现各自的目的;然后是一种更丰富的组合,在这种组合中,概念联系更紧密,并产生新的功能;最后是最丰富的组合,在这种组合中,实现了协同效应(synergy),提供了比概念本身更简单、更统一的用户体验。

当你使用概念进行设计时,你可以选择将它们彼此之间更紧密或更松散地同步。更紧密的同步意味着更多的自动化,但也意味着更少的灵活性。我将通过过度同步和同步不足的一些例子来说明处理不当的陷阱。

为什么传统的组合不起作用

软件组件通常以客户端和服务端的模式组装,其中一个组件扮演“客户端”的角色,另一个或多个组件扮演向客户端提供“服务”的角色。这适用于从最小的程序(其中客户端可能是计算数字列表平均值的函数,而服务是提供基本算术和列表操作的内置库)到最大的系统(其中客户端可能是工资单应用程序,而服务是关系数据库)。

这种客户端和服务端的组合允许从更简单的组件构建具有更复杂功能的组件,并且允许对组件进行分层。客户端只能根据其提供的服务来查看它使用的组件;它无法分辨这些组件本身是否在使用其他服务。

对于概念,这种形式的组合不起作用。由于概念根据定义是面向用户的,我们不希望一个概念隐藏在另一个概念之后。此外,概念必须是独立存在的;正是这一点使它们能够彼此独立地被理解,并在不同的环境中被重用。在客户端和服务端的组合中,客户端无法独立于服务运行,甚至除非知道它所使用的服务的行为,否则无法预测其行为。

一种新的组合方式

概念组合不太常见,但更简单。默认情况下,这些概念彼此独立运行。只要单个概念允许,它们的操作可以以任何交错和任何顺序被调用。

想象一下你可能在火车站找到的一排零食和饮料售货机。你可以走到任何一台机器前,投入一枚硬币,然后做出你的选择;你也可以在一台机器里投一枚硬币,然后在另一台机器里投一枚硬币,再回到第一台机器并在那里做出选择,依此类推。对操作顺序的唯一约束是由机器本身施加的:例如,在投币之前你无法得到饮料。

为了让概念一起工作以实现联合功能,我们将同步(synchronize)它们的操作。这将涉及约束操作发生的顺序,以及传入和传出的值之间的关系。

我们可以将找零机与饮料机连接起来,这样当你将一张一美元纸币插入找零机并获得四个25美分硬币时,这四个25美分硬币会自动用于在饮料机中购买苏打水。这就是一种同步。

如果我们只观察操作序列,每台机器的行为仍然与以前一样。但是在组合中,某些操作序列将不再发生——即那些在找零之后没有接着购买饮料的序列。自动化并没有做你以前不能手动做的新事情;它只是让这些事情变得必然发生。

FIG. 6.1 A todo app showing the tasks carrying the label “chores.”

自由组合

最松散的组合是自由组合 (free composition),其中概念被合并在一个产品中,但在很大程度上彼此独立运行。

Todoist是一个简单但优雅的待办事项应用程序(图6.1)。它通过少量附加功能增强了待办事项列表的基本功能,例如将任务组织到项目和子项目中,以及为任务附加标签。让我们看看这些附加功能中的一个,标签(labeling),看看它如何被表达为一个概念组合。

我们将考虑最基本形式的待办事项 (todo)概念和标签 (label)概念(图6.2和6.3)。 待办事项概念维护一组划分为已完成 (done)待处理 (pending)的任务(这些构成了状态);任务在你添加 (add)它之后最初是待处理的,当你将其标记为完成 (complete)时变为已完成(操作原理)。

标签概念将标签与项目相关联,并包括一个查找 (find)操作,该操作生成带有给定标签的所有项目。还有一个清除 (clear)操作,它删除与项目关联的所有标签。操作原理说明,如果你在一个项目上贴上 (affix)一个标签,然后仅用该标签调用查找(并且在此期间不要将标签从该项目上取下 (detach)),则该项目将包含在结果中;反之,如果你从未在项目上贴上标签,或者你取下了标签,则当查询该标签时,该项目不会出现在结果中。

FIG. 6.2 The todo concept defined.

FIG. 6.3 The label concept defined.

在实践中,这些概念提供了更多功能:例如,待办事项可能会将截止日期与任务相关联并相应地显示它们,而标签可能会提供更丰富的查询(例如,查询标签的组合)。然而,对于理解组合而言,这些将是不必要的复杂化。

FIG. 6.4 A free composition of todo and label concepts. In the diagram (right), the circles on the left represent actions provided to the user, and the black arrow denotes the synchronization.

组合如图6.4所示。它为由组合形成的小型“应用程序”命名(待办标签 (todo label)),列出组合中包含的概念(待办事项 (todo)标签 (label)),然后指定同步 (synchronizations)。请注意,标签概念的包含专门将其用于标记任务,通过将其与待办事项概念的任务 (Task)类型进行实例化。

在这种情况下,只有一个同步,它只是说明在待办事项概念中删除任务会导致该任务的标签在标签概念中被清除。也就是说,当一个任务被删除时,它的标签也会消失。如果没有这种同步,从待办事项概念的角度来看不再存在的任务可能仍然在标签概念中具有标签。这将导致一些异常行为:如果你在一个任务上贴上标签,删除了该任务,然后搜索该标签,该任务将出现在结果中。

如果你觉得这平淡无奇,让我向你保证,这确实是一个无聊的组合。我们只是在为一些更有趣的例子做准备。你需要理解的是,将两个概念组合在一起允许用户以任何顺序调用它们的操作。没有同步,唯一的约束是概念本身施加的约束(例如,你不能在添加任务之前删除它)。

在自由组合中,概念在很大程度上彼此独立,但可能仍然需要进行一些记账工作,以确保排除无意义的执行。在这种情况下,在没有任何同步的情况下将概念放在一起将允许一种执行,其中你删除 (delete)一个任务(在待办事项概念中),然后它出现在查找 (find)的结果中(在标签概念中)。通过同步,todo.delete总是紧随其后的是label.clear。你仍然可以执行查找,但是清除操作已经发生了,所以查找不会显示你刚刚删除的任务。

FIG. 6.5 The email concept defined.

也许有些矛盾的是,同步从不添加新的执行。它只是移除一些执行:在这种情况下去除那些没有意义的执行。可以通过交错两个概念的执行获得的每个其他执行仍然保留。例如,组合允许包含以下操作的行为: todo.add (t), label.affix (t, l), todo.complete (t), label.find (l): t——也就是说,添加一个新任务t;在它上面贴上标签l;将任务标记为已完成;查询具有该标签的所有任务并获得相同任务t

因为像这样情况下的同步目的是确保从每个概念的角度来看存在相同的一组事物——标签概念不引用待办事项概念不知道的任务——我们可能会说这些概念是“存在耦合(existence coupled)”的。在其他各个方面,这些概念是正交的。待办事项概念不知道标签的贴上和取下,标签概念也不知道任务是待处理的还是完成的。

这种松散形式组合的例子很常见,特别是在那些不能很容易地支持其组件之间更丰富同步的平台上构建的应用程序。例如,提供诸如评论和点赞等概念的Web服务和内容管理插件能够工作,正是因为它们连接到站点所需的一切都只是一些共享标识符(对于被评论或点赞的项目),就像我们例子中的任务一样。

FIG. 6.6 A collaborative composition of todo and email concepts. The diagram describes the synchronization only partially: the arrow from receive to add does not imply that every email.receive leads to a task.add; as the text says, only messages to todo-user are relevant.

协作组合

一种更紧密的组合形式将概念连接在一起,提供任何一个概念自身都无法提供的新功能。Todoist的一个很好的特性是,你可以向与你的Todoist帐户关联的特殊电子邮件地址发送电子邮件消息,从而甚至无需打开应用程序即可添加任务。

作为一个概念组合来看,这只是电子邮件 (email)概念的接收邮件操作与待办事项 (todo)概念的任务添加操作的同步。为了使其更具体一点,让我们首先定义电子邮件概念(图6.5)。状态包括从每个用户到该用户收件箱中消息的映射;记录每条消息发件人和收件人的记录;以及消息的内容。

发送 (send)操作形成带有某些内容的新消息;接收 (receive)操作获取以前为收件人创建的消息,并将其添加到他们的收件箱中。操作原理表达了消息传输的理念:在用户发送了带有某些内容的消息后,收件人可以接收它,从而将其添加到收件人的收件箱中,并具有发送时与其关联的内容。

这一次,组合(图6.6)稍微棘手一些。它包含了先前的同步,以及将电子邮件概念的接收 (receive)操作与待办事项概念的添加 (add)操作联系起来的新同步。我已将名称todo-user赋予接收任务消息的特殊电子邮件帐户。通过将接收操作约束为由该用户执行,我们确保其他用户接收电子邮件不受此同步的影响。还要注意,添加操作将正在添加的任务与电子邮件消息的内容绑定在一起,正如预期的那样。

在实际的应用程序中,同步更加精细:你可以格式化电子邮件消息的主题和正文以分别设置任务的标题和描述,甚至可以使用井号标签注释将标签贴到任务上。但我提出的简化同步应该传达了设计的本质。

新功能实际上只是一种便利,它自动化了一个步骤。原则上,每当你想到想要添加到待办事项列表的任务时,你都可以给自己发送一封电子邮件,然后在以后的某个时候,阅读所有这些电子邮件并将它们作为任务添加到待办应用程序中。同步省去了你这些额外的工作。

以下是可以使用协作组合的一些方式:

日志记录 (Logging)。 跟踪事件发生的概念可以与其他概念组合。跟踪可能是为了诊断(通过保留并在以后分析导致失败的事件序列来确定失败的原因);性能分析(检查服务的响应能力);分析学(收集有关服务用户及其使用模式的数据);入侵检测(通过搜索可能表明攻击正在进行的请求模式);或审计(例如,记录哪些医院员工访问健康记录)。

抑制 (Suppression)。 在安全上下文中,添加一个概念可能仅仅是为了抑制其他概念中的某些操作。一个访问控制 (access control)概念可以通过将其授予访问权限 (grantAccess)操作与要授权的操作同步,从而阻止未授权用户的操作,这样如果授予访问权限无法发生(由访问控制决定),则与它绑定的操作也无法发生。同样的想法可以更广泛地用于限制访问,例如在社交媒体应用程序中将朋友 (friend)概念与帖子 (post)概念组合在一起,使得一个用户只有在两个用户是朋友的情况下才能阅读另一个用户的帖子。

分阶段 (Staging)。 组合可能会将一项活动的不同阶段汇集在一起。例如,在手机上拨打电话时,用户可能输入的不是要拨打的号码,而是人的名字;然后,联系人 (contact)电话 (phone call)概念的组合分别处理号码的查找和电话的拨打。这种分阶段的分离允许将诸如呼叫转移 (call forwarding)之类的功能视为概念。类似的模式出现在浏览器请求管道中,该管道以使用域名 (domain name)概念进行查找开始,将域名转换为IP地址,然后由http概念将其用于请求本身。

通知 (Notification)。 大多数应用程序和服务都向用户提供通知:日历发送提醒;帮助论坛允许你注册以接收你提出的问题的回复通知;在线商店发送购买确认;运输公司发送交付更新;社交媒体应用程序在你朋友发布新内容时告诉你。所有这些都可以通过一个通知 (notification)概念来完成,该概念提供了一个事件跟踪操作,该操作与较其他概念中感兴趣的操作同步,以及随后自发发生的通知操作(并且其时间、媒介和频率通常可以由用户配置)。

缓解 (Mitigation)。 有时,自由组合给用户太多的余地,导致不受欢迎的行为,这可以通过协作组合来缓解。例如,许多社交媒体平台将帖子 (post)概念与允许用户对单个帖子进行评分的点赞 (upvote)概念组合在一起。如果帖子概念允许编辑,这就产生了一个两难境地,因为用户可能会为一个帖子获得大量积极的点赞,然后完全更改其内容,给人一种误导性的印象,即新内容获得了所有这些认可。解决这个问题的一个常见方法(例如在Slack中使用)是在已编辑的帖子上添加不可磨灭的标记。另一个方法是将帖子概念的编辑 (edit)操作与另一个概念中撤消一些认可的操作同步。例如,在YouTube中,用户可以将对其视频的好评置顶 (pin)。但是如果评论被编辑,它将被comment.editpinning.unpin操作之间的同步自动取消置顶。

推断 (Inference)。 有时用户的操作不是直接执行的,而是从其他操作推断出来的。大多数通信应用程序区分已读和未读 (unread)项目,并允许用户切换其状态。但是要首次将项目标记为已读,应用程序通常会将另一个操作(例如打开项目或滚动浏览它)与将其标记为已读的操作同步。

桥接分离的关注点 (Bridging separated concerns)。 通过自由组合分离关注点,概念通常可以提高应用程序的清晰度和可用性。例如,在移动电话上,蜂窝网络 (cellular)无线网络 (wifi)概念让你彼此独立地管理你对蜂窝数据和本地网络的使用,以及使用数据的应用程序。然而,有时需要将已经分离的概念耦合起来。例如,Apple的播客(Podcasts)应用程序让你选择防止使用蜂窝连接下载播客 (podcast)(因此当你本来可以通过Wi-Fi免费获取它时,却消耗了你的数据配额)。

协同组合

在自由组合中,软件产品由很大程度上正交的概念组装而成,每个概念带来自己的功能,同步仅用于记账。在协作组合中,同步在概念之间创建了连接,这些连接提供了自动化,因此提供了一些单个概念本身不提供的新功能。

在协同组合中,发生了一些更微妙的事情。通过将概念更紧密地同步在一起,一个概念的功能开始增强另一个概念实现自身目的的能力。现在,组合的整体价值大于各个概念价值的总和。

为了说明这种现象,假设在待办事项 (todo)标签 (label)概念的组合中,我们要用一个内置标签待处理 (pending)来表示任务的待处理/完成状态,该标签在添加任务时自动贴上,在任务完成时取下。这可以用两个同步来描述(图6.7):一个在添加任务时贴上标签,另一个在任务被标记为完成时取下它。为了保持一致性,我添加了第三个同步,导致在贴上标签时将任务标记为完成。

这种组合的优点是,标签查询功能现在合并了任务是否待处理。有一个统一的单一界面,在提供逻辑查询语言的标签概念的更丰富版本中,你可以查询“待处理且紧急”的任务,例如。此外,不再需要记住任务是待处理还是已完成的待办事项概念的状态组件,因为该信息现在存储在标签中。

FIG. 6.7 A synergistic composition of todo and label concepts.

这种简化的设置只能暗示这种组合可以带来的好处。在下一节中,我们将研究一个更强大但也出奇复杂的例子,说明协同组合有多么微妙。在此之前,先看一些其他协同组合的例子:

Gmail标签和废纸篓。 谷歌的电子邮件应用程序Gmail正是以我刚才描述的方式协同使用标签。当发送电子邮件消息时,会自动为其贴上已发送 (sent)标签,而打开已发送消息列表标有“已发送”的按钮只是绑定了对带有已发送标签的消息的查询。同样的想法扩展到了废纸篓 (trash):删除消息会为其贴上已删除 (deleted)标签,而删除该标签会恢复该消息。

Moira列表和组。 麻省理工学院(MIT)使用了一个在20世纪80年代内部开发的名为Moira的系统来管理邮件列表。为了允许多个用户维护邮件列表,你可以创建这些用户的第二个列表;然后可以将该列表指定为第一个列表的所有者。要授予或保留控制权,你只需在第二个列表中添加或删除用户即可。这是一种可爱的协同,其中邮件列表 (mailing list)概念与管理组 (administrative group)概念完美地组合在一起,使得后者不需要自己的界面。

免费样品和购物车。 一些在线商店在他们的购物车中包含了诸如免费样品(或目录等)之类的用户尚未购买的物品。这是购物车 (shopping cart)概念和免费样品 (free sample)概念之间的协同组合;将免费样品添加到订单的操作与将物品添加到购物车的操作同步。这对用户有利(他们可以在一个地方看到他们所有的物品,包括免费物品),并且使开发人员更轻松(消除了单独存储免费样品的需要)。但是,与许多协同效应一样,可能会出现意想不到的障碍。

Photoshop通道、蒙版和选区。 协同作用最突出的例子出现在Adobe Photoshop中,其中蒙版 (mask)选区 (selection)通道 (channel)概念以非凡的威力协同工作。

废纸篓和文件夹的美妙协同

你可能会感到惊讶,当我在第4章介绍废纸篓 (trash)概念时,我将废纸篓仅视为一组项目。在最熟悉的概念实例(在Macintosh或Windows桌面上)中,废纸篓不是一组项目,而是一个文件夹 (folder)

在该概念的一些早期版本中,废纸篓只是一个集合,如果你删除了一个文件夹,它的内容会被分解并单独放入废纸篓中。显然,这使得恢复文件夹变得困难,而更好的设计(Apple从一开始就使用的设计)将这两个概念融合在一起。

现代设计可以理解为这两个截然不同的概念——废纸篓 (trash)文件夹 (folder)的非常巧妙的组合。通过用这些术语来看待它,我们可以分离出行为的各个方面。要理解废纸篓的基本概念——可以删除项目并稍后恢复,或通过清空将其永久删除——你只需要了解废纸篓概念;要了解废纸篓中的项目是如何出现的,你只需要了解文件夹

这种组合所创造的协同效应在用户界面的精简中显而易见。不需要特殊操作来列出废纸篓中的项目,因为在这方面,它只是一个普通的文件夹,你可以像往常一样对其应用排序、搜索等功能。恢复项目也不需要特殊的控件;你只需将其移出废纸篓文件夹即可。当然,正是这种协同作用使得废纸篓能够保留被删除文件夹的结构,以便它可以被完整地恢复。

实现这一点的同步并不复杂。将文件移至废纸篓(在文件夹概念中)与删除文件(在废纸篓中)同步;移动文件夹与删除其包含的所有文件夹和文件同步;同样,将文件夹移出废纸篓与恢复所有这些文件夹和文件同步。

FIG. 6.8 Synergistic composition of the trash and folder concepts. Items can be sorted by “date added,” which corresponds to the date of deletion, elegantly reusing a general feature of folders. The sorting into volumes is a more troubled feature, since it applies only to the trash folder.

协同很少是完美的

在概念之间完美合并功能很少是可能的,因此大多数协同都有一些代价。废纸篓不完全像任何其他文件夹;最明显的是,它需要提供清空 (empty)操作,因此仅在该文件夹中出现了一个小按钮。

Macintosh废纸篓的设计者们已经尽最大努力来最大程度地减少这种不一致性。例如,他们抵制包含“删除日期”字段,这本来只适用于废纸篓文件夹,而是巧妙地包含了“添加日期”字段,这适用于所有文件夹,并且对于废纸篓来说碰巧允许按删除日期排序。

更令人烦恼的是,在Macintosh桌面中,即使有多个驱动器,也只有一个废纸篓文件夹。因此,与任何其他文件夹不同,废纸篓可以“容纳”来自不同卷的项目;为了弄清这一点,最近版本的macOS允许废纸篓中的项目按卷进行分组,这是其他文件夹不具备的功能(图6.8)。

有时将废纸篓表示为文件夹可能会令人困惑:因此我说废纸篓可以“容纳”项目时有些犹豫。在第5章中,我提到了这样一个场景:你将可移动驱动器插入笔记本电脑,并从中将一些项目移至废纸篓文件夹,希望在驱动器上腾出空间。因为只有一个废纸篓文件夹,并且它“属于”你的笔记本电脑,你可能会认为此操作会释放驱动器上的空间。

但是,正如废纸篓按卷的划分所表明的那样,废纸篓文件夹不仅属于外部驱动器,而且属于机器,将文件移至废纸篓永远不会释放空间。如果你弹出外部驱动器,你将看到其被删除的项目从废纸篓文件夹中消失,并在你重新连接它时再次出现。

过度同步和同步不足

当你组合概念时,同步成为整体产品设计的重要组成部分。同步过多会剥夺用户的控制权,阻止自由组合中允许的一些场景。相反,同步过少会给用户带来本可以自动化的工作负担。它还可能承认意想不到的和不受欢迎的行为,有时会导致灾难性的后果。

过度同步与取消研讨会的奇怪案例

在Apple的日历(Calendar)应用程序中,支持在给定时间存储事件的日历事件 (calendar event)概念与邀请 (invitation)概念组合在一起,使得一个用户可以向另一个用户发送一个暂定事件,第二个用户可以接受或拒绝。

在最初的设计中,删除邀请事件会让用户进退两难。删除 (delete)操作无助地与拒绝 (decline)操作耦合在一起,因此如果你不通知发出邀请的人你正在拒绝,你就无法删除该事件。如果你只是想清理日历空间而不想冒犯朋友,这已经够糟糕的了。如果邀请是垃圾邮件,情况就更糟了。在这种情况下,通知会帮助垃圾邮件发送者,确认你的电子邮件地址有效,并增加未来收到垃圾邮件的可能性!

多年来,一种笨拙的变通方法是唯一的出路:创建一个新日历,将事件移至其中,然后完全删除该新日历。最终(2017年的某个时候),Apple解除了删除和通知操作的耦合(图6.9)。

正是这种设计缺陷,但在谷歌日历(Google Calendar)中,导致了我的实验室在研讨会公告方面遇到的一个令人费解的问题。通常在发布初步公告后不久会发送取消研讨会的通知。然后组织者会发送后续跟进,向人们保证研讨会实际上并没有取消。事实证明,这些取消是虚假的。实验室的个人收到了研究研讨会的通知,并将它们作为事件添加到他们的个人日历中。然后当有人删除这样的事件时,谷歌日历会自发地向与最初邀请关联的电子邮件地址发送一条取消消息——在这种情况下,这是一个拥有1000多名成员的邮件列表(listserv)!

FIG. 6.9 The original Apple Calendar dialog that unhelpfully always synchronized deleting an event with notifying the sender (left), and the most recent version (right) that fixes the problem by making the synchronization optional.

其他一些过度同步的例子:

Tumblr有问题的设计。 在Tumblr博客平台上,如果你想允许人们评论你的帖子,你可以在帖子标题的末尾插入一个问号。在进行这种同步中,一个操作(这里是创建帖子 (post))是否产生伴随操作(启用评论 (comments))取决于第一个操作的参数的精确内容。这不仅是一个不受欢迎的同步——如果你的标题是一个修辞疑问句怎么办?——而且还使得标题 (title)概念不再通用(参见注释48中的“概念是通用的”),因此更难理解。后来,Tumblr改为可以勾选一个复选框。

Twitter中的回复。 Twitter中出现了类似的问题。直到2016年中期,以用户名开头的推文 (tweet)都被解释为回复。除了别的以外,这导致了一种惯例,即人们想要在推文开头提及其他人,但不想使其成为回复,会在名字前插入一个句号(”.@daniel Really?”)。

不需要的Google同步。 如果你有一个Google帐户,其用户名 (username)是一个外部电子邮件地址 (email address),并且你为该帐户添加了Gmail功能,你的用户名将自动更改以匹配新的Gmail电子邮件地址,并且你将无法恢复旧的用户名和电子邮件地址![74]

爱普生专横的打印机驱动程序。 在其照片打印机中,爱普生想要阻止用户进行可能损坏打印机的某些设置组合是可以理解的。例如,你不能为纸张选项 (paper option)选择“厚”,同时为纸张来源 (paper source)设置从顶部进纸(这要求纸张在进入打印机时弯曲),这似乎是合理的。但打印机驱动程序做得更远,当选择顶部进纸时,会阻止你使用大多数艺术纸张设置,大概理由是这些设置仅适用于厚纸。然而这是错误的;许多艺术纸既薄又柔韧,这种约束迫使你要么从前面进纸(这非常不方便,因为每张纸都必须手动加载),要么从顶部进纸,但使用错误的墨水设置进行打印。

同步不足和一个永远无法加入的组

大约一年前,我想建立一个邻里讨论的在线论坛,所以我为此建立了一个Google Group。因为我事先不知道邻居的用户名,所以我给他们发送了该组的链接,建议他们请求加入。

不幸的是,这不起作用,人们甚至无法访问带有要求加入按钮的页面。我以为我已经正确设置了组;特别是在响应“选择谁可以加入”的“权限(permissions)”下(图6.10,顶部),我选择了“任何人都可以请求(Anyone can ask)”。

事实证明,有不同的设置决定该组是否出现在组目录中(图6.10,底部)。除非此功能的可见性设置为“网络上的任何人(Anyone on the web)”,否则该组不仅被排除在目录之外,而且根本无法访问,甚至加入请求也无法访问!

决定谁可以加入的操作(在权限 (permission)概念中)和设置可见性的操作(在组目录 (group directory)概念中)之间缺乏同步,让我陷入了这种困境。从那时起,谷歌调整了设计。这两个控件都放在同一页面上(图6.11),但它们仍然不同步,因此对“谁可以加入?”选择“任何人都可以加入”将不起作用,除非你还将“谁可以查看群组?”从默认设置更改为选择“网络上的任何人”。

FIG. 6.10 Access controls for Google Groups (2019). Top, the options shown if you click on “Basic Permissions” in the “Permissions” tab; bottom, the options shown if you click on “Directory” in the “Information” tab. With the default setting of “Directory,” the permission setting was ineffectual, since the page displaying the ask-to-join button was private to members!

其他一些同步不足的例子:

Lightroom导入。 Adobe的照片编目和编辑应用程序Lightroom Classic提供了一种丰富的导入机制,该机制不仅将照片从存储卡或相机复制到你的硬盘驱动器,而且还可以执行一系列附加任务,例如:将复制的照片移动到首选位置;向元数据添加版权信息;对每张照片应用显影设置;添加关键字;建立预览;以及弹出包含原件的外部驱动器或闪存卡。所有这些同步都是可选的,并由一个相当复杂的用户界面对话框中的首选项控制。

2015年,Adobe团队发布了Lightroom 6.2版本,其中包括简化了导入对话框,删除了一些专业摄影师已经习惯依赖的同步功能。用户的反应非常迅速和负面,以至于Adobe实际上恢复了更新。

Google表单、表格和数据可视化。 在一个很好的协同效应例子中,Google表单(Google Forms)使用Google表格(Google Sheets,Google的电子表格应用程序)作为收集数据的存储库。表单应用程序还包含一个很好的可视化工具,它生成饼图、直方图等来总结数据。不幸的是,可视化与数据的不同副本同步,与表格不同。因此,对表格的编辑(例如,数据清理,如删除重复的表单提交)不会反映在可视化中。

FIG. 6.11 A more recent version of Google Group permissions (February 2021), which helpfully consolidates the permissions for viewing into a single setting, and places it on the same page as the permissions for joining. Unfortunately the default is as shown, and asking to join requires being able to see the group.

Zoom的粘滞举手。 在Zoom视频会议应用程序中,参与者可以举起虚拟手向主持人发出想要发言的信号。在不说话时,参与者通常会将他们的麦克风静音以减少背景噪音。然后当被叫到时,参与者取消静音,提出观点,然后再次静音。但是大多数参与者忘记了他们举起的手,使主持人感到困惑,主持人后来注意到举起的手,不确定那个人是只是忘了放下它,还是想再说话。同步举手 (raised hand)音频静音 (audio mute)概念可能会消除这种烦恼。

Therac 25放射治疗机。 20世纪80年代末发生的一系列灾难性的放射治疗机事故最终被追踪到是一个同步缺陷。辐射源是可调节的电子束,通过(a)用于聚焦电子的磁准直器,或(b)将电子转换为X射线光子的平滑滤波器(取决于旋转转盘的位置)。

患者以两种模式之一进行照射:直接用电子或用X射线。X射线产生需要高电子电流——远远超过安全直接施加的电流。因此,目的是确保只要电子束电流高,平滑滤波器就位。可悲的是,同步机制的一个缺陷有时会导致高电流直接传递给患者,导致大剂量、致命的过量照射。尽管该缺陷被归因于编程错误——未能确保预期同步的代码中的一个错误——但更好的设计本可以防止它。

经验教训和实践

本章的一些经验教训:

  • 概念不像是程序那样被组合,较大概念包含较小概念。相反,每个概念都在平等条件下向用户公开,并且软件应用程序或系统是串联运行的概念的集合。
  • 概念通过同步它们的操作来组合。这永远不会添加新的概念执行,而是约束现有的执行,消除孤立概念可能存在的一些操作序列。
  • 在自由组合中,概念彼此独立运行,仅受到一些记账约束以确保(例如)概念对存在哪些事物具有一致的看法。
  • 在协作组合中,概念通过自动化一起工作以提供新功能。
  • 在协同组合中,概念更紧密地交织在一起,一个概念的功能帮助另一个概念实现其目的。
  • 即使概念本身很熟悉,组合也提供了进行创造性设计的机会。协同效应往往是设计的本质,从简单部分的组合中带来意想不到的力量。
  • 同步是软件设计中必不可少的一部分。同步不足可能导致不当或令人困惑的行为,错失自动化的机会;过多则可能限制用户的选择。

现在你可以应用的一些实践:

  • 如果你设计的概念看起来很复杂,请尝试将其视为更简单概念的组合,这可能更容易描述和证明(通过拥有更清晰的目的和更令人信服的操作原理)。(更多关于这一点的信息见第9章。)
  • 选择概念时,要注意寻找可以重用的熟悉概念(第10章)。例如,你可能会识别出一个通知概念,然后找到在整个应用程序中更统一地使用通知的方法。
  • 在设计中,首先决定要包含哪些概念,然后决定如何同步它们。从消除不良行为的同步开始。然后考虑自动化,但一定要给用户留下足够的灵活性。
  • 寻找可以通过将一个概念与另一个概念组合来简化该概念的协同作用。但请记住,完美的协同作用是很少能实现的。