本文源自丹尼尔-杰克逊(Daniel Jackson)教授所著的《软件的本质》(The Essence of Software)一书。书中介绍了一种名为 "概念设计 "的半正式方法。这种方法和之前介绍的 Christopher Alexander 所提出的「Design patterns」有许多相似的地方。
这篇文章对我的启发在于,将之前自己设计产品时模糊的步骤边界给明确了出来:设计功能、界定概念、保持概念独立且同步 —— 而在此之前,第一步和第二步往往没有那么明确,并且在设计「动作」方面抽象的不够干净,更喜欢从 UI 流程上下手,而不是抽象的功能性上下手。
除此以外,对于「设计」的思考也让人颇受启发:在大多数情况下,设计并不是要从零开始发明一套全新的行为。相反,它需要决定使用哪些标准概念,然后如何将它们组合在一起。新颖性来自于以新的方式将现有的概念组合在一起。概念设计可以帮助你确定是什么让应用程序与众不同,以及公司的真正资产是什么 —— 比如对于 PS 来说,重要的不是那些绘图功能,而是围绕「图层」这个概念进行打磨。
文中补充了一些解释和自己的例子方便大家理解(如有问题也欢迎留言)
概念设计,是一种思考软件、软件设计、构建和使用的新视角。这样做除了能提高软件质量,缩小体验和工程设计之间的差距,还可以将其视为用户体验设计的一种计算方法。
这个概念看似新颖,但其实优秀的设计师一直在做这些事情,这里只不过是提炼整合的想法。
整个概念设计分为三个阶段,每个阶段都采用了与出传统设计不同的视角。
▌第一阶段:设计 = 功能性
第一阶段往往并不激进,将软件设计视为其功能的塑造。
这听起来很明显,但实际上人们并不是这么做的,功能是软件的功能,而不是:
- 软件的实现方式 —— 这正是软件架构的意义;
- 软件的展示方式 —— 这正是用户界面设计的意义;
这是最基本的出发点,因为只有知道代码该实现什么功能,才能考虑代码;只有知道用户界面该支持什么功能,才能开始考虑用户界面。在实践中,功能往往是隐藏的,应用程序的「设计」都是从代码片段和现况开始的。知道我们在「设计什么」,是第一阶段最重要的事情。
举例子,假如我们在设计一款社交媒体应用 WhatUp,可以让用户在群中发布信息。在设计时,如何通过一种方法来描述我们所设想的功能呢?
我们可以先定义一组状态:用户、群组和帖子;帖子由用户创建,并发布到不同的群组;用户可以是多个群组的成员。这里可以将「候选不变式(candidate invariants)」视为一个设计问题,从而更精确地探讨这些状态:一个帖子必须属于一个群组吗?一个帖子必须来自它所在群组的成员么?
补充注释:候选不变式(candidate invariants)是一个设计术语,指在设计一个系统(例如数据库、软件应用等)时需要考虑的一组规则或条件,以确保系统的某些特定属性或状态始终保持不变。这有助于在设计时明确什么是允许的操作,什么是不允许的。例如,如果一个帖子必须只属于一个群组,那么这就是一个不变式。
不变式通常用于帮助开发者和设计者理解和明确系统的边界和限制,以确保系统运行的可靠性和一致性。在数据库领域中,这些通常称为「完整性约束」(integrity constraints)。
然后,我们可以列出用户可以执行的操作:创建、删除、加入或离开群组;向群组内添加帖子;回复帖子等。
这种方法的妙处在于,只需要写下「状态」和「不变式」,就能描述出一整套(无限的)可能行为。而一个优秀的设计者可以通过一个看似简单的「不变式」看出它对行为(甚至软件架构)的影响,因此状态设计本身就成为了一个强大的设计工具。
假设我们规定一个群组中的帖子必须由同一群组的成员发布。那么如果用户离开了某个群组,会发生什么情况呢?据推测,他们的帖子将被删除,但如果帖子有回复,那么将会产生什么后果呢?
状态也是衡量复杂性的一个标准:有时你在考虑某些行为时,会意识到它需要一个更丰富的状态。例如,我们可能想确保用户只能看到在他们加入后群组中的新帖子。如果是这样,状态不仅要包括帖子的日期,还要包括用户的加入日期。
用状态和操作来描述一个应用程序,是一项相当有挑战性工作。对整个应用程序进行描述似乎仍是一项艰巨的任务。有什么办法可以分解任务,使其变得更简单吗?
▌第二阶段:概念
第二阶段是最基础的阶段,在这一阶段,我们为应用程序的功能引入了一些模块化结构。
当我们介绍 WhatsUp 时,会考虑将其描述为一款「让用户在群组中发布信息」的应用程序。你可能会问:这还不够吗?你明白我的意思,因为你已经熟悉了我所说的概念。例如,「群组」的概念在许多社交媒体应用中都存在,可以追溯到最早的聊天应用(如 IRC)。我本可以叫它「聊天室」。
那么「群组」究竟是一个什么概念?对于哲学家来说,概念是一种类型或者分类,比如「狗」的概念对应于所有属于狗的事物集合。将这种思路应用到软件设计中,就会产生概念网络和本体论。但这还不是最重要的,因为对于软件设计来说,概念是关于行为的:群组是你加入、离开和发布的东西。
在本中的方法论中,「概念」是一种纳米服务(nanoservice),有自己的 API,由其操作定义。和微服务(Microservice)特征一样,只不过更小,更集中而已。
另一方面,概念本身也是一种「协议」,它为用户描述了游戏的规则,比如:你必须先加入群组才能在群组中发帖 —— 其实软件中所有概念(例如,用户登录、密码认证、数据分享等)都是人为设计出来的规则或协议,用于指导或约束用户如何与软件进行交互。比如「预约」这个概念发明于 19 世纪,而我们今天在网络上订餐也依旧沿用了这个概念。
所以软件不仅仅是代码或界面,而是包含了一整套人为设计的规则和协议,这些规则和协议定义了用户如何与软件进行互动。
如何定义概念的行为?就像我们为整个应用程序定义状态和行为一样。但不同的是,现在我们有了一些结构 —— 每个概念都有自己的本地状态和行为:
- 帖子的分组是「群组」概念状态的一部分,而帖子的内容可以是另一个概念「发布」的一部分。
- 加入和离开是群组行为;编辑帖子是发布的行为。
不同的设计人员(或不同的团队)可以设计不同的概念,从而实现功能的模块化设计。
这就是「概念设计」与当前设计观点(如「设计思维」)的不同之处。我的观点是,如果你观察一下大多数应用程序,就会发现它们都是由许多其他应用程序中出现的传统概念组成的 —— 这就是我们为何能够轻松地从一个社交媒体应用切换到另一个应用的原因(补充:也是许多特立独行的创新产品最终无法被大多数人接受的原因)
这就意味着,在大多数情况下,设计并不是要从零开始发明一套全新的行为。相反,它需要决定使用哪些标准概念,然后如何将它们组合在一起。新颖性来自于以新的方式将现有的概念组合在一起,当然有时也来自于引入全新的概念。
应用程序就像分子一样,具有不同的特性,但由相同的原子构成。因此,我们可以将 WhatsApp 视为分子 PoGrUsRe(发布 + 群组 + 用户 + 反应);将 Facebook 视为 PoFrUsUpRe(发布 + 好友 + 用户 + 最高票数 + 反应);将 Twitter 视为 PoFoUsUpHa(发布 + 追随者 + 用户 + 最高票数 + 标签);等等。
这将如何改变设计流程?这意味着,你的第一步是要弄清楚现有的一些概念是否能完成任务。你可能想要调整它们,甚至添加一些新概念。你不需要详细说明大多数概念的状态和动作,因为你已经知道了。
你可能会问,这是否会降低设计的创造性?我认为这会让设计更有创意,因为你花在重复发明轮子上的时间更少,而把更多的时间放在真正新颖的方面。
当你以创造性的方式将概念组合在一起时,神奇的事情就会发生。比如 Arc 浏览器以一种新的方式将标签(Tab)和书签(Bookmark)概念结合在一起,使标签更易于管理。
概念设计可以帮助你确定是什么让应用程序与众不同,以及公司的真正资产是什么。
- Photoshop 的成功得益于「图层」概念,而不是「图像编辑功能」;
- Tim Berners-Lee 对网络的发明不是基于超文本、标记或 HTTP(这些都不是什么新鲜事物),而是基于 URL 概念;
- Microsoft Word 引入了「样式」,这一概念几乎定义了桌面排版;
- Dan Bricklin 的 VisiCalc 拥有「相对参照」,这一概念让电子表格行之有效。
▌第三阶段:独立
当概念的设计(甚至实施)可以完全独立于概念本身时,概念才能充分发挥作用。
虽然应用程序中的概念可以协同工作并相互影响,但用户对它们的理解是独立的。HackerNews 将 「帖子」和「投票」结合在一起,以便对帖子进行投票,而「投票」概念的状态(在运行时)将包含对「帖子」的引用,而「帖子」是在「发布」概念中创建的。但在用户心中,「投票」概念与《纽约时报》用于评论排名的概念相同。
因此,我们需要在不依赖任何其他概念的情况下描述像「投票」这样的概念。这意味着首先它的类型不能引用其他概念的类型,比如「投票」不应该依赖于「帖子」「留言」等概念。它必须是多态的,因此被投票的项目可以有任何类型,无论是帖子、报纸文章、评论,甚至是用户。
补充例子:为何 flomo 现在的方案是只有全局一条置顶,而不支持在标签下置顶 MEMO,这也牵涉到「概念」的独立性:
- 标签下的置顶 MEMO,是否同步置顶到全局;
- 标签下的置顶 MEMO,是否同步置顶到子标签;
- 在标签下取消置顶 MEMO,是取消了该标签下的置顶,还是全局置顶,那么子标签呢?;
- ……
你看,如果这样设计,就会让「置顶」这个概念复杂化,需要让用户区分是「全局」还是「标签」下置顶。
其次,独立性要求一个概念的行为不能调用另一个概念的行为。考虑一下 Facebook 将「投票」和 「反应」整合在一起的做法(颇具争议)。我们想说,当用户对一个帖子做出反应时,它也会得到投票。这样,Reaction.react(u, r, p)(表示用户 u 在帖子 p 中添加了反应 r)就被建模为调用 Upvote.upvote(u, p) 的代码。
但是,如果在「反应」概念中嵌入对「投票」概念的操作的调用,就能将这两个概念结合在一起。那该怎么办呢?标准的编程技巧是使用回调(或对象),但这很复杂,会把我们从简单的状态和操作带入高阶函数的世界。
受托尼-霍尔(Tony Hoare)的 CSP 的启发,概念设计采用了一种简单得多的方法。我们没有修改 「反应」概念本身,而是将其与「投票」概念同步,即每当一个动作发生时,另一个动作也应发生:
sync Reaction.react(u, r, p)
Upvote.upvote (u, p)
同步就像事务(transaction):要么所有操作都发生,要么都不发生。因此,这种同步可能会强制要求用户只有在声望(Karma)这个概念达到 20 点时,才能向群组发帖。
sync Group.post (u, p)
Karma.permit (u, 20)
在许多应用程序中,尤其是那些由标准概念组成的应用程序中,设计的大部分挑战在于如何将这些概念整合在一起。概念设计将这一因素考虑到单独的同步中,让您可以独立地关注这一方面。
许多可用性问题都可以追溯到有问题的同步。例如,在 Google 的日历应用中,「事件」和「邀请 」是同步的,因此删除邀请会向邀请者发送一条拒绝信息,这可能是不受欢迎的。
有时,即使是巧妙的同步也无法让概念组合发挥作用。Gmail 的「标签」和「会话」组合方式很巧妙,但也很麻烦:如果你知道为什么搜索带有「已发送」标签的邮件会包括「已收到」的邮件,你就会明白这一点。
最后,在某些情况下,同步功能非常强大,可以实现各种新的工作流程。例如,Photoshop 可以同步通道、选区和图像,这种方式只有专家才能完全理解,但却带来了惊人的机遇(如果你曾经从图像中提取边缘并对其进行锐化处理,你就会明白我的意思)。
▌那么,你能用这些做什么呢?
作为个人设计师,概念设计镜头可以改变你的工作方式:
- 应用程序 = 概念。将应用程序视为概念的组合会带来新的视角:现在你可以解释文字处理器与文本编辑器(它有段落、样式和格式概念,但通常没有强大的线条概念)和桌面排版应用程序(它有文本流概念和真正的页面概念)的不同之处。
- 设计 = 重复使用(90% 的时间)。现在,设计应用程序主要是以创造性的方式结合现有概念。(补充例子,比如对于 flomo 来说,重要的不是设计编辑器,而是将简化的编辑器、多级标签进行结合,并将重点放在「回顾」上面,形成自己的独特价值)
- 行为 = 状态 + 动作。将行为用 UI 界面表现出来虽然看似有启发,但作用有限。如果将行为视为状态和动作,并借助概念将其模块化,就能走得更远,同时还能避免代码的复杂性。
概念可以帮助你更深入地思考设计:
- 协同作用。最好的概念组合会带来协同效应。以 Mac 操作系统中的垃圾桶为例,它实际上是一个特殊的文件夹,只不过结合了「文件夹」和「垃圾桶」这两个概念。好处在于能给用户提供一个临时存放即将被删除文件的地方,从而增加了操作的可逆性。然而,这种复杂性也可能导致一些不直观或者意外的情况。如果用户没有完全理解这两个合成概念的工作方式,他们可能会在清空垃圾桶后意外地删除重要文件。
- 设计动作。你可以根据已知的设计步骤来思考设计的步骤,并在物理设计中进行类比。例如,通过加强两个概念的同步来产生协同效应。比如 Zoom 会议中,大多数人都会闭麦,然后举手发言通过后,会开麦说话,说完后会闭麦。但此时举手状态还在,往往需要主持人提醒。所以更好的设计是举手后再次闭麦,同时就取消举手。
- 分析概念「过载」。每个概念都应该只有一个目的。通过分析那些「过载」了多个目的的概念,你可以诊断出细微的缺陷。(补充例子:比如为何 flomo 不单独做收藏、Anki 功能,就是因为需要增加收藏/取消收藏,以及是否记忆/忘记等诸多属性,会让 MEMO 的概念变得模糊。再比如为何不支持标签下置顶,只支持全局置顶 MEMO,也是如此)
- 彻底独立。我们习惯于让我们的概念相互依赖。但如果让它们真正独立呢?如果将名称与身份分离,文件系统会发生怎样的变化?首先,Git 会知道你何时重命名了一个文件。
在组织层面,概念设计可以产生更大的影响:
- 创建资产目录。您的产品概念是您的关键资产、差异化优势(也是负债)。
- 跨产品统一。许多公司都有一个产品系列,但随着时间的推移,这些产品会逐渐疏远。统一概念有助于用户获得更加统一的体验,并节省开发工作。
- 共同语言。概念是沟通工程师和设计师的桥梁,也是将整个组织(市场营销、战略、知识产权)团结在一起,明确了解产品的途径。
推荐阅读:
