Deepening

如何在给定依赖下安全地加深一组 shallow module。假设 SKILL.md 中的词汇 — moduleinterfaceseamadapter

Dependency categories

评估 deepening 候选时,对其依赖分类。类别决定 deepened module 如何跨 seam 测试。

1. In-process

纯计算、内存 state、无 I/O。始终可 deepen — 合并 module 并直接通过新 interface 测试。无需 adapter。

2. Local-substitutable

有本地 test stand-in 的依赖(Postgres 用 PGLite、in-memory filesystem)。若 stand-in 存在则可 deepen。deepened module 在 test suite 中用 stand-in 测试。seam 在内部;module 的 external interface 上无 port。

3. Remote but owned (Ports & Adapters)

跨网络边界的自有服务(microservice、内部 API)。在 seam 处定义 port(interface)。deep module 拥有 logic;transport 作为 adapter 注入。test 用 in-memory adapter。生产用 HTTP/gRPC/queue adapter。

推荐表述:"Define a port at the seam, implement an HTTP adapter for production and an in-memory adapter for testing, so the logic sits in one deep module even though it's deployed across a network."

4. True external (Mock)

你无法控制的第三方服务(Stripe、Twilio 等)。deepened module 将外部依赖作为注入 port;test 提供 mock adapter。

Seam discipline

  • One adapter 意味着 hypothetical seam。Two adapters 意味着 real seam。 除非至少两个 adapter 有理由(通常 production + test),否则不要引入 port。单 adapter seam 只是 indirection。
  • Internal seam vs external seam。 deep module 可有 internal seam(implementation 私有,供自身 test)以及 interface 处的 external seam。不要因为 test 用它们就把 internal seam 暴露到 interface。

Testing strategy: replace, don't layer

  • shallow module 上的旧 unit test,一旦 deepened module interface 处有 test 就成 waste — 删掉。
  • 在 deepened module 的 interface 写新 test。interface 就是 test surface
  • test 断言 interface 上的可观察 outcome,非 internal state。
  • test 应 survive internal refactor — 描述 behavior,非 implementation。若 implementation 变 test 就要变,说明在 test 穿过 interface。