R1.17.1generic names attracting extra duties设计

名字越通用的组件越容易被塞进无关职责

别名: 万能组件 · 杂物袋组件 · catch-all component · Card dumping

概念解释

组件的名字是一张请柬,写明「什么可以进来」。叫 CardItemBoxCommonWidget 的组件几乎没有拒绝词:列表行、营销瓷砖、设置分组、空态插画,看起来都像一张「卡片」,于是全被塞进同一个入口。通用名字吸引额外职责(generic names attracting extra duties),不是因为作者想做万能,而是名字没有声明边界,调用方只能按字面最宽的意思来用。名字越不具体,被当作杂物袋的概率越高。

职责是行为与约束的集合,不是外观像不像。设置分组有自己的标题层级和折叠键盘,营销瓷砖有媒体比和点击整块——两者都「像卡片」,却不该共享一个拒绝不了任何一种的名字。

机制

人在调用点先搜名字,再读接口。搜到 Card 就停,因为字面覆盖了眼前这块矩形。接口若没有「我不是 X」的信号,新职责以属性或插槽的形式长进去:先加一个 clickable,再加一个 collapsible,再加 mediaRatio。每一次新增都有真实产品理由,名字却从未收窄,于是理由可以无限叠加。组件的实现变成一堆互不认识的模式的并集,每种模式的约束(键盘、空态、密度)在并集里互相稀释。

具体的名字反过来会挡:SettingsGroup 不会被拿去放首页促销。挡住是功能——它把请柬上没写的客人拦在门外。通用名把挡的责任推给使用方的自制力;自制力在进度压力下失效,杂物袋就形成了。

边界

真正的布局原语(栈、网格、分隔)名字本就通用,它们的职责是排版而不是业务;再起一个更具体的名字反而会诱使往里面塞业务。实验性的本地组件、只在一个文件里用、不进库,短名字的危害被文件边界挡住。品牌表达层的装饰性容器如果明确写着「无行为、无状态」,通用名可以留下,前提是行为入口被类型拒绝。改名本身治不好已经长进去的职责——名字收窄之后,旧职责仍在,需要把它们搬到该去的组件,而不是只换招牌。

怎么落地

  • 库里的每个组件名字必须能读出拒绝:读完名字能说出至少两件「这个不管」。说不出来的,先改名再谈接口。
  • 新需求到来时,先问「现有名字是否已经拒绝这件事」。被拒绝就新开组件;没被拒绝才允许加属性。禁止用「反正也是一块矩形」作为加入 Card 的理由。
  • 审查 Card / Item / Box / Common 的调用点,按任务类型聚类。一个名字下出现两种以上互不相容的键盘或空态,拆开并给每一半具体名字。
  • 验证:把组件名发给没读过实现的人,请他们列出「可以拿它做什么」和「不该拿它做什么」。后一张表若是空的,名字在揽责。再抽十处调用,看有多少是靠开关打开互斥行为——每一组互斥行为都是本该被名字挡掉、却被塞进来的职责。

延伸

  • 同组R1.17.2 为复用而增加的间接层会推高理解成本 · R1.17.3 内联重复有时比错误抽象更便宜
  • 相邻R1.08 过度抽象 · R1.02 组件库与变体
  • 站内检索generic names attracting extra duties · catch-all component · component naming

同组卡片

快捷操作

分享

分享当前页面

ios_share

https://hci.top/zh/handbook/R1.17.1