好交互不是好看:软件工程师应理解的设计底层逻辑
引言
作为一名软件工程师,我一直不太知道如何做好交互设计。
我当然知道一些具体的经验,比如界面要简洁,按钮要清楚,状态要有反馈。但这些经验总让我觉得不够完整。它们更像是一些零散的技巧,而不是一套可以指导我判断和取舍的方法。
我始终认为,交互设计应该是一门严肃的学科。它不是单纯依赖审美,也不是靠灵感把页面摆得更好看。它背后一定遵循某些稳定的原则、方法和认知规律。通过这些方法的指导,我们才有可能做出像 Figma、VS Code、Stripe Dashboard、GitHub、Linear、Apple Shortcuts、Zapier 这样交互优秀的软件:复杂但不混乱,强大但不压迫用户,能力很多但用户仍然知道下一步该做什么。
这个问题困扰了我很久。直到有一天,我和聪明的 Codex 进行了一次比较深入的对话,才逐渐意识到:优秀交互的底层逻辑,并不是“把界面做漂亮”,而是让用户操控一个可理解的对象系统。
也就是说,一个好的软件界面,应该能持续回答这几个问题:用户现在想做什么?他正在操作什么对象?这个对象处于什么状态?当前最合理的动作是什么?动作之后系统给了什么反馈?反馈之后用户应该进入哪一步?
这篇文章,就是我对这次对话的整理。它不是一篇设计教程,更像是一个软件工程师试图理解交互设计这门学科时,梳理出来的一套基本思考框架。
为什么工程师容易把界面做复杂
工程师做产品时,很容易从系统能力出发:
-
我们支持创建项目。 -
我们支持编辑节点。 -
我们支持自动保存。 -
我们支持校验。 -
我们支持加载预设。 -
我们支持右键菜单。
这些能力本身都没错。问题在于,如果它们被同时摊在同一个界面上,用户看到的不是能力,而是噪音。
用户第一次打开软件时,脑子里并没有系统架构图。他只是在问几个非常朴素的问题:
-
我现在在哪? -
我正在操作什么东西? -
这个东西现在是什么状态? -
我下一步应该做什么? -
我做完以后,系统有没有理解我?
如果界面不能回答这些问题,再漂亮的卡片、按钮和图标都只是装饰。
这就是很多“功能很强但不好用”的软件的根源:它们把系统内部结构暴露给用户,却没有把用户的行动路径设计出来。
优秀软件的共同点:不是少,而是有秩序
Google Search、Figma、VS Code、Stripe Dashboard、GitHub、Linear、Apple Shortcuts、Zapier 这些产品形态完全不同,但它们有一个共同点:
它们不是把功能直接堆给用户,而是先让用户理解一个可操作的对象系统。
Google Search 的对象是“搜索意图”,所以首页几乎只保留一个输入框。
Figma 的对象是“画布上的设计元素”,所以默认让画布成为主角;只有选中对象后,右侧属性才变得重要。
VS Code 的对象是“工作区、文件、编辑器、终端、问题列表”,复杂能力被组织进侧栏、命令面板和状态栏。
Stripe Dashboard 的对象是“客户、付款、订阅、发票、事件日志”,每个对象都有清晰状态和下一步操作。
GitHub 的对象是“仓库、Issue、PR、Commit、Branch”,几乎所有操作都围绕对象状态展开。
Zapier 和 Apple Shortcuts 的对象是“一条自动化流程”,所以它们用一步一步的构建方式控制复杂度,而不是一上来展示完整系统能力。
所以优秀交互不是“界面元素少”,而是用户始终知道自己在操控什么、它处于什么状态、下一步能做什么。
这背后的学科基础
优秀交互设计不是审美玄学,它背后至少有五个重要学科。
1HCI:人机交互
HCI 研究的是人如何向系统表达意图,以及系统如何把状态反馈给人。
Don Norman 在《The Design of Everyday Things》中提出过一个非常重要的思路:用户使用系统时,会经历从目标到动作,再从系统反馈到理解结果的循环。
这个循环里有两个常见断点:
-
执行鸿沟:用户知道自己想做什么,但不知道该怎么操作。 -
评估鸿沟:用户做了操作,但不知道系统发生了什么。
很多糟糕界面的问题,正是这两个鸿沟太宽。
例如一个画布工具里,用户想“创建第一步”,但界面同时显示创建、载入、保存、校验、配置、项目 ID、节点列表,他就不知道该点哪里。这是执行鸿沟。
用户拖出一条线后,没有明确告诉他能接什么、不能接什么、为什么不能接,这是评估鸿沟。
2认知心理学:人不是无限内存机器
工程师习惯同时持有很多上下文:数据结构、调用链、状态机、异常分支。
但普通用户,甚至专业用户,在使用界面时的工作记忆也很有限。界面一次给太多选择,会让用户停止行动。
这不是用户“不聪明”,而是人类认知系统的正常限制。
所以好交互的关键不是“把所有能力都展示出来”,而是控制用户当下需要处理的信息量。
这就是渐进披露的价值:先让用户完成当前一步,再在新状态下展示下一步。
3信息架构:先组织对象,再组织页面
很多界面混乱,是因为设计时先想页面,而不是先想对象。
优秀软件通常有非常清楚的对象模型:
-
GitHub:Repository、Issue、Pull Request、Commit、Branch。 -
Stripe:Customer、Payment、Subscription、Invoice、Event。 -
Figma:File、Page、Frame、Layer、Component。 -
Linear:Workspace、Project、Issue、Cycle、Status。
对象清楚以后,页面才会清楚。因为页面只是对象、状态和动作的组织方式。
如果对象不清楚,界面就会变成功能按钮的集合。
4控制论:界面是系统的控制面板
软件不是静态海报,而是一个可以被用户控制的系统。
一个好的控制面板必须表达四件事:
-
当前状态是什么? -
用户能控制什么? -
控制后系统发生了什么? -
系统进入了什么新状态?
如果用户创建了节点,界面要显示“已选中这个节点”。如果用户连接失败,界面要显示“连接被阻止,因为输出和输入类型不匹配”。如果系统正在保存,界面要显示“自动保存中”。如果保存完成,界面要显示“已自动保存”。
反馈不是锦上添花,反馈是功能的一部分。
5视觉感知:结构比装饰重要
视觉设计不是把界面做漂亮,而是帮助用户理解结构。
距离、对齐、分组、对比、层级,都会影响用户如何理解信息。
如果所有东西都有边框,用户就不知道哪个最重要。
如果所有按钮都是高亮按钮,用户就不知道哪个是主动作。
如果空状态、编辑器、校验结果、保存状态同时抢注意力,用户就不知道当前任务是什么。
好视觉不是“加设计感”,而是让交互结构变得可见。
一个更底层的公式
可以把优秀软件的交互逻辑压缩成一个公式:
意图 -> 对象 -> 状态 -> 动作 -> 反馈 -> 下一步
这不是一个文案公式,而是一个产品结构公式。
用户先有意图。例如:我想搭建一条内容生产管线。
系统需要让用户找到对象。例如:当前编辑的是某个项目里的某张画布。
对象必须有状态。例如:空画布、已有节点、选中节点、连接中、校验失败、已保存。
每个状态只应该突出当前最合理的动作。例如:空画布时只需要创建第一个原始节点;选中节点后才需要显示可连接的下一步。
动作之后必须有反馈。例如:节点已创建、连接已建立、连接被阻止、自动保存完成。
反馈之后进入下一状态。例如:从空画布进入“已有第一个节点”的状态。
如果这个链路断了,用户就会迷路。
工程师可以怎么落地
对软件工程师来说,最有用的不是“审美建议”,而是一套可以进入开发流程的方法。
第一步:先写对象模型
不要先画页面。先回答:
-
用户在操作什么对象? -
对象之间是什么关系? -
哪些对象是持久化的? -
哪些对象只是临时状态?
例如管线工具里,至少有这些对象:
-
Project:项目,负责存储作用域。 -
Canvas:画布,负责承载一张图。 -
Node:节点,代表一个处理步骤。 -
Port:端口,代表输入输出类型。 -
Edge:连接,代表数据流向。 -
Validation:校验结果,代表系统判断。
对象模型清楚以后,页面不容易乱。
第二步:列出对象状态
每个核心对象都应该有状态。
以画布为例:
-
empty:空画布。 -
editing:已有节点,正在编辑。 -
node-selected:选中了某个节点。 -
connecting:正在连接。 -
blocked:操作被阻止。 -
dirty:有未保存更改。 -
saving:保存中。 -
saved:已保存。 -
invalid:校验失败。
很多 UI 混乱,是因为没有状态模型。设计时只是在想“这里放什么组件”,而不是“当前是什么状态”。
第三步:每个状态只保留一个主动作
这是最重要的一条规则。
空画布状态下,主动作就是:创建第一个原始节点。
不要同时突出保存、校验、节点配置、项目 ID、历史记录、全部节点类型。
已有节点但未选中时,主动作可以是:选择节点或继续创建。
选中节点后,主动作可以是:从当前节点继续。
出现校验错误时,主动作应该变成:修复错误。
一个状态只有一个主动作,用户才知道下一步。
第四步:把复杂能力延后出现
不是所有信息都必须隐藏,但所有信息都应该有出现时机。
空画布时,不需要显示节点配置面板。
没有错误时,不需要展开校验详情。
没有选中对象时,不需要显示属性编辑器。
没有保存过时,不需要显示复杂的版本历史。
这不是阉割功能,而是控制复杂度的进入节奏。
第五步:把反馈当成协议设计
工程师设计 API 时会认真定义返回值,却常常忽视 UI 反馈。
但对用户来说,反馈就是系统协议。
建议每个重要动作都定义:
-
pending:系统正在处理什么? -
success:成功后如何表达? -
warning:有哪些非阻塞风险? -
error:失败原因是什么? -
blocked:哪些动作在提交前就应该被阻止? -
recovery:用户下一步怎么恢复?
例如连接两个节点时:
-
拖动输出端口:预览可连接目标。 -
悬停不兼容输入:显示阻止状态。 -
松手连接失败:不创建边,并说明原因。 -
连接成功:创建边,标记 dirty,触发自动保存。
这才是完整交互。
用这套逻辑重看几个优秀产品
Figma:选择之后才有属性
Figma 的强大不在于右侧属性面板有多少功能,而在于它遵守了一个基本秩序:
先有画布对象,再有选择状态,再有属性编辑。
没有选中对象时,很多属性没有意义。选中 Frame、Text、Component 后,右侧面板才进入对应上下文。
这就是对象状态驱动界面。
VS Code:复杂能力不挤在一个页面里
VS Code 的功能非常多,但它没有把所有能力塞进编辑区。
文件树在侧边栏,命令在命令面板,问题在 Problems,终端在底部,状态在状态栏。
它的核心原则是:主工作区保持稳定,复杂能力进入不同的控制区域。
这对专业工具非常重要。
Stripe:业务对象有清晰状态
Stripe Dashboard 处理的是非常复杂的业务系统,但它把复杂性落在对象和状态上:
客户、付款、订阅、发票、事件日志,每个对象都有身份、状态、时间线和可用动作。
用户不需要理解 Stripe 内部系统,只需要理解“这个付款现在成功了吗?失败了吗?为什么?下一步能做什么?”
Zapier:流程不是一次性铺开
Zapier 不是让用户一上来面对完整流程图,而是引导用户一步步添加 Trigger 和 Action。
这对自动化产品特别关键。因为流程本身很复杂,必须通过构建顺序降低理解成本。
用户每完成一步,系统进入一个新状态,然后展示下一步。
对工程团队最有价值的检查表
如果一个页面看起来乱,不要先问“怎么美化”,先问这些问题:
-
这个页面的核心对象是什么? -
用户第一次进入时,最可能的意图是什么? -
当前对象有哪些状态? -
每个状态下唯一的主动作是什么? -
哪些信息可以延后到状态变化后再出现? -
用户执行动作前,系统有没有预览可行性? -
用户执行动作后,系统有没有明确反馈? -
错误发生时,用户知道怎么恢复吗? -
页面是在展示系统能力,还是在支持用户完成任务?
这套问题比“按钮放左边还是右边”更重要。
最后:好交互是工程能力的一部分
很多工程师会把交互设计看成设计师的事情。但实际开发中,交互质量很大程度取决于工程团队如何理解系统。
因为交互设计并不只是颜色、间距和动效。
它要求你定义对象模型、状态机、反馈协议、错误恢复、信息层级和复杂度进入节奏。
这些恰恰都是工程师最擅长的事情。
所以,一款交互优秀的软件,底层逻辑并不是“更漂亮”,而是:
让用户操控一个可理解的对象系统,在每个状态下只面对当前最重要的动作,并在每次动作之后获得清晰反馈。
当我们用这个标准去设计软件时,界面自然会变得简洁。不是因为功能少了,而是因为复杂性被放回了正确的位置。
参考阅读
-
Nielsen Norman Group:10 Usability Heuristics for User Interface Design -
W3C:Web Content Accessibility Guidelines 2.2 -
W3C:WAI-ARIA Authoring Practices Guide -
Material Design:Interaction states -
Don Norman:《The Design of Everyday Things》
夜雨聆风