R1.17.3inline duplication cheaper than wrong abstraction设计

内联重复有时比错误抽象更便宜

别名: 内联重复 · 复制更便宜 · duplication cheaper · local copy

概念解释

两处界面今天长得像,并不等于明天还该是同一个模块。把它们抽成一份共享实现之后,任何一边的真实差异都要穿过共享接口,变成开关、特例分支或「先改共享再盼另一边没事」。内联重复(inline duplication)把两份代码留在各自的调用点旁边,改动半径止于那一处。当共享的那份抽象已经对不上两边将要走的路,复制的总工时——含两处都要改的那几次——仍可能低于维护错误共享的工时。便宜指的是改动半径和理解半径,不是少打几行字。

重复不是美德。它是在「抽错」的代价已经可见时,选一个半径更小的结构。抽对的共享仍然更便宜;这条说的是抽错的那一类。

机制

错误共享把两处未来的分叉绑死。一边要加键盘快捷键,另一边要改成不可交互的陈列,共享模块必须同时表达两种互斥命运,于是长出谁都不真正拥有的参数。每次改动要回归两边,即使需求只来自一边。回归失败时,两边互相怪「谁把共享弄坏了」。成本是耦合,不是行数。

内联把耦合剪断。一边改键盘,另一边的陈列甚至不会被重新编译。看起来浪费的那几十行,买下的是独立的失败域。当两处的差异速率高于它们的相同速率——每个迭代都在分叉,而「真正两边都要的修补」很少——复制的净支出更低。共享只有在相同速率持续高于差异速率时才划算;一旦方向已经反了,继续加开关是在为错误抽象付复利。

边界

无障碍合成、焦点环、对比计算这种跨产品必须一致、且差异速率接近零的内核,重复会立刻变成漏洞:一边修了焦点,另一边忘了。复制只适用于产品语义层,不适用于这些内核。法律文案、安全提示若必须逐字相同,重复会制造漏改,必须单源。两处已经在对同一缺陷各打各的补丁、且补丁开始互相拷贝时,说明相同速率回来了,该抽,只是抽的边界要按修补走,不要抽回那个错误的大模块。生成代码的两份输出长得像,源只有一份,那不是内联重复,不要手工去「去重」生成物。

怎么落地

  • 准备抽取前先写两列:未来三个迭代里两边各自确定会变的,以及两边确定必须一起变的。前一列更长,就先复制,不抽。
  • 已经抽错的共享,允许一次合法的拆回:把模块复制到两处调用旁,删掉为迁就对方而存在的开关,再分别改。拆回要在变更单上标明「解除错误共享」,避免被当成回归。
  • 对必须单源的内核(焦点、对比、名称合成)列白名单,评审拒绝把这些复制进产品文件。
  • 验证:打开那个共享模块,数为「只有一边需要」而存在的分支。分支数大于两边真正共用的行为数,抽象在亏。选一个只属于 A 的改动做预演:若必须给 B 加回归、或给共享加开关,复制会更便宜。拆回之后,A 的改动不再触发 B 的测试,才说明失败域已经分开。

延伸

  • 同组R1.17.1 名字越通用的组件越容易被塞进无关职责 · R1.17.2 为复用而增加的间接层会推高理解成本
  • 相邻R1.08 过度抽象 · R1.02 组件库与变体
  • 站内检索inline duplication cheaper than wrong abstraction · duplication cheaper · wrong abstraction

同组卡片

快捷操作

分享

分享当前页面

ios_share

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