.opencode/skills/duplication-vs-abstraction/SKILL.md
Use when deciding whether to extract shared abstractions across similar-but-independent types, code-generated clients, or parallel implementations serving different external systems
npx skillsauth add recyclarr/recyclarr duplication-vs-abstractionInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Decision framework for when code that looks the same should stay duplicated versus being unified behind a shared abstraction. Applies especially to code-generated types, multi-service adapters, and parallel implementations with shared ancestry.
DRY is about knowledge, not syntax. Hunt/Thomas (The Pragmatic Programmer) explicitly state the principle targets knowledge duplication. Dave Thomas (2003 Artima interview): "Most people take DRY to mean you shouldn't duplicate code. That's not the point." Two code paths that look identical but represent different domain concepts are incidentally similar, not duplicated knowledge.
Rule of Three. Two instances do not warrant abstraction (Fowler/Beck, Refactoring). Three instances provide enough data points to identify what varies and what stays the same. Abstract at three, not two.
Wrong abstraction > duplication. Sandi Metz: "Duplication is far cheaper than the wrong abstraction." The trajectory of a premature abstraction: extract, bend for new case, add conditional, repeat until incomprehensible. Existing abstractions resist removal due to sunk-cost inertia.
Ask these questions in order. Stop at the first "no."
Is this essential duplication? Would fixing a bug in one copy always require the same fix in the other? If they could legitimately diverge, it is incidental duplication. Keep it duplicated.
Do you have three or more instances? Two instances give insufficient signal about the correct abstraction shape. Wait for the third.
Is the abstraction's cost lower than the duplication's cost? Count: generic type parameters, indirection layers, cognitive load for new readers, coupling surface. If the abstraction requires CRTP, 4+ type parameters, or forces callers to understand complex generic constraints, the cost likely exceeds the benefit.
Are the types under your control? Abstracting across code-generated types, external DTOs, or types from different bounded contexts creates fragile coupling to things you do not own. Prefer abstracting over your own domain types.
When multiple API clients produce near-identical types (OpenAPI codegen, gRPC, GraphQL):
DDD practitioners (Evans) prescribe one ACL per external bounded context. Sharing adapter implementation across different external systems blurs the isolation boundary. Even when two external APIs look identical today, they represent different domain agreements that evolve independently.
Per Metz and Abramov: inline first, re-extract later.
testing
Use when creating or editing ADRs or PDRs in docs/decisions/
testing
Use when updating CHANGELOG.md for a release or user-facing change
tools
Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layers like Lobster, ACPX, plugins, or plain code. Keep conditional logic in the caller; use TaskFlow for flow identity, child-task linkage, waiting state, revision-checked mutations, and user-facing emergence.
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------