1 分钟

Rich Hickey 与 Clojure:简洁、不可变性与更好的默认值

浅显讲解 Rich Hickey 的 Clojure 思想:简洁、不可变性与更好的默认值——关于如何构建更平静、更安全的复杂系统的实用经验。

Rich Hickey 与 Clojure:简洁、不可变性与更好的默认值

为什么复杂性在实际项目中常常占上风

软件很少一次性变得复杂。它是通过一次又一次“合理的”决定走到那里的:为了赶期限加了一个快速缓存、为了避免拷贝共享了可变对象、因为“这个例外特殊”而放宽规则。每个选择看起来都小,但累积起来就会产生一个对变更充满风险、错误难以复现、添加新功能比实现它们花更长时间的系统。

复杂性占上风因为它提供了短期的舒适感。引入新依赖通常比简化现有结构更快。修补状态比追问“为什么状态分散在五个服务里”更容易。当系统增长速度超过文档更新时,人们也更容易依赖约定和部落知识。

本文是什么(以及不是什么)

这不是一本 Clojure 教程,你也不需要懂 Clojure 才能获益。目标是借用一套与 Rich Hickey 的工作相关的实用思想——这些思想可应用于日常的工程决策,无论使用何种语言。

为什么默认值比你想的更重要

大多数复杂性并不是由你刻意编写的代码产生,而是由工具让某些做法成为默认变得容易而产生的。如果默认是“到处都是可变对象”,你会得到隐藏耦合。如果默认是“状态驻留在内存中”,你会在调试和可追溯性上吃亏。默认值塑造习惯,而习惯塑造系统。

我们将关注三个主题:

  • 简洁:不是更少特性,而是更少活动部件和更少特例。
  • 不可变性:把数据当成不会改变的值来对待,这样你可以推理它们。
  • 更好的默认值:选择那些让安全且可预测的选项最容易的模式。

这些思想不会从领域中移除复杂性,但可以阻止你的软件将其成倍放大。

Rich Hickey 与 Clojure 想要解决的问题

Rich Hickey 是一位长期的软件开发者和设计者,因创建 Clojure 以及在演讲中挑战常见编程习惯而闻名。他的关注点不是追逐潮流,而是分析系统变得难以更改、难以推理和难以信任的反复出现的原因。

Clojure 是什么(高层,无行话)

Clojure 是一门现代编程语言,运行在诸如 JVM(Java 运行时)和 JavaScript 等常见平台上。它旨在与现有生态协作,同时鼓励一种特定的风格:把信息表示为纯粹的数据,偏好不变的值,并把“发生了什么”与“你在屏幕上显示什么”分离开。

你可以把它看作一门促使你更倾向于使用更清晰构建块、远离隐藏副作用的语言。

它旨在减少的问题

Clojure 的目标不是让小脚本更短,而是解决反复出现的项目痛点:

  • 来自共享状态的增长性复杂性:当系统的许多部分都能修改同一份数据时,缺陷会带有时序性并难以复现。
  • 数据与行为紧耦合:当信息被锁在对象或类内部时,可重用性变差,变更会在代码库中产生连锁反应。
  • 并发头疼:随着系统增加后台任务、队列和平行工作,“谁在什么时候改变了什么?”会成为日常问题。

Clojure 的默认倾向促进更少的活动部件:稳定的数据结构、显式更新和让协作更安全的工具。

即便你从不采用 Clojure 也有用

价值并不限于换语言。Hickey 的核心思想——通过消除不必要的相互依赖来简化、把数据视为持久事实、最小化可变状态——能改善 Java、Python、JavaScript 等语言中的系统。

简洁:不是“容易”,而是更少的活动部件

Rich Hickey 清晰地区分了简单(simple)容易(easy)——这是大多数项目在不知不觉中跨过的一条线。

简单与容易(用日常例子说明)

容易关乎“现在的感觉”。简单关乎“有多少部件以及它们有多纠缠”。

  • 方便面是容易的。基本炖菜可以是简单的:几种食材、一口锅、没有隐藏的东西。
  • 一个有 60 个按钮的遥控器可能让一个功能“容易”(按钮就在那),但它并不简单。一个有 6 个清晰控制的遥控器更简单,即便需要花一分钟学习。

在软件中,“容易”常意味着“今天敲代码快”,而“简单”意味着“下个月更难被破坏”。

“现在容易”如何制造未来的复杂性

团队常常选择能立即降低摩擦的捷径,但这些捷径会增加需要维护的隐性结构:

  • “我们就加一个标志。”现在每个功能都要考虑这个标志。
  • “我们把计算结果存起来以节省时间。”现在你必须在多处保持一致性。
  • “我们可以在 UI 里补丁式修复。”现在相同的业务规则出现在多个地方。

每个选择可能感觉像速度,但它增加了活动部件、特例和交叉依赖。这就是系统在没有任何戏剧性错误的情况下变脆弱的方式。

速度并不等于简洁

快速交付可以很好——但没有简化的速度通常意味着你在透支未来。利息表现为难以复现的 bug、拖慢的入职速度,以及需要“谨慎协调”的变更流程。

防止意外复杂性的快速检查表

评审设计或 PR 时问自己:

  • 我们引入了新的模式、标志或配置分支吗?
  • 我们是否在缓存或复制必须保持一致的数据?
  • 为完成一个行为是否需要同时修改多个模块?
  • 这个规则是否在不止一个地方实现?
  • 新队友能否在没有额外解释的情况下预测系统是如何工作的?

状态:复杂性的隐形倍增器

“状态”就是系统中会改变的东西:用户的购物车、账户余额、当前配置、工作流的步骤。关键问题不是变化存在,而是每次变化都增加了不一致的机会

当人们说“状态导致 bug”时,他们通常指的是:如果同一条信息在不同时间或不同地点可能不同,那么代码就必须不断回答“现在哪个版本才是真正的?”回答错误就会产生看起来随机的错误。

可变性:你看不见的改变

可变性意味着对象在原地被修改:“同一件事物”会随时间而异。听起来高效,但它让推理变得困难,因为你不能依赖之前看到的东西。

一个可感知的例子是共享的电子表格或文档。如果多个人可以同时编辑同一单元格,你的理解会立刻失效:总计改变、公式失效或行被重排。即便没人恶意操作,“可共享可编辑”的特性本身就会制造混乱。

软件状态也是如此。如果系统的两部分读取相同的可变值,其中一部分可以在另一部分仍然基于过时假设运行时悄悄改变它。

为什么调试变得如此痛苦

可变状态把调试变成考古学。一个 bug 报告很少会告诉你“数据在 10:14:03 被错误地改了”。你只看到结果:错误的数字、意外的状态、只在特定情况下失败的请求。

因为状态会随时间更改,最重要的问题变成:是什么编辑序列导致了现在的结果? 如果无法重构那个历史,行为就不可预测:

  • 同样的操作会因时序不同产生不同结果。
  • 修复在我的机器上有效,但在生产无效。
  • 增加日志会改变时序,导致 bug 消失。

这就是为什么 Hickey 把状态视为复杂性的倍增器:一旦数据既被共享又是可变的,可能的交互数量增长得比你维持它们的能力还快。

用非计算机科学行话解释不可变性

不可变性简单来说就是数据在创建后不再改变。你不是在原有信息上就地编辑,而是创建一个反映更新的新信息。

把它想像成收据:打印出来后你不会擦掉某行再重写总计。如果有变动,你会开一张更正收据。旧的保留,新的一目了然地是“最新版本”。

为什么它减少惊喜

当数据不能被悄悄修改时,你就不必担心背后有隐形改动。这让日常推理容易得多:

  • 如果你持有一个值,你可以信任它会保持不变。
  • Bug 更容易重现,因为相同输入保持不变。
  • 在系统各部分之间共享数据更安全,因为没人能意外地“搞砸”别人的数据。

这是 Hickey 反复谈到简洁的一个重要原因:更少的隐藏副作用意味着更少需要跟踪的心理分支。

“新版本” vs. “就地编辑”

创建新版本听起来可能浪费,直到你比较替代方案。就地编辑会让你不断问:“谁改了它?什么时候改的?之前是什么?”有了不可变数据,变更是显式的:新版本存在,旧版本仍可用于调试、审计或回滚。

Clojure 倾向于让把更新视为产生新值而不是修改旧值变成自然的做法。

需要诚实面对的权衡

不可变性不是免费的。你可能会分配更多对象,习惯于“直接更新东西”的团队也需要时间适应。好消息是现代实现常常在底层共享结构以减少内存成本,回报通常是更平静的系统和更少难以解释的事故。

当数据不变时,并发更容易处理

为更简单的路径做原型
通过对话把你的下一个重构想法变成可运行的原型。

并发只是“许多事情同时发生”。一个处理数千请求的 web 应用、在生成收据时更新余额的支付系统、或后台同步的移动应用——这些都是并发场景。

难点不在于多件事同时发生,而在于它们常常触及相同的数据。

为什么共享且可变的数据会产生竞态条件

当两个工作者都可以读取然后修改同一值时,最终结果可能取决于时序。这就是竞态条件:不是容易复现的错误,而是在系统繁忙时出现的错误。

例如:两个请求尝试更新订单总额。

  1. 请求 A 读到 total = 100
  2. 请求 B 读到 total = 100
  3. A 加上 20 并写入 120
  4. B 加上 10 并写入 110

没有“崩溃”,但你丢失了一次更新。在高并发下,这类时序窗口更常见。

传统的修复办法——锁、同步代码块、谨慎的顺序控制——有效,但它们迫使所有人协调。协调代价高:降低吞吐量,并且随着代码库增长变得脆弱。

不可变性如何将协调降到最低

使用不可变数据时,值不会被原地编辑。相反,你创建一个代表更改的新值。

这一改变去除了整类问题:

  • 读取者不必担心他们读取的东西会在读取过程中改变。
  • 写入者不会在相同内存上“争斗”;他们生成新版本。
  • 系统可以选择安全的方式发布最新版本(通常用简单、经过验证的原语)。

结果:高负载下更可预测的行为

不可变性并不意味着并发无成本——你仍需规则来决定哪个版本是当前的。但它让并发程序更可预测,因为数据本身不再是一个移动的目标。当流量暴增或后台作业堆积时,你不太可能看到神秘的、时序依赖的故障。

“更好的默认值”在实践中意味着什么

“更好的默认值”意味着安全的选择会自动发生,只有在你明确选择退出时才承担额外风险。

这听起来很小,但默认值会悄悄影响你周一早上写的代码、审查者在周五下午接受的改动,以及新队友从首个代码库学到的习惯。

能降低风险的默认值

“更好的默认值”不是替你做每个决策,而是让常见路径更不易出错。例如:

  • 默认不可变数据:不是“修改这个东西”,而是创建它的新版本。这使得意外影响程序其他部分更难发生。
  • 纯函数作为常态风格:函数接受输入并返回输出,不秘密修改共享数据或依赖隐藏的全局状态。这让行为更易预测和测试。
  • 显式的状态变更:当必须改变时,通过清晰、明确定义的机制发生,而不是任何地方都能随意修改。

这些并不能消除复杂性,但能阻止复杂性蔓延。

默认值如何影响团队和代码审查

团队不仅遵循文档——他们遵循代码“想要”你做的事。

当共享状态容易被修改时,它会成为惯常的捷径,审查者不得不争论意图:“这里安全吗?”当不可变性和纯函数是默认时,审查者可以专注于逻辑和正确性,因为高风险的操作会显得突出。

换句话说,更好的默认值创造了更健康的基线:大多数改动看起来一致,而不寻常的模式足够显眼值得被质疑。

维护与入职

长期维护主要是关于安全地读取和修改现有代码。

更好的默认值帮助新队友更快上手,因为隐藏规则更少(“小心,这个函数悄悄更新了那个全局映射”)。系统更易推理,从而降低未来每次功能、修复和重构的成本。

把事实与视图分离:时间、历史与可追溯性

Hickey 演讲中一个有用的思维转变是把事实(发生了什么)与视图(我们当前相信的是什么)分开。大多数系统把它们混为一谈,只存储最新值——用今天覆盖了昨天——这会让时间消失。

事实是追加的,视图是派生的

事实是不可变记录:“订单 #4821 在 10:14 下单”,“支付成功”,“地址被更改”。这些不会被编辑;当现实变化时你会新增事实。

视图是应用当前所需的东西:“当前的收货地址是什么?”或“客户余额是多少?”视图可以从事实重算、缓存、建立索引或物化以提速。

保留历史的好处

保留事实会带来:

  • 可审计性:你可以解释为什么当前值是现在这样。
  • 调试:你可以回放序列并找到偏离的时刻。
  • 可追溯性:"谁在什么时候改了它,以及之前是什么?" 成为数据问题而不是侦探故事。

一个易懂的例子:覆盖 vs 追加

覆盖记录就像更新电子表格单元格:你只能看到最新数字。

追加式日志就像支票簿记账:每一条条目都是事实,“当前余额”是从条目计算出来的视图。

并非每个系统都需要完整事件溯源

你不必采用完整的事件溯源架构就能受益。许多团队从小处开始:为关键更改保留追加式审计表、为少数高风险工作流存储不可变的“变更事件”,或保存快照加短期历史窗口。关键在于养成习惯:把事实当作持久的,把当前状态当作方便的投影。

数据优先:让信息持久且灵活

从更好的默认设置开始
在变得混乱之前,使用 Koder.ai 以更安全的默认值构建小功能。

Hickey 一个很实用的思想是数据优先:把系统信息当作纯值(事实)来处理,把行为当作针对这些值运行的东西。

数据是持久的。如果你存储清晰、自包含的信息,就可以在以后重新解读它、在服务间迁移、重新建立索引、审计或用于新功能。行为则不那么持久——代码会变、假设会变、依赖会变。

值 vs 行为(无行话)

  • 数据(值):"什么是真的?" 客户邮箱、订单总额、时间戳、状态。
  • 行为(动作):"我们做什么?" 验证、计算折扣、发送通知、决定状态含义。

当你把两者混在一起时,系统会变得粘滞:你无法重用数据而不拖拽生成它的行为。

更少耦合,更多复用

把事实与动作分离可以减少耦合,因为组件可以在数据形状上达成一致,而不必在共享代码路径上达成一致。

报告任务、支持工具和计费服务都可以消费相同的订单数据,各自应用自己的逻辑。如果你把逻辑嵌入到持久化表示中,每个消费者都会依赖那段嵌入逻辑——改变它就会变得风险极大。

例子:存储干净数据 vs 在存储中放微型程序

干净数据(易于演化):

{
  "type": "discount",
  "code": "WELCOME10",
  "percent": 10,
  "valid_until": "2026-01-31"
}

存储中的微型程序(难以演化):

{
  "type": "discount",
  "rule": "if (customer.orders == 0) return total * 0.9; else return total;"
}

第二种看起来灵活,但它把复杂性推到了数据层:你现在需要安全的评估器、版本控制规则、安全边界、调试工具以及当规则语言变化时的迁移计划。

为什么这使系统更易演化

当存储的信息保持简单且显式时,你可以在不重写历史的情况下改变行为。旧记录依然可读。新服务可以添加而不必“理解”遗留的执行规则。你可以通过编写新代码引入新解释——新的 UI 视图、新的定价策略、新的分析——而不是通过改变数据的“含义”。

将这些思想应用到复杂系统(不用大规模重写)

大多数企业系统不是因为某个模块“坏”而失败。它们失败是因为一切互相连接。

需要警惕的失败模式

紧耦合以“小改动需要数周回归测试”的形式出现。给某服务加个字段会破坏三个下游消费者。共享数据库 schema 成为协调瓶颈。单一可变缓存或单例“配置”悄然成为半数代码库的依赖。

级联式变更是自然结果:许多部分共享同一可变事物时,冲击半径会扩大。团队通过增加流程、规则和移交来应对——往往让交付更慢。

用更简单的边界减少冲击半径

你可以在不换语言或重写所有东西的情况下应用 Hickey 的思想:

  • 在边界处偏向不可变数据。 把消息、事件和 API 输入视为不会就地编辑的“事实”。如果发生变化,创建新版本。
  • 把状态移动到边缘。 让核心逻辑保持纯变换:输入数据 → 输出数据。数据库、缓存和 UI 负责“当前状态”。
  • 停止共享可变结构。 如果两个模块都写同一个对象,它们就耦合了。传递值,而不是计划去修改的引用。

当数据不会在脚下改变时,你花在调试“它是如何进入这种状态的?”上的时间就会减少,而更多时间可以用来推理代码的实际行为。

团队间的“更好默认值”

不一致从默认值悄然渗入:每个团队都自创时间戳格式、错误形状、重试策略和并发方案。

更好的默认看起来像:版本化的事件 schema、标准的不可变 DTO、清晰的写入归属,以及一小套被认可的序列化、校验和跟踪库。结果是更少的惊喜集成和更少的一次性修复。

在现有代码库中可行的增量采用

从已经有变更的地方开始:

  1. 用接受/返回不可变数据的 API 包装现有模块。
  2. 把一个高变更频率的工作流转换为事件式的“追加式”记录。
  3. 用可从持久事实重算的视图替换共享可变缓存。

这种方法在保持系统运行的同时提升可靠性和团队协调,并把范围控制到可完成的规模。

平台与工具如何强化“更好的默认值”

拥有你的源代码
导出源代码以便审查、重构,并在边界处强制不变性。

当你的工作流支持快速、低风险的迭代时,这些思想更容易应用。例如,如果你在构建像 Koder.ai 这样的产品(一个面向 Web、后端和移动应用的聊天式即写即编码平台),有两项功能直接契合“更好默认值”思路:

  • 规划模式促使你在实现前明确边界和数据形状——这往往决定了一套是简单的数据流还是意外耦合。
  • 快照与回滚让变更更安全,因为当“容易”的捷径变成复杂性激增时,你可以快速回退。

即便你的栈是 React + Go + PostgreSQL(或移动端的 Flutter),核心观点依然:你每天使用的工具会悄悄教给你一种默认工作方式。选择那些让可追溯性、回滚和明确规划成为常态的工具,可以减少现场“就地修补”的压力。

权衡、局限与避免教条主义

简洁和不可变性是强有力的默认选择,而不是道德律令。它们在系统增长时能减少出乎意料的改变次数。但真实项目有预算、期限和约束——有时可变性是正确的工具。

何时可变性是可接受的

当它被限制时,可变性是实际且合理的:性能热点(紧循环、高吞吐解析、图形、数值运算)、函数内部局部变量、通过接口隐藏的私有缓存、或单线程组件在明确边界内的使用。

关键是封装。如果“可变的东西”绝不会泄露出去,它就无法在代码库中传播复杂性。

通过归属与接口限制复杂性

即便在以函数式为主的风格里,团队仍需明确归属:

  • 一个模块拥有一段状态并暴露小而清晰的 API。
  • 数据作为纯值跨越边界(而非带有秘密行为的活动对象)。
  • 副作用被推到边缘(I/O、数据库、时间)。

这也是 Clojure 倾向于数据与显式边界的好处所在,但这是一种架构纪律,而非语言独有的特性。

Clojure 不能解决的事情

没有任何语言能修复糟糕的需求、不明确的领域模型或无法达成共识的团队。不可变性不会让一个混乱的工作流变得可理解,“函数式”代码仍然可能将错误的业务规则写得更整洁——但并不正确。

避免教条:用最小变更降低风险

如果系统已上线,不要把这些思想当作非此即彼的重写理由。寻找能降低风险的最小举措:

  • 在模块边界用不可变数据替代共享可变结构。
  • 在关键审计点增加追加式日志或历史记录。
  • 用窄接口封装遗留状态,防止它蔓延。

目标不是追求纯粹,而是每次变更产生更少意外。

一个实用检查表,迈向更简洁的软件

这是一个可在不更换语言、框架或团队结构下完成的冲刺级清单。

下个冲刺要尝试的 3–5 件事

  1. 让你的“数据形状”默认不可变。 把请求/响应对象、事件和消息当作创建后不再修改的值。如果必须改变,就创建新版本。

  2. 在工作流中间偏好纯函数。 从一个工作流开始(例如定价、权限、结账),把核心重构为接受数据并返回数据的函数——不要有隐藏的读/写。

  3. 把状态移到更少、更清晰的位置。 每个概念选一个权威来源(客户状态、特性开关、库存)。如果多个模块各自保存一份副本,那就把它作为一个明确的决定并制定同步策略。

  4. 为关键事实增加追加式日志。 在一个领域记录“发生了什么”作为持久事件(即便你仍然存储当前状态)。这改善可追溯性并减少猜测。

  5. 在 API 中定义更安全的默认值。 默认应最小化惊讶:明确时区、明确空值处理、明确重试和显式的顺序保证。

设计评审问题(逐字使用)

  • 这里有哪些可变部分,谁被允许修改它们?
  • 我们能否把它建模为“事实 + 派生视图”,而不是覆盖字段?
  • 如果两个请求同时运行,什么会出问题?
  • 我们依赖了哪些默认(时间、顺序、重试、缓存),它们是否有文档?
  • 我们需要什么来在三个月后调试这个问题?

建议重温的 Hickey 演讲主题

寻找关于简单与容易的区别、管理状态、面向值的设计、不可变性,以及“历史”(随时间的事实)如何帮助调试与运营的材料。

简洁不是一个你贴上去的功能——它是通过一系列小而可重复的选择来实践的策略。

常见问题

为什么复杂性在真实项目中常常“获胜”?

复杂性通过一系列看似合理的、局部优化的决定慢慢累积(额外的标志位、缓存、例外处理、共享工具等),这些决定增加了“模式”和“耦合”。

一个好的信号是:一次“小改动”需要在多个模块或服务间协调修改,或者审查者需要依赖“部落知识”来判断是否安全。

在软件中,“简单”和“容易”有什么区别?

因为捷径优化的是“今天的摩擦”(上线速度),却把成本推到未来:调试时间、协调开销和变更风险。

一个有用的习惯是在设计/PR 评审时问:“这会引入哪些新的运动部件或特例?谁来维护它们?”

编程语言和框架的默认设置如何制造意外复杂性?

默认行为塑造工程师在压力下的选择。如果可变性是默认值,共享状态就会蔓延;如果默认假设“内存就足够”,可追溯性就会消失。

通过让安全路径成为最省力的路径来改进默认值:边界处使用不可变数据、明确的时区/空值/重试策略,以及清晰的状态归属。

为什么把“状态”称为“复杂性倍增器”?

“状态”是随时间变化的任何事物。难点在于变化带来了分歧的可能性:两个组件可能拥有不同的“当前”值。

因此,漏洞常表现为时序相关的行为(“本地可重现”、“在生产中不稳定”),因为问题变为:我们基于哪个版本的数据进行了操作?

不可变性在实用、非学术的层面上是什么意思?

不可变性意味着你不在原处编辑某个值;而是创建一个代表更新的新值。

实用层面上,它的好处包括:

  • 读取者可以信任数据在使用期间不会改变。
  • 重现 bug 更容易(输入保持稳定)。
  • 在线程/模块间共享数据更安全。
什么时候可变性是可接受(甚至更合适)的?

并非总是不可变优于可变。在受限的情况下,可变性是合理甚至必要的:

  • 函数内部的局部变量
  • 性能热点(紧循环、解析、数值计算)
  • 隐藏在窄接口后的私有缓存

关键规则是:不要让可变结构泄漏到许多组件可以读写的边界之外。

不可变性如何在高并发下帮助解决问题?

竞争条件通常来自共享且可变的数据被多个工作者先读后写。

不可变性减少了协调的表面积,因为写入者生成新版本而不是编辑共享对象。你仍需一个发布“当前版本”的规则,但数据本身不再是一个随时会变化的目标,从而降低时序相关失败的概率。

什么是将“事实”与“视图”分离?如何渐进应用?

把事实视为不可变的追加记录(事件),把“当前状态”看作从这些事实导出的视图。

可以循序渐进地开始,不必全面采用事件溯源:

  • 为关键更改增加审计表
  • 为一个高风险工作流记录变更事件
  • 保留快照加有限历史窗口以便回放/调试
什么是“以数据为先”的设计,为什么它减少耦合?

把信息以清晰、显式的数据(值)存储,然后针对这些数据执行业务逻辑。避免把可执行规则嵌入到持久化记录中。

这样系统更可演化,因为:

  • 当代码变化时旧记录仍然可读。
  • 新的消费者可以重用相同的数据结构。
  • 可以通过写新代码来改变逻辑,而不是重写历史。
在下一个冲刺里减少复杂性的前三个具体改动是什么?

挑选一个经常变更的工作流并在下一个冲刺中应用三步:

  1. 将边界数据设为不可变:把 API 输入/输出、消息和事件视为“创建后不再修改”。
  2. 把核心重构为纯变换:输入数据 → 输出数据;将 I/O 和副作用推到边缘。
  3. 减少共享可变结构:每块状态由一个所有者负责,通过窄接口暴露。

衡量成功的指标包括更少的间歇性错误、更小的变更影响面,以及发布时更少的“精心协调”。

Related posts