核心问题

什么是真正的简单?

软件美学里的“简洁”,不是少写几行代码,也不是把所有东西压扁。

真正的简单是:

必要的东西都在,不必要的东西都不在。

这和 Dimension 1 的 KISS 有关系,但角度不同。

KISS 问:

不要创造不必要的实体。

美学里的简洁问:

系统是否去掉了噪音,同时保留了表达力?

简洁 vs 简陋

简洁和简陋很容易混淆。

简陋

type User = {
  id: string
  role: string
  status: string
  metadata: Record<string, unknown>
}

它看起来简单,因为字段少、类型少。

但复杂性被藏进了:

  • 字符串约定
  • metadata
  • 调用方判断
  • 运行时错误
  • 文档和口头约定

这种“简单”只是把复杂性从代码结构里赶到人的脑子里。

简洁

type Account = {
  id: AccountId
  status: AccountStatus
}

type OrganizationMembership = {
  accountId: AccountId
  organizationId: OrganizationId
  role: "owner" | "manager" | "member"
}

type CourseAccessGrant = {
  accountId: AccountId
  courseId: CourseId
  source: CourseAccessSource
  validFrom: Date
  validUntil?: Date
}

这里类型更多,但概念边界更清楚。

读者不用猜:

  • 账号是什么
  • 组织身份在哪里
  • 课程访问权从哪里来
  • 哪些状态合法

所以它更简洁。

核心句:

简洁不是少东西,而是少歧义。

压缩不等于简化

有些代码短,但不简单。

例如:

const ok = u.s === "a" && !!u.m?.[cid] && !u.b

它很短,但读者要解码。

更好的写法:

const canAccess =
  account.status === "active" &&
  hasCourseAccessGrant(account.id, course.id) &&
  !account.isSuspended

甚至:

const canAccess = await canAccessCourse(account.id, course.id)

代码长度不是核心。

核心是:

读者能不能快速建立正确心智模型?

消除偶然复杂性

复杂性分两种:

本质复杂性

来自问题本身。

例如:

  • 支付有成功、失败、退款、争议
  • 权限有主体、资源、动作、上下文
  • 访问权有购买、订阅、企业分配、撤销

这些复杂性不能消失,只能被清楚表达。

偶然复杂性

来自实现方式。

例如:

  • 重复判断散落各处
  • 名字含糊
  • 状态用字符串乱传
  • 错误码不统一
  • 目录结构无规律
  • 函数参数顺序容易传错
  • 配置隐藏在多个地方

美学上的简洁,主要是消除偶然复杂性。

核心句:

本质复杂性要被表达,偶然复杂性要被消除。

简洁 API

一个简洁 API 不是参数最少,而是调用者不需要知道不该知道的东西。

坏 API:

accessResolver.resolve({
  subjectType: "account",
  subjectId: accountId,
  relationType: "course",
  relationId: courseId,
  includePurchase: true,
  includeSubscription: true,
  includeOrganizationAssignment: true,
  includeRevoked: false,
  mode: "effective",
})

调用者知道太多内部细节。

好 API:

canAccessCourse(accountId, courseId)

它隐藏了访问来源、撤销规则和有效期判断。

简洁 API 的标准是:

调用者只提供业务上必要的信息。

简洁模块

简洁模块不是文件少,而是职责边界清楚。

坏模块:

CourseManager
  create course
  publish course
  grant access
  refund purchase
  send email
  calculate progress
  export report

这个模块太宽。

好方向:

course-catalog
course-access
course-progress
billing
notification
reporting

每个模块回答一个清楚问题:

  • 课程是什么?
  • 谁能访问课程?
  • 学到哪里了?
  • 钱和订单怎么处理?
  • 需要通知谁?
  • 如何汇总数据?

核心句:

模块简洁不是小,而是边界自然。

简洁数据模型

简洁数据模型避免两种极端。

过度压扁

user.courseIds: string[]

它没有说明:

  • 买过?
  • 分配过?
  • 正在学?
  • 收藏过?
  • 完成过?
  • 有访问权?

过度拆碎

CourseAccessIntent
CourseAccessRelation
CourseAccessContext
CourseAccessBinding
CourseAccessMetadata

它把读者拖进抽象森林。

好的模型应该刚好表达稳定事实:

Purchase
CourseAssignment
CourseAccessGrant
Enrollment
LessonProgress

核心句:

数据模型的美,在于它刚好承认真实差异。

简洁错误处理

错误处理也有美学。

坏错误:

throw new Error("failed")

或者每个地方自创一套:

not_allowed
permission_denied
forbidden
NO_ACCESS
accessFailed

好方向:

throw new DomainError("COURSE_ACCESS_DENIED", {
  reason: "account_suspended",
  accountId,
  courseId,
})

简洁错误模型应该:

  • 稳定
  • 可聚合
  • 可解释
  • 可测试
  • 不泄露内部细节

错误不统一,会让系统显得粗糙。

简洁不是贫血

有些人追求“简单”,会把领域规则全放到外面。

例如:

type Subscription = {
  status: string
  currentPeriodEndsAt: Date
}

然后到处写:

subscription.status === "active" &&
subscription.currentPeriodEndsAt > now

这不是简洁,是贫血。

更好:

isSubscriptionActiveAt(subscription, now)

或者:

subscriptionPolicy.isActiveAt(subscription, now)

简洁系统会给重要规则一个名字和位置。

简洁检查清单

看一个设计是否简洁,问:

  1. 它减少了歧义,还是只是减少了代码行?
  2. 它表达了本质复杂性,还是把复杂性藏进约定?
  3. 调用者是否需要知道太多内部细节?
  4. 名字是否具体到足以阻止误用?
  5. 模块边界是否自然?
  6. 错误模型是否一致?
  7. 新人是否能快速建立正确心智模型?
  8. 如果需求变化,变化有地方去吗?

小结

  1. 简洁不是少东西,而是少歧义。
  2. 压缩不等于简化。
  3. 本质复杂性要被表达,偶然复杂性要被消除。
  4. 简洁 API 让调用者只提供业务必要信息。
  5. 简洁模块的边界自然,不只是文件少。
  6. 简洁数据模型刚好承认真实差异。
  7. 贫血模型不是简洁,只是把规则赶到各处。