核心问题
命名到底是在描述代码,还是在塑造系统?
很多人把命名当成最后的润色:
代码写完了,把变量名改好看一点。
但在软件工程里,命名不是装饰。命名是设计本身。
因为名字会决定:
- 团队如何理解一个概念
- 后续需求会被塞到哪里
- 哪些状态看起来合法
- 哪些关系会被隐藏
- 哪些错误会变得容易发生
一个坏名字不只是“不优雅”,它会持续把系统引向错误方向。
名字会吸附需求
如果系统里有一个很宽的名字:
User
新需求来了,大家会自然地把东西塞进去:
user.role
user.companyId
user.subscriptionStatus
user.isInstructor
user.isCustomer
user.permissions
user.metadata
这叫名字的吸附效应。
越宽泛的名字,越容易变成垃圾桶。
相反,如果名字边界清楚,需求会自然流向正确位置:
Account
Credential
OrganizationMembership
InstructorProfile
Subscription
CourseAccessGrant
好名字像路标。它告诉后来的人:
这个变化应该放这里,不应该放那里。
名字应该回答边界问题
好名字不只是“看得懂”,还要回答边界问题。
例如:
Customer
这个名字看似常见,但边界不清:
- 下过单就是 customer 吗?
- 当前付费才是 customer 吗?
- 退款后还是 customer 吗?
- 企业合同里的员工算 customer 吗?
- 免费试用用户算 customer 吗?
如果这些问题没有答案,Customer 就不是好名字。
更清楚的名字可能是:
PayingAccount
ActiveSubscriber
Purchaser
OrganizationContractHolder
这些名字更窄,但更可验证。
原则:
好名字应该让边界争议显形,而不是把争议藏起来。
名字应该绑定事实
日常语言容易含糊,工程语言必须落到事实。
例如:
Student
在课程平台里,Student 很可能不够精确。
更好的名字取决于事实:
Enrollment
CourseAccessGrant
LessonProgress
CourseCompletion
如果你想表达“正在学习某门课”,可能是:
Enrollment
如果你想表达“有权访问某门课”,可能是:
CourseAccessGrant
如果你想表达“学完了某门课”,可能是:
CourseCompletion
这些名字的优势是:它们不是印象,而是事实。
避免万能名词
万能名词通常是坏设计的温床。
高危词包括:
Manager
Handler
Processor
Service
Helper
Util
Data
Info
Context
Object
Record
Entity
Relation
Metadata
这些词不是绝对不能用,但它们往往说明我们还没想清楚。
例如:
CourseManager
它管理什么?
- 课程创建?
- 课程发布?
- 课程访问?
- 课程内容编辑?
- 课程推荐?
- 课程归档?
更好的名字可能是:
CoursePublisher
CourseAccessPolicy
CourseCatalog
CourseEditor
CourseArchiver
名字越具体,职责越难乱跑。
避免过度技术化命名
有些名字过于工程内部化:
AbstractActorRelationResolver
GenericResourceBinding
UnifiedSubjectContext
这些名字的问题不是长,而是它们没有领域含义。
领域系统里的核心名字应该尽量来自业务语言:
Enrollment
Purchase
Refund
Subscription
OrganizationMembership
CourseAccessGrant
InstructorProfile
技术词可以出现在基础设施层:
Repository
EventBus
OutboxWorker
CacheAdapter
PolicyEvaluator
但如果业务层充满 Generic、Abstract、Base、Context,通常说明设计正在离开领域。
名字应该暴露层级
一个好名字能让人知道它在哪一层。
例如:
事实层
CoursePurchased
PaymentRefunded
LessonCompleted
OrganizationMemberInvited
这些名字通常是过去式,因为它们表示已经发生的事实。
状态层
SubscriptionStatus
EnrollmentStatus
CoursePublicationStatus
这些名字表示生命周期阶段。
规则层
canAccessCourse
canEditCourse
canInviteMember
isSubscriptionActiveAt
这些名字通常是谓词或动作判断。
效果层
CourseAccessGrant
PermissionGrant
NotificationRecipient
这些名字表示多个事实共同产生的效果。
机制层
OutboxEvent
RetryPolicy
MessageConsumer
Repository
这些名字属于技术机制。
如果不同层级混名,系统会变难理解。
例如:
UserEventStatusManager
这个名字里同时出现对象、事件、状态、管理器,但没有说清楚它到底负责什么。
布尔命名要小心
布尔值尤其容易制造假简单。
坏例子:
isActive
isValid
isEnabled
isComplete
这些名字的问题是语义不完整。
isActive 对账号、订阅、课程、企业合同可能含义完全不同。
更好的名字:
canSignIn
isSubscriptionBillable
isCoursePublished
isEnrollmentCompleted
isContractInForce
布尔命名最好包含判断对象和判断标准。
如果一个布尔值需要读文档才知道意思,名字还不够好。
状态命名要表达生命周期
状态名要能看出流转。
例如:
type CoursePublicationStatus = "draft" | "published" | "archived"
比:
type CourseStatus = "active" | "inactive"
更好。
因为前者表达了课程发布生命周期,后者只是模糊开关。
好的状态命名应该满足:
- 状态集合互斥。
- 每个状态有明确含义。
- 状态之间能画出流转图。
- 不依赖外部解释才能理解。
函数命名要表达意图,不只表达动作
坏例子:
processUser(user)
handleCourse(course)
updateData(data)
syncStatus(id)
这些名字只说“做了点事”,没有说明为什么做。
更好的名字:
grantCourseAccessFromPurchase(purchaseId)
expireInactiveEnrollments(now)
publishCourse(courseId)
revokeAccessAfterRefund(refundId)
inviteOrganizationMember(organizationId, email)
好函数名应该让调用者知道业务意图。
命名是一种压缩
一个名字是对一组规则的压缩。
例如:
canAccessCourse(accountId, courseId)
这个名字背后可能包含:
- 是否购买
- 是否退款
- 是否订阅有效
- 是否企业分配
- 是否课程下架
- 是否账号被封禁
调用者不需要知道这些细节,只需要相信这个名字。
所以命名的质量决定了压缩是否可靠。
坏名字会压掉关键差异。
好名字会隐藏细节,但不隐藏边界。
改名是低成本重构
很多重构不需要先改架构,先改名字就能让问题显形。
例如把:
User
改成:
Account
你会立刻发现哪些字段不属于账号:
legalName
companyRole
courseProgress
billingAddress
再比如把:
isActive
改成:
canSignIn
你会立刻发现它和订阅是否有效不是一回事。
改名的价值是:
它强迫系统承认真实边界。
命名检查清单
给一个新概念命名时,可以问:
- 这个名字对应事实、状态、规则、效果还是机制?
- 它有没有独立生命周期?
- 它有没有独立不变量?
- 它的反例是什么?
- 它和相邻概念有什么区别?
- 它是否依赖上下文?
- 它是否太宽,容易吸附未来需求?
- 它是否太技术化,脱离业务语言?
- 它能否让调用者少知道细节?
- 未来看到这个名字的人,会不会把新需求放错地方?
小结
- 命名不是润色,而是设计。
- 宽泛名字会吸附需求,最终变成垃圾桶。
- 好名字让边界争议显形。
- 日常语言要落到系统事实。
- 避免万能名词和过度技术化命名。
- 不同层级应该有不同命名风格。
- 布尔命名要包含判断对象和标准。
- 改名是低成本但高价值的重构。