generalize
generalize
Removes hardcoding and overfitting by promoting special cases into data, parameters, and honest abstractions — at the right moment. Detects the smells (magic literals, one-case branches, single-fixture logic, "it works for the example"), generalizes the minimum that kills them, and stops before speculative generality. Use when code works for one input/instance/environment but must handle a family of them, when review says "hardcoded", or when a fix smells overfit to the bug report.
- Category
- Category: implementation
- generalization
- hardcode
- overfitting
- magic-numbers
- parameterize
- data-driven
- rule-of-three
- abstraction
- configuration
- special-case
Install this skill
npx github:yingkitw/skillsheet --remote --tool claude-code --only generalizeFetches the latest published version from skillsheet.dev — the first run installs the package from GitHub, after that npx caches it.
name: generalize description: Removes hardcoding and overfitting by promoting special cases into data, parameters, and honest abstractions — at the right moment. Detects the smells (magic literals, one-case branches, single-fixture logic, "it works for the example"), generalizes the minimum that kills them, and stops before speculative generality. Use when code works for one input/instance/environment but must handle a family of them, when review says "hardcoded", or when a fix smells overfit to the bug report. category: implementation keywords: generalization, hardcode, overfitting, magic-numbers, parameterize, data-driven, rule-of-three, abstraction, configuration, special-case
Generalize
Overfitting is fitting to the example instead of the population — in ML, in code, in fixes. A hardcoded value is a decision frozen in the wrong place; a one-case branch is a rule that pretends it's an exception. Generalizing means moving each frozen decision to where it can vary: a parameter, a table, a token, a loop over data. The discipline is bidirectional — generalize too late and the code is brittle; too early and you ship speculative generality (an abstraction with one user is a tax, not a tool).
When to apply
Use when code is correct for the case in front of you but visibly borrowed from it: literal values from the sample input, a branch shaped exactly like one customer/environment/format, a fix that passes the bug report's repro and nothing else. Also use on request: "this is hardcoded", "don't overfit to this fixture", "make it handle the general case".
Smells that say "overfit"
- Magic literals with provenance —
if (x === 37), a+2pixel fudge, a timeout copied from the repro. The value encodes an observation, not a rule. - One-case branches —
if (env === "staging")where staging is the only env seen so far; the branch enumerates instances instead of expressing the dimension. - Fixture-shaped logic — parsing that only handles the exact structure of the one sample file; a test suite of one happy path; regex anchored to a single example.
- Copied blocks differing by a literal — three near-identical lines where only a value changes; the value is the loop variable you didn't write.
- Fix overfit to the report — the patch makes repro #1 pass by special-casing its exact inputs, leaving the underlying rule unfixed.
- Environment/instance leakage — absolute paths, hardcoded hostnames, IDs, names, dates, locale assumptions, or model names baked into logic.
The generalization ladder
Climb one rung at a time; each rung is cheaper to review than the one above:
- Name it — extract the literal into a named constant where it's used. Zero structure change; kills the magic.
- Parameterize it — hoist the constant to a function argument or module config. The caller decides; the rule stays one shape.
- Make it data — replace instance-branching with a table: map/config/array of records, iterated by one generic path.
if type === "a" … if type === "b" …becomes rows in a map. Most "generalization" ends here, well before an abstract class. - Extract the rule — only when ≥2–3 implementations genuinely differ in behavior (not just values) introduce an interface/strategy. Rule of three: tolerate one duplicate, extract at two, demand a third before anything fancier.
- Stop. — a parameter nobody passes, a plugin point with one plugin, a config key nobody sets — that's speculative generality; delete it (see
engineering-roast's classic hits).
Workflow
- Collect the population — before generalizing, gather ≥2–3 real instances: sample inputs, environments, customers, formats. One instance is an anecdote; generalizing from it is how you launder a guess into a rule. If only one instance exists, say so and generalize only rungs 1–2.
- Find the invariant — across instances, name what stays the same (the shape of the rule) versus what varies (the values, the rows). The invariant becomes the code; the variance becomes data.
- Choose the rung — pick the lowest ladder rung that eliminates the smell. Prefer data over interface, constants over parameters, deleting over either.
- Preserve the concrete case — the original instance must still pass unchanged. Add the new instances as tests in the same change; a generalization with no second example is unproven.
- Prove the family, not just members — where feasible, test the dimension itself (loop over the table, property-test the parameter range) so the next instance arrives as a new row, not a new branch.
- Sweep for siblings — grep for the same literal/smell elsewhere; generalized code with five hardcoded cousins is half a refactor.
- Record the decision — one line in the config/table/README stating what varies and what's intentionally fixed. The next reader should learn the dimension from the code, not archaeology.
Rules
- No generalization from a single instance — name it or parameterize it, but don't abstract it.
- Data beats structure — a table entry is cheaper than a subclass, always.
- Every generalized path keeps a passing concrete case — unproven generality is a guess with types.
- The fix must not be a lookup of the bug report — if the patch only answers repro #1, it's overfit.
- Generality you can't point to a second consumer for is debt — climb down a rung.
Related skills
- lean-implementation — The counterweight: don't generalize past the problem at hand.
- minimal-fix — Fix the rule, not the instance; how to shape the non-overfit patch.
- engineering-roast — Roasts speculative generality, the overcorrection of this skill.
- round-trip-testing — Property-style corpora that test the family instead of members.