名字越通用的组件越容易被塞进无关职责
别名: 万能组件 · 杂物袋组件 · catch-all component · Card dumping
概念解释
组件的名字是一张请柬,写明「什么可以进来」。叫 Card、Item、Box、Common、Widget 的组件几乎没有拒绝词:列表行、营销瓷砖、设置分组、空态插画,看起来都像一张「卡片」,于是全被塞进同一个入口。通用名字吸引额外职责(generic names attracting extra duties),不是因为作者想做万能,而是名字没有声明边界,调用方只能按字面最宽的意思来用。名字越不具体,被当作杂物袋的概率越高。
职责是行为与约束的集合,不是外观像不像。设置分组有自己的标题层级和折叠键盘,营销瓷砖有媒体比和点击整块——两者都「像卡片」,却不该共享一个拒绝不了任何一种的名字。
机制
人在调用点先搜名字,再读接口。搜到 Card 就停,因为字面覆盖了眼前这块矩形。接口若没有「我不是 X」的信号,新职责以属性或插槽的形式长进去:先加一个 clickable,再加一个 collapsible,再加 mediaRatio。每一次新增都有真实产品理由,名字却从未收窄,于是理由可以无限叠加。组件的实现变成一堆互不认识的模式的并集,每种模式的约束(键盘、空态、密度)在并集里互相稀释。
具体的名字反过来会挡:SettingsGroup 不会被拿去放首页促销。挡住是功能——它把请柬上没写的客人拦在门外。通用名把挡的责任推给使用方的自制力;自制力在进度压力下失效,杂物袋就形成了。
边界
真正的布局原语(栈、网格、分隔)名字本就通用,它们的职责是排版而不是业务;再起一个更具体的名字反而会诱使往里面塞业务。实验性的本地组件、只在一个文件里用、不进库,短名字的危害被文件边界挡住。品牌表达层的装饰性容器如果明确写着「无行为、无状态」,通用名可以留下,前提是行为入口被类型拒绝。改名本身治不好已经长进去的职责——名字收窄之后,旧职责仍在,需要把它们搬到该去的组件,而不是只换招牌。
怎么落地
- 库里的每个组件名字必须能读出拒绝:读完名字能说出至少两件「这个不管」。说不出来的,先改名再谈接口。
- 新需求到来时,先问「现有名字是否已经拒绝这件事」。被拒绝就新开组件;没被拒绝才允许加属性。禁止用「反正也是一块矩形」作为加入
Card的理由。 - 审查
Card/Item/Box/Common的调用点,按任务类型聚类。一个名字下出现两种以上互不相容的键盘或空态,拆开并给每一半具体名字。 - 验证:把组件名发给没读过实现的人,请他们列出「可以拿它做什么」和「不该拿它做什么」。后一张表若是空的,名字在揽责。再抽十处调用,看有多少是靠开关打开互斥行为——每一组互斥行为都是本该被名字挡掉、却被塞进来的职责。