DRY, KISS and YAGNI as Economic Decisions
Purpose
Decide whether duplication should be merged and whether an abstraction should exist,
treating both as investments with costs — not as rules to obey. The failure mode this
skill prevents is symmetrical: merging two similar-looking methods that encode different
business rules, then feeding the resulting helper boolean parameters until every caller
pays for every other caller's requirements.
DRY is about knowledge, not text. Two fragments are duplicates only if they must change
together because they state the same fact about the domain. Code that merely looks the
same but answers to different rules, owners or schedules is coincidence; merging it buys
coupling, not reuse.
Workflow
- Establish the target and authority. Inspect compiler release/toolchains, callers,
tests and change history; identify who owns the rule and its effective version/date.
Examples use Java 17-compatible syntax (records need Java 16+ without preview); on older
targets use existing classes, without an implicit upgrade. If authority or caller behavior
is unknown, document the gap and defer a merge that would silently decide policy.
- Classify the duplicated knowledge, not whole fragments. Enumerate change reasons. If a
subset must change together under one authority, extract that nucleus while leaving
independently varying policy separate. Similar fragments need not be all-shared or all-
incidental. Read references/decision-heuristics.md.
- Price the abstraction that would remove it. Every caller becomes coupled to the
shared code, and to each other through it. Count the parameters — especially booleans —
the merged version needs to serve all callers today. A flag encoding caller identity or
unrelated policy is a warning; an explicit domain policy such as
RoundingMode can be a
legitimate parameter.
- Decide.
- Knowledge duplication → centralize the authoritative rule where release/ownership
constraints permit, with mechanics from java-refactoring; otherwise share specification
and conformance tests while retaining necessary execution points.
- Incidental duplication → leave it.
- An existing abstraction whose callers fight it — flags, mode enums, callers using
half of it — → inline it back into the callers, then re-extract only what is
genuinely shared. Read references/worked-examples.md
when performing either operation.
- Verify. After a merge, rule tests cover meaningful boundaries and consumer tests
confirm each caller selects the right policy/version. Remove caller-identity flags, not
legitimate domain inputs. After an inline, preserve each supported caller's behavior while
removing branches used only by others. Report any policy change separately.
Rules
- Duplication and wrong abstractions have different failure costs; compare impact and rollback
rather than assuming one is always worse. When unsure, keep duplication and reconsider on the third
occurrence, once the copies have demonstrably changed together. The rule of three is a
heuristic, not a law: for money, authorization or regulated rules, establish one authority
early, but verify that contexts actually share the rule and effective version before merging.
Independently owned bounded-context models should remain separate; intentional shared
contracts need explicit governance and compatibility checks.
- A boolean/mode added because "caller A behaves differently" is a wrong-abstraction signal.
Split caller-specific policy; retain parameters that are genuine input to one coherent
operation and are named as domain choices rather than implementation branches.
- Build for the requirement that exists. A type parameter with one instantiation, a config
point never configured differently, or a hook nobody calls is a signal to investigate,
not proof of speculative generality. Check test seams, external users and planned work
before removing it. (Its detection as a
smell lives in java-code-smells.)
- Separate essential from accidental complexity before "simplifying". Code implementing a
genuinely intricate rule is not a KISS violation; indirection the problem does not
require is. Deleting essential complexity moves it into callers or into production
incidents — it does not remove it.
- KISS ranks the designs that meet the requirement; it never justifies missing it.
Deliverable
State the shared knowledge (or independent change reasons), evidence/authority, chosen boundary
and main cost. For changes, list preserved behavior, deliberate policy changes and checks
actually executed. Missing history is uncertainty, not evidence that future flexibility is useful
or useless; keep conclusions proportionate to the available caller and requirement evidence.
Safety and production constraints
- One source of truth does not mean one execution point. Authorisation policy can be centralized
while checks occur at gateway and protected operation; validation can repeat structural
constraints at independently trusted boundaries. Remove duplicated decisions, not defense
in depth.
- Never centralise context-sensitive output encoding or "sanitisation" behind a generic helper.
HTML, SQL, shell, LDAP and log sinks have different grammars; parameterization/contextual
encoding belongs at the sink.
- Shared code creates a release and incident blast radius. Before merging across modules/teams,
define owner, compatibility policy, rollout order and rollback. A shared library that deploys
at different cadences can increase live version skew even while deleting source duplication.
- Performance duplication may be intentional specialization. Merge only after profiles show the
abstraction preserves the required data layout, inlining/vectorization and allocation behavior;
otherwise share tests/specification and allow separate implementations.
References
- Decision heuristics and false positives — tests for
knowledge versus incidental duplication, the wrong-abstraction and speculative-generality
signatures, the cost model, and the cases that look like violations but are correct.
Read before merging or inlining anything.
- Worked examples — one inlining of a wrong abstraction
back into duplicates, one merge of genuine knowledge duplication, each with trade-offs
and verification. Read when performing either operation.
1---2name: java-dry-kiss-yagni3description: The economics of duplication and abstraction in Java: knowledge duplication versus incidental (textual) duplication, what a shared abstraction costs, the wrong-abstraction failure mode, premature abstraction and speculative generality, essential versus accidental complexity. Use when deciding whether two similar pieces of code should be merged, whether a shared helper should be inlined back into its callers, when a utility has grown boolean parameters, or when reviewing code generalised for requirements that do not exist. Does not cover the smell catalogue (java-code-smells) or the mechanics of extracting and inlining (java-refactoring).4---56# DRY, KISS and YAGNI as Economic Decisions78## Purpose910Decide whether duplication should be merged and whether an abstraction should exist,11treating both as investments with costs — not as rules to obey. The failure mode this12skill prevents is symmetrical: merging two similar-looking methods that encode different13business rules, then feeding the resulting helper boolean parameters until every caller14pays for every other caller's requirements.1516DRY is about knowledge, not text. Two fragments are duplicates only if they must change17together because they state the same fact about the domain. Code that merely looks the18same but answers to different rules, owners or schedules is coincidence; merging it buys19coupling, not reuse.2021## Workflow22230. **Establish the target and authority.** Inspect compiler release/toolchains, callers,24 tests and change history; identify who owns the rule and its effective version/date.25 Examples use Java 17-compatible syntax (records need Java 16+ without preview); on older26 targets use existing classes, without an implicit upgrade. If authority or caller behavior27 is unknown, document the gap and defer a merge that would silently decide policy.281. **Classify the duplicated knowledge, not whole fragments.** Enumerate change reasons. If a29 subset must change together under one authority, extract that nucleus while leaving30 independently varying policy separate. Similar fragments need not be all-shared or all-31 incidental. Read [references/decision-heuristics.md](references/decision-heuristics.md).322. **Price the abstraction that would remove it.** Every caller becomes coupled to the33 shared code, and to each other through it. Count the parameters — especially booleans —34 the merged version needs to serve all callers today. A flag encoding caller identity or35 unrelated policy is a warning; an explicit domain policy such as `RoundingMode` can be a36 legitimate parameter.373. **Decide.**38 - Knowledge duplication → centralize the authoritative rule where release/ownership39 constraints permit, with mechanics from java-refactoring; otherwise share specification40 and conformance tests while retaining necessary execution points.41 - Incidental duplication → leave it.42 - An existing abstraction whose callers fight it — flags, mode enums, callers using43 half of it — → inline it back into the callers, then re-extract only what is44 genuinely shared. Read [references/worked-examples.md](references/worked-examples.md)45 when performing either operation.464. **Verify.** After a merge, rule tests cover meaningful boundaries and consumer tests47 confirm each caller selects the right policy/version. Remove caller-identity flags, not48 legitimate domain inputs. After an inline, preserve each supported caller's behavior while49 removing branches used only by others. Report any policy change separately.5051## Rules5253- Duplication and wrong abstractions have different failure costs; compare impact and rollback54 rather than assuming one is always worse. When unsure, keep duplication and reconsider on the third55 occurrence, once the copies have demonstrably changed together. The rule of three is a56 heuristic, not a law: for money, authorization or regulated rules, establish one authority57 early, but verify that contexts actually share the rule and effective version before merging.58 Independently owned bounded-context models should remain separate; intentional shared59 contracts need explicit governance and compatibility checks.60- A boolean/mode added because "caller A behaves differently" is a wrong-abstraction signal.61 Split caller-specific policy; retain parameters that are genuine input to one coherent62 operation and are named as domain choices rather than implementation branches.63- Build for the requirement that exists. A type parameter with one instantiation, a config64 point never configured differently, or a hook nobody calls is a signal to investigate,65 not proof of speculative generality. Check test seams, external users and planned work66 before removing it. (Its detection as a67 smell lives in java-code-smells.)68- Separate essential from accidental complexity before "simplifying". Code implementing a69 genuinely intricate rule is not a KISS violation; indirection the problem does not70 require is. Deleting essential complexity moves it into callers or into production71 incidents — it does not remove it.72- KISS ranks the designs that meet the requirement; it never justifies missing it.7374## Deliverable7576State the shared knowledge (or independent change reasons), evidence/authority, chosen boundary77and main cost. For changes, list preserved behavior, deliberate policy changes and checks78actually executed. Missing history is uncertainty, not evidence that future flexibility is useful79or useless; keep conclusions proportionate to the available caller and requirement evidence.8081## Safety and production constraints8283- One source of truth does not mean one execution point. Authorisation policy can be centralized84 while checks occur at gateway and protected operation; validation can repeat structural85 constraints at independently trusted boundaries. Remove duplicated **decisions**, not defense86 in depth.87- Never centralise context-sensitive output encoding or "sanitisation" behind a generic helper.88 HTML, SQL, shell, LDAP and log sinks have different grammars; parameterization/contextual89 encoding belongs at the sink.90- Shared code creates a release and incident blast radius. Before merging across modules/teams,91 define owner, compatibility policy, rollout order and rollback. A shared library that deploys92 at different cadences can increase live version skew even while deleting source duplication.93- Performance duplication may be intentional specialization. Merge only after profiles show the94 abstraction preserves the required data layout, inlining/vectorization and allocation behavior;95 otherwise share tests/specification and allow separate implementations.9697## References9899- [Decision heuristics and false positives](references/decision-heuristics.md) — tests for100 knowledge versus incidental duplication, the wrong-abstraction and speculative-generality101 signatures, the cost model, and the cases that look like violations but are correct.102 Read before merging or inlining anything.103- [Worked examples](references/worked-examples.md) — one inlining of a wrong abstraction104 back into duplicates, one merge of genuine knowledge duplication, each with trade-offs105 and verification. Read when performing either operation.