Skip to content

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 generalize

Fetches 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 +2 pixel 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:

  1. Name it — extract the literal into a named constant where it's used. Zero structure change; kills the magic.
  2. Parameterize it — hoist the constant to a function argument or module config. The caller decides; the rule stays one shape.
  3. 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.
  4. 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.
  5. 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

  1. 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.
  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.
  3. Choose the rung — pick the lowest ladder rung that eliminates the smell. Prefer data over interface, constants over parameters, deleting over either.
  4. 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.
  5. 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.
  6. Sweep for siblings — grep for the same literal/smell elsewhere; generalized code with five hardcoded cousins is half a refactor.
  7. 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.