What is the Common Reuse Principle (CRP), what real cost does an unused dependency inside a component impose on its consumers, and how is CRP related to the Interface Segregation Principle?
answer
- CRP = ISP for components
- Depend on B → depend on ALL of B
- Unused class still forces rebuild/redeploy
- `common`/`utils` grab-bag = archetypal violation
- Over-split → dependency hell (REP suffers)
basics
~20 sCRP says don't force users of a component to depend on things they don't need. Classes used together belong together; classes used separately belong apart. It is the Interface Segregation Principle applied to whole components.
solid answer
~60 sCRP: *classes that tend to be reused together belong in the same component; those reused independently should be split out.* Its stronger, more useful reading is about exclusion — **keep out** of a component anything its consumers don't need. The cost: when component A depends on component B, A depends on **all** of B. If a class in B that A never calls changes, B gets a new version, and A's owners must at minimum revalidate, recompile, retest and redeploy A — a rebuild/redeploy triggered by a change that is functionally irrelevant to them. Beyond that: fatter artifacts, extra transitive third-party dependencies, more version-conflict/diamond risk, longer builds, and a larger attack and licensing surface from code that never executes. CRP is ISP at component scale: ISP says don't make a client depend on interface methods it doesn't use; CRP says don't make a consumer depend on component classes it doesn't use. Both are summarised as *"don't depend on things you don't need."* The classic offender is a catch-all `common`/`utils` component that every consumer must take whole.
code
text · 12 linesBEFORE (CRP violation)
common-utils v1.9 ──> used by billing, search, mailer, admin
strings/, dates/, retry/, dto/, errors/
Any edit to strings/ ⇒ common-utils v1.10 ⇒ ALL four consumers
must revalidate, rebuild and redeploy.
AFTER (CRP applied — split by usage cluster)
text-utils ──> admin
time-utils ──> billing, search
retry ──> mailer
error-model ──> billing, mailer
Edit to text-utils now touches only admin.go deeper
State it as 'don't make people depend on code they don't use' and note that you depend on the entire component, not just the class you call.
Add the ISP mapping and enumerate the real costs: forced rebuild/retest/redeploy, transitive dependencies, version conflicts, binary and audit surface.
Discuss the common-module anti-pattern, splitting by observed usage clusters, and the direct conflict with CCP and REP including when over-splitting produces dependency hell.
Frame it as consumer-coupling governance: dependency-graph policy, ownership of shared artifacts, versioning/compat strategy, and when duplicating a small piece of code is cheaper than the shared-component coupling it removes.
## Statement > **Common Reuse Principle (CRP):** *Don't force users of a component to depend on things they don't need.* Equivalently: *classes and modules that tend to be reused together belong in the same component; classes that are not reused together should not be in the same component.* --- ## Why an unused class still costs you The insight that makes CRP non-obvious: **a dependency is not free just because you never call the code.** When consumer A depends on component B: 1. **A depends on all of B, not on the parts it uses.** Dependency is at artifact granularity. 2. Any change *anywhere* in B produces a **new version of B**. 3. A must then decide: stay on the old version (accumulating drift, missing security fixes) or upgrade. 4. Upgrading means **recompile, re-run tests, re-validate, re-release and redeploy A** — even though the changed class is one A never touches. In regulated or safety-critical settings that re-validation can be very expensive. 5. **Transitive drag**: B's own dependencies come along. If the unused half of B pulls in a heavyweight HTTP client or an ORM, A now ships and must patch that too. 6. **Version-conflict/diamond risk**: more edges in the graph means more chances that two paths demand incompatible versions of the same library. 7. **Bloat and surface**: larger binaries/images, longer cold starts, more code to audit for CVEs and licences, more to scan and sign. So the *rebuild-and-redeploy tax on functionally-unaffected consumers* is the concrete harm CRP prevents. CRP is therefore stated in the **negative** — it tells you what to keep out. --- ## The classic violation: the `common` / `utils` / `shared` component A shared grab-bag typically contains string helpers, date helpers, a retry policy, DTOs, an error taxonomy, and some domain constants. Nothing is *reused together*; every consumer uses a different slice. Consequences: - every service depends on the whole grab-bag; - every change to any helper re-releases it; - every consumer is nudged to upgrade and redeploy; - the graph gets an edge from everything to one volatile node, which also concentrates coupling and often produces cycles. CRP's prescription: split it along *usage clusters* — e.g. `retry`, `time`, `error-model` — each released independently, so a change in one doesn't ripple to consumers of the others. --- ## Relation to ISP **ISP (class/interface level):** *no client should be forced to depend on methods it does not use* — split fat interfaces into role interfaces. **CRP (component level):** *no consumer should be forced to depend on classes it does not use* — split fat components. Same principle, one granularity up. Martin summarises both as **"don't depend on things you don't need."** The mechanism is identical: an unused member of the thing you depend on still transmits change to you. --- ## Tension and how far to push it - **CRP vs CCP:** CCP pulls change-mates in (bigger components), CRP throws non-co-used classes out (smaller components). These can point at the same class in opposite directions: two classes that always change together but are used by disjoint consumers. You must pick, and the right pick depends on whether developability or consumer independence matters more right now. - **CRP vs REP:** shredding a library into dozens of micro-packages honours CRP but can wreck REP — consumers now have to coordinate a dozen version numbers, and "which versions are compatible?" becomes their problem. Over-application produces dependency-hell / package-explosion (a real pathology in fine-grained package ecosystems). - **Practical test:** examine actual consumer usage. If no consumer imports classes X and Y from the same component, that component is a CRP candidate for splitting. If every consumer imports both, leave them together. - **Mitigations short of splitting:** optional/peer dependencies, facade modules, tree-shaking or dead-code elimination (reduces binary bloat but *not* the re-release/re-validate tax), and stable-API discipline so an unused-part change doesn't force a major version. --- ## Edge cases - **Vendored/duplicated code** can beat a shared component when the shared thing is tiny and the coupling cost is high — a deliberate CRP-motivated trade of DRY for independence. - **Semantic versioning helps but does not remove the cost**: a patch bump still means the consumer must decide, test and redeploy. - **Monorepos blur it**: if everything builds from source together, the version-skew part of the cost drops, but the build/test-invalidation and blast-radius part remains.
- CRP pushes toward smaller components. What goes wrong if you take it to the extreme?You get package explosion: dozens of tiny artifacts each with its own version, and consumers must solve a combinatorial compatibility puzzle. That violates REP — the release story stops being coherent — and typically produces diamond conflicts and unmanageable upgrade churn. The triangle exists precisely to stop you maximising one corner.
- Does tree-shaking or dead-code elimination solve the CRP problem?Only partly. It removes unused code from the final binary, addressing bloat and some attack surface, but it does not remove the dependency edge: a new version of the component still obliges the consumer to evaluate, retest and redeploy, and transitive version constraints still apply.
- A class is always changed together with class B (CCP says group them) but no consumer ever uses both (CRP says split them). How do you decide?Decide by which cost currently dominates. If the consumers are external or independently deployed teams, honour CRP and split, accepting coordinated releases. If it's one team, one deployment and reuse is internal only, honour CCP and keep them together. Record it as a revisitable decision, since the balance shifts as the system matures.
Subscribing to a bundled cable package: you only watch two channels, but every time the shopping channel reshuffles its schedule your whole subscription changes and you have to re-read the terms. CRP says sell the channels separately.
saying these in an interview costs you the question
- Believing an unused class costs nothing because it is never executed
- Confusing CRP with CCP — 'used together' vs 'changed together'
- Justifying a catch-all `shared`/`common` module as good reuse
- Claiming CRP means 'make every class its own package'
- Saying semantic versioning eliminates the consumer-side cost of an irrelevant change