2. 引导用户使用你的产品
机器及其设计者有义务理解人。理解机器那些武断且毫无意义的指令,并不是我们的义务。
— Don Norman,见 The Design of Everyday Things
你的产品用户就是你的英雄。就像在小说里一样,你希望这位英雄走到终点并达成目标。
与小说作者不同,产品设计师的目标是让用户旅程尽可能轻松。在场景的指导下,你会在恰当的位置与恰当的时机提供路标,帮助用户沿路前进。
在经典著作 The Design of Everyday Things 中,Don Norman 将这种路标称为 signifiers(意符)。意符是界面中提示某个功能作用的线索。意符的例子不胜枚举:
- 用名称或图标来指示用途
- 按钮外的边框,提示你可以点击
- 向下的尖角符号,提示这是下拉菜单
- 单选按钮,表示最多只能选择一个选项
- 一组复选框,表示可以同时选择多个选项
- 带下划线的蓝色超链接,表明目标是网页,链接文字则说明该页面的相关性或名称
- 错误消息,提示用户接下来该做什么
等等。
本章将探讨意符这一概念及其在用户旅程中的作用。通过它们,你将学会如何打磨出更精致的界面。
首先,我会介绍一个关于分层菜单的案例研究,里面有很多可以深入讨论意符的机会。
这会是我们观察本章建议的两个视角之一。第二个视角会以如下侧栏形式出现:
编码实践
在这些模块里,你会看到如何把产品思维应用到日常编码中的建议。毕竟,函数、类等构造本身就是微型产品,而它们的用户正是你的队友和协作者。好的命名和注释会让代码评审与维护高效得多,同时也能帮助你练习产品思维能力。
通过这两个视角,我将审视用户旅程的三个主要阶段:发现、理解与使用。
过程中,我还会展开一些主题,例如本体(ontology)、层级化设计,以及一致性与特异性之间的权衡。
最后,我会拉远视角,整体审视用户旅程,讨论当不同阶段发生冲突时该如何权衡,以及何时需要从更宏观的层面思考设计。
案例研究导入
在 2000 年代中期,微软在 Office 套件(Word、Excel、PowerPoint)上遇到巨大问题:系统过于复杂,用户难以理解并定位自己需要的功能。到 2003 年,Microsoft Word 除了下拉菜单外,还有 31 个工具栏和 19 个任务窗格。根据产品经理 Jensen Harris 的相关演讲,图 2-1 展示了典型会话截图。
通过对数百名用户的访谈,团队发现用户很难发现功能,也难以建立对软件的掌控感。许多呼声很高的功能虽然被做出来了,但用户找不到它们。
到了 Office 2007,微软将“Ribbon(功能区)”菜单系统引入其 Office 桌面应用,如 图 2-2 所示;同时还有在选中文本时弹出的上下文菜单,如 图 2-3 所示。

图 2-1. 2003 版 Microsoft Word 的凌乱截图

图 2-2. 2025 年 Windows 版 Microsoft Word 的功能区

图 2-3. 2025 年 Windows 版 Microsoft Word 的上下文弹窗
在设计这次改版时,团队收集了数千项功能的使用频率,并将它们重组为新的层级化菜单设计,同时引入了新的功能呈现方式。这些变化让高度复杂的应用对许多用户更容易上手,并且迄今经受住了时间考验。理解其“怎么做、为什么做”,能为我们在自己的应用中引导用户旅程提供线索。
用户旅程中的场景
用户旅程通常会经过三类主要场景:发现(Discovery)、理解(Understanding)和使用(Usage)。
- 发现:用户有一个想解决的问题,但还不知道怎么解决。
- 理解:用户遇到一个功能,想弄清它做什么、怎么工作。
- 使用:用户希望按预期目的使用该功能,同时避免不安全使用。
如果你负责交付某项功能,就必须对这三个方面都负责。让功能可见、帮助用户理解、引导用户安全使用,都是你团队的责任。漏掉其中任何一个,你的功能都无法产生应有影响。
如 第 1 章 所述,你应该能讲出一个可信的故事,让用户贯穿三个阶段且没有情节漏洞。确保覆盖这三类主要场景,是消除漏洞的好方法。并且在针对每个场景时,还要继续补全更多覆盖细节的故事。
发现场景
读者将如何发现你的抽象?工程师常忽略用户旅程这一段,但它往往最关键:不知道产品存在的人,根本无法使用它。这就是线上广告会形成巨大产业的原因之一:它解决的是发现问题。
考虑发现时,要记住用户可能从不同方向进入。以 Microsoft Word 的上标功能为例,用户可能通过阅读功能区按钮、使用搜索栏、查阅在线文档、查看快捷键列表,或与聊天机器人交互来得知它。当你认为用户会需要你的功能时,他们所有探索路径都应能导向它,所以请尽可能多做心智模拟:用户会怎么接近它?哪里有情节漏洞或不合理的逻辑跳跃?
这一节里,我会先讨论用户带来的既有知识,再探讨帮助他们找到功能的工具,最后说明如何把功能做成层级结构,避免一上来就压垮用户,或者把东西藏得找不到。为引导你理解,我会介绍一种实用设计方法:产品发现地图(Product Discovery Mapping),它也能把这些讨论可视化。
产品发现地图
产品发现地图(Product Discovery Map, PDM)可以帮助你绘制用户学习产品时会走过的路径。假设你在复查产品实现,想确认用户能找到在 Word 中制作简报所需的关键排版功能。这类用户会想做多栏布局、使用横向页面、在正文中嵌入图片,而且很可能会把这些技巧组合使用。
我们聚焦“连续分节符(Continuous Section Break)”的发现过程,这个概念你很确定用户一开始并不理解。图 2-4 展示了 Microsoft Word 的“布局”菜单,并展开了“分隔符”下拉菜单。

图 2-4. Microsoft Word 的布局菜单
下面是 Yves 的一个用户场景。Yves 所代表的是一类轻度使用者人物画像:偶尔用 Office 做社区活动宣传的人。
Yves 正在为本地教会做月度简报。他想做成双栏排版,同时让简报标题居中显示在顶部。他先输入标题并在“开始”选项卡中把它居中。然后呢?他看到了“布局”选项卡,并注意到一个“分栏”控件。他点击并选择两栏,但标题跑到了左栏,这不是他想要的。他撤销后看到了醒目的“分隔符”按钮,并根据下拉菜单中的说明,学会了如何使用连续分节符。
当你要交付很多功能,或要测试大型产品可用性时,这类场景很快会变得冗长。产品发现地图是一种简写表达,包含三个关键要素:
- 每张图都针对一个特定客户画像,表示其如何在产品中“走迷宫”。
- 每个节点代表产品中的一个元素。
- 每条边表示该画像如何发现这条知识路径,如 图 2-5 所示的 Yves 旅程。

图 2-5. 连续分节符的 PDM
这张图假设 Yves 知道什么是“分栏”,但不知道什么是“分节符”;同时它展示了他成功走到正确功能的一条路径。
你可以把多条流程放进同一张图里,更快速、更直观地构思产品的某个部分。
构建和评审地图时最重要的是思考与协作过程。命名是否足够有帮助?你希望用户完成的认知跳跃是否合理?到达某功能所需步数,是否和它的使用频率匹配?常一起使用的功能是否也被放在一起?
利用用户已有知识
在 第 1 章 中,我说过,人物画像有其“手段”——他们带来了哪些技能与知识?
你的产品呈现的一组名称与概念,以及这些概念之间的关系,称为本体(ontology)。本体本质上是一张图。用户对产品本体越熟悉,理解越深入,使用也越有效。
让东西更容易被发现的最简单方式,就是使用用户本体里已经有的名称和概念。也就是,不要重复造轮子。
在“布局”菜单这个例子里,Yves 知道“分栏(Column)”,但面对“连续分节符(Continuous Section Break)”时,他只带着“分节(section)”这个概念进入。观察 图 2-4 就能看到,微软在“分隔符”上的设计要比“分栏”谨慎得多:他们必须提供更详细的说明和图示,来弥补命名上的陌生感。
编码实践
要警惕晦涩的项目代号。它们不在用户本体里。比如别人要在公司代码库或文档里找一个分布式缓存框架,可能会搜“cache”,但他们不会提前知道你的项目叫“Phoenix”(因为它从上一个分布式缓存的灰烬中“重生”)。如果你确实想做品牌化命名,可以考虑像“SuperCache”这种方案:既有一点品牌感,也保留一点可发现性。
晦涩代号在某些场景也有价值,例如保密需求,或对象足够独特、很难直白描述,导致直接命名反而误导。但要清楚你牺牲了多少可发现性,并确保留下其他面包屑(例如注释)让人能找到它。
我在 Stripe 工作时,我父亲(一位会计,几乎不会编程)读了 Stripe 在线支付和账单 API 的文档。他抱怨我,因为他一直在找“应收款(receivables)”之类严谨的会计术语。
我告诉他,这其实是有意为之。这些 API 面向的是通用型程序员,努力使用那些即便没上过会计课的人也能接受的术语。
但这也说明:如果 Stripe 希望会计人群也能顺畅使用,仍有不少工作要做。
提供多条发现路径
当然,语言里有同义词和相关概念,但你的本体里通常只能选一个主叫法。你需要给那些带着不同猜测进入的用户提供通向它的路径。
举个例子,功能区提供了好用的命令搜索框,并支持一定的模糊匹配。如果我在框里输入“Margin”,它会返回含有“Indent”的命令,如 图 2-6 所示。我甚至可以直接在该菜单中内联操作这些命令。

图 2-6. 在 Word 中搜索 margin
编码实践
通过注释为你的抽象增加更多可被找到的入口。比如你的基类叫“Plugin”,可以在注释里用“Extension”这样的同义词来描述它。
这个原则可以推广为:
Tip
给用户提供多种发现某个功能的方式,以适配他们不同的“手段”。
以 Word 这类复杂应用为例。大改版之前,发现体验一团糟。用户得在海量菜单、工具栏、任务窗格中判断哪一个适合当前任务。若他们需要工具栏,只有图标给提示;若看菜单,则要在长长的词语列表里寻找,而且排序几乎没有规律可循。功能分类看起来也很随意:为什么某些功能在 Tools 菜单而不是 Format 菜单?为什么 Find 在 Edit 菜单里,即便它本质是读取操作?
一个平庸的 Help 组件是应对这团乱麻的主要替代方案。
- 命令被分组到更少的选项卡中,并分配了更多屏幕空间,让 Yves 更容易在“布局”选项卡里扫到与其用例相关的内容。
- 作为常用且推荐命令,“分隔符(Break)”被放大并放在相关命令附近。
- “Break”和“Continuous Section Break”同时用图标与文本表达。不同用户可能更容易通过其中一种形式找到它。
- 提供搜索框;如果 Yves 知道“section”这个词却不知道在哪个菜单,这就很有帮助。它修复了传统菜单的一个根本问题。
图 2-7 展示了 Yves 可能发现连续分节符的多种有效路径。

图 2-7. 发现连续分节符的多条路径
- 搜索
- 询问聊天机器人
- 视觉扫描你的应用,或边点边看,寻找名称和其他意符
- 使用屏幕阅读器(面向视障人群)或收听语音菜单
- 查阅操作指南或其他文档
尽量不要只依赖文档。在消费级应用里,你很可能根本不会依赖它。
优雅揭示复杂性
几乎所有成功应用都会不断增加功能。即便是以简洁搜索框著称的 Google,如今也有许多配套搜索结果的选项卡和工具。
为了驾驭这种功能扩张,设计师会向用户优雅地揭示复杂性。
看看功能区。即使已经改进,它仍有太多命令和目标画像,难以让菜单始终易用,因此微软引入了“上下文菜单”的概念。它只在合适时机出现。图 2-8 展示了“形状格式”选项卡:除非你点击了形状,否则它不会挤占界面空间。

图 2-8. 功能区中的上下文菜单
功能区默认有九个选项卡,已经接近令人不堪重负,这在产品发现地图里很容易看出来。通过把“形状格式”节点下移到图的更深层级(见 图 2-9),这种优雅揭示避免了问题恶化。
Word 还通过上下文菜单实现优雅揭示:当你高亮文本时,鼠标旁会弹出包含加粗、斜体等常用命令的菜单。这类菜单会主动“自我宣告”,可发现性很强;同时选项很少,用户也更容易快速扫描到所需工具。

图 2-9. 用于发现“形状格式”选项卡的 PDM
多人物画像设计
应用变复杂的一个主要原因是:随着增长,它会吸引不同人物画像的人出于不同目的来使用。
Microsoft Word 服务的人群非常广,从需要章节导航的小说作者,到需要图表的营销人员,再到需要屏幕阅读器与语音输入的视障用户。
因此,大多数成功应用至少会有两套不同界面。理想情况下,每套界面都应在用户需要时被优雅揭示出来。
大多数应用都有重度用户,通常占受众的 5%-20%,他们活跃且深度投入。这些用户希望拥有更强大的原语来定制体验。原因往往在于,他们有更强激励去使用并深入掌握你的产品。一个靠 Word 工作谋生的重度用户,比一个写高中英语诗歌作业的学生,更可能用宏和文档修订追踪。
- Wikipedia 页面有一个不显眼的小“编辑”按钮,读者可忽略,但对贡献者很有用。
- 网站面向新手给出极其醒目的“创建账号”,而对老用户只放一个小小的“登录”按钮。
- 现代操作系统既有面向大众的图形界面,也有面向 IT 专业人员的命令行外壳。
- TypeScript 这类渐进类型语言中,类型标注对简单脚本可选,但在大型组织里可被要求用于构建更健壮的代码库。
- 无代码或低代码框架对非程序员友好,同时允许程序员编写自定义代码。
- 面向轻度用户提供按钮点击,同时给习惯型重度用户提供键盘快捷键。
作为设计师,一个非常重要且令人解放的认知是:当你意识到产品已到应提供多界面的阶段,就可以开始为各人物画像分别优化。这会打开更大的设计空间,也给你更多解决问题的机会。
把“未知的未知”变成“已知的未知”
到目前为止,我大多假设用户知道你的应用具备某些功能,只是找到它需要多长时间的问题。但如果他们压根不知道这个功能存在呢?这在新颖或创新功能中很常见,也常见于对领域本体理解尚浅的新用户。某个用于“神奇记账”的 Excel 函数,资深会计都熟知,但会计专业本科生可能闻所未闻。
所以,把你的产品想象成一片神秘森林,用户正在其中穿行。请给他们留下一条面包屑轨迹,帮助导航。
举个例子,最近我在多个应用里都看到小型聊天机器人控件弹出来。Microsoft Word 在我的光标旁有一个“Draft with Copilot”上下文菜单。此前我并不知道 Word 有这个能力。我也不完全确定这个聊天机器人能为我做什么,但至少我可以去探索。一个“未知的未知”就这样变成了“已知的未知”。
你还要认真思考菜单里省略了什么。用户可能并不知道缺失命令可以在别处找到。Word 在“布局”选项卡里展示了全部 Break 类型,但在“插入”选项卡只放了“分页符(Page Break)”,如 图 2-10 所示。这样设计的风险是:没看到“布局”选项卡的用户可能误以为分页符是唯一选项。权衡点在于,相比埋在更模糊的“Break”命令下,单独露出的“Page Break”确实更易被发现。

图 2-10. “插入”选项卡中的分页符命令
理解场景
当用户发现了所需功能后,下一步必须更深入地理解它到底做什么。
我先从最常见、也是促进用户吸收关键概念的方式讲起:起好名字。随后我会介绍其他意符,当名称不够,或你想兼顾不同学习风格时,它们会很有用。
选择易懂的名称
名称是你向读者脑海植入一两个关键想法的机会。好名字除了帮助可发现性,也应促进理解。用户不一定想知道“这个功能是什么”,他们更想知道“它如何帮到我?”这两个问题很多时候答案一致,但并不总是。具备产品思维,意味着你要思考自己对读者产生的影响,而不只是描述系统本身。
命名最重要的规则之一也许是:避免“知识诅咒(Curse of Knowledge)”。
“换位到对方的鞋子里”(shoe-shifting)是避免这一诅咒的最佳方式。考虑目标画像已有的本体;更好的做法是,直接找这个人群中的人,问他们会如何解读你的命名。
重新审视经典命名建议
大多数人都听过命名建议——那些适合印在项目符号列表里、或者贴在笔记本上的短句。我想用更以用户为中心的视角,重写其中几条。
避免歧义(而不是“要能自解释”)
如果你只能在便签上写下一条命名箴言贴在显示器边上,那就写“避免歧义”。
在没被知识诅咒“感染”的读者看来,功能命名含糊不清的情况其实非常常见。要防止这一点,就换位到用户头脑中,头脑风暴你的命名可能被如何误解。(或者,再说一遍,直接去问他们。)
当我写“布局”案例并扮演做教会简报的 Yves 时,就被一个歧义惹恼了。我看到“布局”选项卡里有这个 Align 按钮,如 图 2-11 右侧所示。

图 2-11. Word“布局”选项卡的一部分
你猜它是做什么的?
我本以为它能用来让简报标题居中。然而我展开它后(如 图 2-12 所示),所有看起来有用的选项都灰掉了。我试了几种办法让它生效,但都没成功。

图 2-12. 一个令人困惑的下拉菜单
如果 Align 指的并不是通用文本、图片或图表的对齐,而是更具体的东西,那它就应该明确写出来!
编码实践
- 角色与关系:当存在两个职责不同的对象(如 parent/child、sender/recipient)时,澄清“到底是哪一个?”至关重要。
- 变量的类型:这能在每一行使用该变量的代码上说明“它是什么”。
- 度量单位:你不会希望读者把磅当牛顿,或把毫希沃特当希沃特,最后导致火星探测器事故或辐射灼伤。
- 词性:
persist是一个表示“是否写盘”的布尔值,还是一个真正执行写盘的函数?如果是前者,should_persist会更好。
在一致性与特异性之间权衡(而不是“保持一致”)
一致的命名有助于用户:他们在本体里学会一个概念后,就能反复复用。给新功能命名时,务必先在现有产品里找“前例”来对齐。
但一致性经常要和特异性做权衡,而特异性往往是减少歧义的最佳手段。在前面的例子里,“Align”术语非常一致,却不够具体。
更具体有时也更符合习惯表达。比如你在音乐流媒体应用中设计一个“随播放显示歌词”的功能,即使产品里一贯用“Track”表示音频文件,你可能仍更愿意写“Song Lyrics”,而不是生硬的“Track Lyrics”。
另一个常见困境是:某个相关概念在很久以前命名得很差,你现在要决定是沿用它保持一致,还是回到第一性原理选一个正确的新名字。
对一些工程师而言,一致性可能变成教条,因为他们无需考虑用户视角也能围绕一致性展开推理。你本地的技术负责人可能并未聚焦你的功能,没有思考当前场景或共情用户,但始终能对“一致性”发表意见。
对抗这种偏差的一种方法是:同时头脑风暴一个“最易懂、最具体”的命名,以及一个“与现有产品最一致”的命名。如果最终得到两个不同答案,再讨论权衡。这样能确保你既在共情用户,也在用现有本体检验命名。衡量时请继续换位,贯穿思考可发现性、可理解性与可用性。下面是一些启发式规则:
- 没有充分理由时,优先保持一致。
- 记住你的知识诅咒。作为系统设计者,你通常比典型用户掌握更全面的信息。只使用产品子集的新用户,可能根本看不到足够内容,自然也不太在意一致性。
- 问清不同受众规模:也许旧命名只被少数重度用户使用,或者只是遗留功能;而你这个新产品有更广泛目标。为了做对未来,牺牲一点一致性也许值得。
- 在多画像应用中,更重要的是每个画像各自体验到什么。比如 Microsoft Excel 完全可以给同一公式提供两个名称,一个面向会计,一个面向软件工程师。又如你的应用同时支持 iOS 和 Android,平台内一致性很重要,但跨平台一致性优先级较低,因为用户并不常在两个平台间切换。
只在目标画像普遍使用时才用缩写(而不是“避免缩写”)
缩写词对理解场景很糟,因为没人会凭空猜出字母含义;它们对发现也不友好。
但你的目标画像可能已经习惯某些术语。你显然不该把“HTML”到处拼成全称。“CPU”更适合软件工程师受众;若面向更大众的人群,试试“processor”这样的词更合适。
如果不确定,就调研目标用户,看看他们如何解读你提出的术语。
只传达用户需要知道的内容(而不是“简洁”)
严格说,concise(简洁)意为“简短但完整”。这听起来有道理。用户学习时会被词藻堆砌搞得吃力,但也不希望你漏掉关键细节。
但“完整”是对什么完整?我会改写这条建议,把重点更多放在读者和“什么能帮到他们”上。
我曾玩过一个线上游戏“PHP Diplomacy”——它改编自一款经典桌游,名字取自该移植版本所用的编程语言。非程序员很可能看不懂这个名字。回头想想,我当时一起玩的好像也全是软件工程师。
通过换位,你能识别哪些只是实现细节。名称可承载的信息位有限,每删掉一个无关信息,就多出一个位置传达真正有意义的内容。
假设你在构建一个长时任务 API:用户会把请求入队,系统从队列取出并执行。用户可以阻塞等待结果,也可以稍后拿句柄去查询。
把异步请求 API 命名为 enqueue_request 看起来很自然。乍看不错,但那“既入队又等待结果”的函数该叫什么?enqueue_and_wait_for_request?当你觉得这个名字别扭时,就该问:读者为了正确使用它,究竟需要知道名字里的哪些信息?
最相关的事实是它是异步的——响应可能延迟。“enqueue”这个词比需要表达的更具体。
这看似细枝末节,但当你去掉“queue”这个实现细节后,就能得到 start_request 与 execute_request 这样简洁又传神的命名,分别对应异步版和同步版。简洁,也更易发现。
编码实践
如果你想把非常短促的函数名和变量名提交进代码库,请记住 Python 之父 Guido van Rossum 的那句话:“Code is read much more often than it’s written.”
函数名被“阅读并理解”的场景——代码评审、调试、学习代码库、搜索修改点——远比它被“敲出来”的场景——加新功能、写测试——要多得多。而输入阶段还有自动补全帮你。
在代码库生命周期里,读者会不断出现。如果你在构建可能长期存在价值的系统,请用清晰命名对它负责。
提供冗余解释
选一个既便于发现、又能帮助无歧义理解的名字非常难,有时甚至不可能完美传达用户所需的一切信息。因此,值得用冗余机制来传达你的意图。
看看 Word 功能区,它有效利用了图标来表达功能。在 图 2-13 中,你可以看到“开始”选项卡里的这些格式命令;它们经过精心设计,在帮助理解方面优于“multilevel list”“justify”这类纯文字标签。

图 2-13. 格式命令图标
下面是一些不只依赖命名、同样能提升理解的方法:
- 图文结合:图像可以把文字试图传达的意思可视化,前面的分节符描述就是例子(图 2-4)。也别忘了,并非所有用户都在使用其母语版本产品。对英语母语者来说,“Column”也许很基础,但对其他用户未必属于常用词汇。
- WYSIWYG(所见即所得):用户甚至不需要先理解,因为操作效果会立刻显示出来。Windows 版 Microsoft Office 除了是 WYSIWYG 编辑器外,还提供“实时预览(Live previews)”,可直接看到切换样式对当前文档的影响。
- 换个说法:在工具提示和文档里使用同义表达或改写。用不同措辞复述同一概念,能帮助用户交叉定位其本意并建立信心。
- 始终提醒上下文:人的短时记忆容量有限,容易忘记自己在哪。比如用户正处于电商购买流程中,若他们中途离开再回来,每个页面都应提醒“当前正在购买什么”。
编码实践
冗余在代码、文档和其他信息密集场景中都很有价值。一旦你从用户场景和“人类工作记忆有限”出发,这一点就非常清楚。
例如,看下面这段代码,你发现什么了吗?
def email_lunch_invitation(sending_user, recipient_user, message):
recipient_organization = recipient_user.get_organization()
title = f"{sending_user.name} from {recipient_organization} " \
"has sent you an invitation to lunch!"
send_email(
sending_user.email_address, recipient_user.email_address, title, message)你在计算 title 时看出 bug 了吗?如果我用的是 organization 而不是 recipient_organization,你觉得你还能这么容易看出来吗?
下面这些场景里,一点额外冗余会很有帮助:
- 读者在做代码评审,抽样检查质量。
- 函数作者可能像上面那样误用变量。
- 程序员通过断点、引用跳转、查找所有引用、调试调用栈或 lint 规则来到这一行,还没读完整个函数。
如果我们需要知道的关于某行代码的信息都写在这一行上,生活就会轻松很多。
使用场景
当读者旅程进入尾声,开始动手操作时,危险就更大了。安全问题无处不在。我们会在 第 8 章 引入“可供性”概念时更系统地讨论用户安全;先在这里尝一点:看看意符如何在缺乏其他保护措施时,仍能引导人做对的事。好的命名能把用户“导上轨道”,让他们凭直觉做出正确操作,甚至意识不到自己本可能偏离。
这里有个极端例子。2010 年代初,某家互联网公司内部有一个特殊配置变量,会被复制到所有提供 Web 流量的机器上。这个变量里放的是一个正则表达式,会在每个对外 HTML 页面发送到用户浏览器前,执行一次查找/替换!
如果要紧急打补丁,比如移除一个有问题的 HTML 标签,工程师就可以把正则写进这个变量,瞬间发布到整个平台。
这听起来危险吗?确实非常危险,而且本意只用于紧急情况。若你用正则改坏了 HTML 导致解析错误,整个站点都可能瘫痪。因此它被命名为 TAKE_DOWN_THE_SITE,名字不是描述“它做什么”,而是强调“误用会发生什么”。
一个更常见的危险信号做法,是把内容塞进“高级”菜单。这既拉长了发现路径、减少会找到它的用户数量,也在暗示使用它可能带来意料之外的影响。Microsoft Office 就是这样做的:用户必须先显式启用,功能区才显示“开发工具”选项卡,把宏和 VB 脚本等工具限制在更可能受益、也更不容易把设置搞乱的人群中。
要判断是否需要显式标注危险,请构造用户“走错路”的模拟,并观察怎样的命名能让这些错误故事变得不再合理。
与“隐藏危险功能”相反的做法,是主动强调概念,比如强制用户做选择。在“布局”菜单选择 Break 时,Word 不会默认最常用的“分页符(Page Break)”,而是强制我先选具体类型。这样既教育了我有哪些选项,也避免我把文档弄乱。
编码实践
在抽象上通过注释留下面包屑。比如有两种实现路径,用户找到其中一种时,也要提示另一种他们可能不知道的方式,尤其当后者对大多数用户更推荐时。
优化整段用户旅程
正如 TAKE_DOWN_THE_SITE 案例所示,有时为了提升可用性,你会牺牲可发现性和可理解性;换言之,在优化发现、理解、使用时,常常存在取舍。
例如,如果“不要发明晦涩缩写”这条建议这么好,为什么 Unix / Linux 里还有 ls、cd 这种流行却晦涩的命令名?它们只是老旧和糟糕吗?
也不完全是。可以说这些名字仍然合理,因为常用命令被“输入”的频次远高于“阅读”的频次。你学一次(或者忘了再学一次)就行,但你不希望每天敲六遍 list_files 而不是 ls。而且它们面向的是习惯型、技术型用户。在这种情况下,使用场景压过了发现与理解,规则自然会被打破。
但如果你是在做市场传播设计,就可能不得不优先发现性和搜索引擎优化。比如气泡水品牌“Liquid Death(液体死亡)”这个名字,谢天谢地只有 50% 准确、也不精确,但确实很会营销。在这条用户旅程中,名字先抓住购物者注意力,他们再看副文案确认它到底是什么。
Tip
为你的功能明确发现、理解、使用三者的相对重要性。
意符的边界
并非每个问题都能靠意符解决,尽管你事先未必知道。你可能一开始想找一个完美名称,但随着你推演用户旅程,才发现根本没有一个名字能传达目标画像需要知道的全部内容。也许它太复杂,或者包含某种安全陷阱。
看看浏览器 Cookie,以及网页上那些提醒你“页面可能在记录数据”的弹窗。要向非技术用户准确传达“到底发生了什么、侵入性到底多强”非常困难。换言之,立法者要求网站沟通 Cookie,网站也在努力合规;他们试图用意符去解决的问题,其实本质上是更深层的设计挑战。
人们做过不少努力,比如区分“必要 Cookie”和营销 Cookie;但用户仍缺乏对 Cookie 本体的清晰理解。结果是大多数人会一刀切关掉所有 Cookie,即使其中一些本可带来好处,或能在几乎无个人成本下支撑互联网经济。
更深的问题是,Cookie 本来就是低层编程抽象,而不是面向用户的功能。人们也提出过更好的抽象,比如提供更细粒度、用户更易理解的分类。可惜,可能由于集体行动困境,截至 2025 年,这个问题仍基本无解。
在产品周期较早阶段就尝试命名与组织产品,是一件非常值得做的事。它会迫使你关注完整用户旅程,并常带来有价值的设计洞察。敬请关注第 7 章和第 8 章,我们会更深入地探讨产品设计。
编码实践
如果某个抽象既别扭又不安全,而你暂时修不好,就别试图掩盖它。让名字丑一点,吸引注意与审视。也许你的代码评审会有更好想法,也许后来有人会把它改好。我曾见过一个私有类变量,半开玩笑地命名为 _do_not_use_or_you_will_be_fired,对此我也只能“勉强肃然起敬”。
本章小结
我们一路跟随用户,观察他们如何发现、理解并使用产品。此外,在侧栏中我们聚焦了日常编码中的命名决策和编码接口设计,把它们作为练习产品能力的方法。
我最终给出的建议如下:
- 对于发现:优先选择用户本体中已有的名字,或与其他命名保持一致。提供能帮助用户在产品中导航的意符,并以优雅方式逐步揭示更复杂功能。若有帮助,可花时间绘制产品发现地图来可视化用户旅程。
- 对于理解:首先避免歧义,并为每类用户提供一致体验。通过提供冗余的“进入路径”来拥抱受众及其视角的多样性,帮助他们理解你的产品。
- 对于使用:利用命名突出潜在陷阱与“危险区”,引导人们正确使用你的函数。
对于多画像应用,不要害怕为不同画像提供不同本体和发现路径。
最后,虽然本章位于 Develop 阶段,请记住:为产品本体中的关键概念命名和建模应更早发生,因为它能为整体设计过程带来重要洞察。
练习
在以下练习中,我会把某个东西命名为 foo 或 bar,你的任务是给它起更好的名字。(由于 foo 完全不含信息,可以把你的任务理解为尽可能拉大 foo-distance!)当然,这些题并不存在“标准答案”,但请从发现、理解、使用场景去思考。我希望它们接近真实世界练习,这意味着你可能需要做一点轻量设计思考,或上网查资料。
- 你的数据库允许用户在某张表数据变化时订阅变更事件。如果他们继承
DataChangeSubscription并向数据库注册,那么每次数据库事务提交后都会触发其回调。你会把这两个由子类实现的方法分别命名为什么?class DataChangeSubscription(ABC): # subscriptions apply to a certain database table or collection. @abstractmethod def foo() -> TableType: @abstractmethod def bar(old_record, changes: Dict[str, Any]) -> None: - 你在做一款面向啤酒爱好者的评分应用。用户点击某款啤酒后,会看到一个名为 Foo 的自由文本输入框来写感受。它该叫什么?
- 新用户会完成一段问卷和注册流程,你正在优化这条流程,因此想追踪一个聚合指标
foo,用于衡量他们花了多久。你会怎么命名? - 你在维护一个小型社交网络,最近提交的一次改动导致性能回退。你调用了这个函数:实际上你并行调用了很多次(每个好友一次),结果比预期昂贵,包含了一些你没预料到的计算与隐私校验。你暂时没时间优化。有没有可能通过重命名来避免其他人将来重蹈覆辙?
mutual_friends = await current_user.getMutualFriends(friend_user)
参考答案
- 对于 foo,
table_type可以,但可考虑table_type_filter以增强清晰度,让人明确“表类型是用于过滤条件”。对于 bar,可用on_record_changed。on_前缀符合常见事件处理器命名;record(单数)明确变化对象;changed(过去式)明确提交已发生。若以后新增“提交前”回调,可命名为on_record_changing。另外,我选择changed也是为了与类名中的Changed保持一致。 - 做一点用户研究(或问问聊天机器人)后,你会发现啤酒爱好者常用 “tasting notes” 来描述啤酒。你也可以用
description或review这类通用词,但 “Tasting Notes” 更可能让目标画像写出“香气”“口感(mouthfeel)”这类细致内容,这正是你想要的。 - 设想队友在团队看板上读图时会问什么。第一问通常是单位:毫秒、秒还是分钟?第二问可能是统计口径:平均值、中位数还是 p90?因此你可以命名为
user_signup_seconds_to_complete.p50之类。 getMutualFriends应该以某种方式提示它“很贵”。比如直接改成computeMutualFriends,也许就足以让你意识到成本。若后续易于重命名,也可叫computeMutualFriends_Expensive。也许未来正是这种“丑得显眼”的名字,会促使后来者去做缓存或把它优化得更便宜。