核心问题
什么是真正的简单?
软件美学里的“简洁”,不是少写几行代码,也不是把所有东西压扁。
真正的简单是:
必要的东西都在,不必要的东西都不在。
这和 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)
简洁系统会给重要规则一个名字和位置。
简洁检查清单
看一个设计是否简洁,问:
- 它减少了歧义,还是只是减少了代码行?
- 它表达了本质复杂性,还是把复杂性藏进约定?
- 调用者是否需要知道太多内部细节?
- 名字是否具体到足以阻止误用?
- 模块边界是否自然?
- 错误模型是否一致?
- 新人是否能快速建立正确心智模型?
- 如果需求变化,变化有地方去吗?
小结
- 简洁不是少东西,而是少歧义。
- 压缩不等于简化。
- 本质复杂性要被表达,偶然复杂性要被消除。
- 简洁 API 让调用者只提供业务必要信息。
- 简洁模块的边界自然,不只是文件少。
- 简洁数据模型刚好承认真实差异。
- 贫血模型不是简洁,只是把规则赶到各处。