design-legacy-seams

v2026.09.24

Get legacy code under test before changing it — find seams, write characterization tests. Use when changing untested code, breaking dependencies, or taming legacy.

GitHub
安装命令
npx skhub add laurigates/design-legacy-seams
Markdown
SKILL.md

Design: Legacy Code Seams

From Working Effectively with Legacy Code (Feathers): legacy code is code without tests. The dilemma is circular — to change it safely you need tests, but to test it you often need to change it. The way out is to find a seam (a place where behaviour can be altered without editing in-line) to break the dependency that makes the code untestable, then pin the current behaviour with characterization tests before you touch logic.

The sequence is non-negotiable: get it under test first, change it second. Characterization tests document what the code does (not what it should do), so a refactor that preserves behaviour stays green and a behaviour change shows up as a deliberate, reviewed diff.

When to Use This Skill

Use this skill when...Use something else instead when...
About to modify untested code and want a safety net firstThe code already has good tests → code-quality-plugin:code-review
A dependency (DB, clock, network, global) blocks unit testingJudging test quality of existing tests → code-quality-plugin:code-test-quality
Pinning current behaviour before a risky refactorA large multi-phase refactor → workflow-orchestration-plugin:workflow-checkpoint-refactor
Breaking a hidden dependency to inject a fakeDiagnosing a live bug → code-quality-plugin:debugging-methodology

Core Principle

Seam typeWhere you alter behaviourTypical use
Object seamOverride a method / inject a collaboratorReplace a real dependency with a fake (most common)
Link seamSwap a library/module at build/link timeStub a third-party at the boundary
Preprocessing seamMacro/build substitution before compileLast resort in C/C++-style builds

Two enabling moves when no seam exists yet: Extract & Override (pull the hard-to-test work into a method, subclass in the test to override it) and Parameterize Constructor/Method (pass the dependency in instead of constructing it inside). Both are tiny, behaviour-preserving, and create the seam the test needs.

Parameters

Parse $ARGUMENTS:

  • Target (optional, first positional) — a file, class, function, or diff to bring under test. If absent, default to the current change (git diff HEAD + staged) and say so.

Execution

Execute this get-under-test workflow:

Step 1: Find the change point and its dependencies

Locate where the behaviour change is needed. List the dependencies that make it hard to test now — constructed-inside collaborators, global/singleton state, clocks, randomness, I/O, network, static calls.

Step 2: Find or create a seam

For each blocking dependency, identify the cheapest seam:

  • An existing object seam (already-injectable collaborator, overridable method)
  • Parameterize Constructor/Method — pass the dependency in
  • Extract & Override — move the hard call into an overridable method
  • A link seam when you genuinely can't touch the callee

Make only the smallest behaviour-preserving edit needed to open the seam. Note that the seam you introduce is often an Adapter (see software-design-plugin:design-patterns).

Step 3: Write characterization tests

Pin the current behaviour, not the intended behaviour:

  1. Write a test that calls the code and asserts whatever it currently returns.
  2. Run it; if it fails, change the assertion to match actual output (you are documenting reality, not judging it).
  3. Add cases until the branches you're about to touch are covered. A surprising characterized behaviour is a finding — flag it, don't silently "fix" it yet.

Step 4: Now change, then report

With the net in place, make the intended change; the characterization tests that should still hold stay green, and the ones that intentionally change become a reviewed diff. Report: the change point, the seam(s) opened (type + the minimal edit), the characterization tests added, and any surprising current behaviour the characterization surfaced.

Anti-patterns

MistakeCorrect approach
Refactoring untested code "carefully" by handGet it under test first — careful is not a safety net
Writing tests for what the code should doCharacterize what it does; behaviour change comes after
A giant rewrite to make one method testableSmallest seam-opening edit (Extract & Override / Parameterize)
"Fixing" a surprising behaviour mid-characterizationFlag it; change behaviour as a separate, deliberate step

Quick Reference

BlockerSeam move
Collaborator constructed insideParameterize Constructor
Hard call buried in a methodExtract & Override
Global / singleton / clockInject via an object seam
Untouchable third-partyLink seam / Adapter at the boundary
Need to pin behaviourCharacterization test asserting current output

Related

  • code-quality-plugin:code-test-quality — once under test, judge whether those tests are good (this skill only gets them present)
  • software-design-plugin:design-patterns — the seam you open is often an Adapter; Extract & Override leans on subclassing
  • workflow-orchestration-plugin:workflow-checkpoint-refactor — the multi-phase refactor this safety net unblocks
  • code-quality-plugin:debugging-methodology — a characterization test is also a reproduction harness for a legacy bug
发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

Sep 24, 2026

分类

未分类

许可证

MIT

源路径

software-design-plugin/skills/design-legacy-seams

默认分支

main

最新提交

1668324

Tree SHA

b2d4cc3