What does the Common Reuse Principle (CRP), one of Robert C. Martin's component-cohesion principles, state, and what problem is it meant to prevent?
answer
- Used together → packaged together
- Negative form: don't force unneeded dependencies
- Component = unit of release, all-or-nothing
- Splitter force (REP/CCP are groupers)
- Unused class still means re-release + transitive deps
basics
~10 sCRP says classes that are used together should be packaged together, and classes that are not used together should not be. It keeps consumers from depending on, rebuilding, and redeploying code they never use.
solid answer
~50 sCRP states that the classes in a component are reused together: if you depend on one class in a component, you effectively depend on all of them. Its practical form is negative — "don't force users of a component to depend on things they don't need." A component is a unit of deployment and release (jar, npm package, wheel, DLL), so dependency is all-or-nothing at that granularity. A grab-bag component makes every consumer inherit the whole thing: its transitive dependencies, its security surface, its version constraints, and every one of its releases. Each release forces consumers to re-integrate, re-test, and redeploy even when the change touched code they never call. CRP is therefore a splitting force: carve components along lines of actual joint usage, so what sits inside a component is what a typical consumer needs as a unit.
code
text · 10 lines# CRP violation: one component, three unrelated reasons to depend on it
acme-common 3.4.0
|-- StringUtils (used by ~everyone)
|-- PdfRenderer (used by 2 of 30 consumers) -> pulls pdf-engine, font-lib
\-- KafkaEventBus (used by 4 of 30 consumers) -> pulls kafka-client, avro
# CRP-aligned: split along observed joint usage
acme-text 1.0.0 (no transitive deps)
acme-pdf 1.0.0 (pdf-engine, font-lib)
acme-eventbus 1.0.0 (kafka-client, avro)go deeper
State both forms of the principle and give one concrete grab-bag example (a utils package). Say that a component is the unit of release, so dependency is all-or-nothing.
Add the mechanism: why an uncalled class still costs you (transitive deps, re-test/redeploy on every release, version conflicts, CVEs). Name the ISP analogy explicitly.
Frame CRP as the splitting force in Martin's cohesion triangle, describe how you'd measure "used together" from real consumer data, and name the costs of over-splitting.
Talk about it as platform/library governance: where you place the boundary changes over a product's lifecycle, how you migrate an existing monolithic artifact without breaking consumers (facade artifact, deprecation windows), and how build tooling mitigates but does not remove the coupling.
## Vocabulary first **Component** here means a *unit of deployment and release*: the smallest thing you can independently version, publish and consume — a jar, a NuGet/npm package, a Python wheel, a Go module, a shared library, a Maven artifact. It is not "a class" and not merely "a folder" or "a namespace". A namespace is a *naming* device; a component is a *shipping* device. That distinction is the entire point of CRP, because you can only take a dependency on a whole component — you cannot depend on 3 of its 40 classes. **Cohesion** means "the degree to which the things inside a unit belong together". At class level we ask whether methods belong in one class; CRP asks the same question one level up: which classes belong in one *shipped* unit. ## The principle, stated two ways 1. **Positive form:** *The classes in a component are reused together. If you reuse one of the classes in a component, you reuse them all.* 2. **Negative (operational) form:** *Don't force users of a component to depend on things they don't need.* The negative form is the one you actually apply. Positive-form statements tell you what may be grouped; the negative form tells you what must be **split apart**. CRP is a *splitter* — it pushes component boundaries to be smaller and more numerous. (Its siblings REP and CCP are *grouper* forces that push components to be larger; see the tension-triangle question.) ## Why does an unused class cost anything? The naive objection is "if I never call that class, it costs me nothing." It costs plenty, because a dependency is not just a call: - **Release coupling.** When the component publishes 2.4.0 because an unrelated subsystem changed, *you* are the one who must decide whether to upgrade, re-run your test suite, re-certify, and redeploy. Even choosing to stay on 2.3.0 is a cost: you now diverge, and later security patches will land on top of changes you didn't want. - **Transitive dependency load.** The unused half of the component drags in *its* dependencies. Pull in a component for its JSON reader and you may also inherit an XML parser, a logging framework, and a reflection library. - **Version-conflict surface.** Those transitive dependencies collide with other components' choices (the "diamond"/version-skew problem). More unused code means more chances of an unresolvable constraint. - **Security and compliance surface.** A CVE in the unused subsystem is still a CVE in your dependency tree; scanners flag it, auditors ask about it, and you must upgrade or justify. - **Build and artifact size.** Longer compiles, bigger images/bundles, slower cold starts, more classes to load. - **Cognitive and API surface.** Every consumer's IDE autocomplete, documentation and mental model now include things irrelevant to them. So "depending on" ≠ "calling". Depending on a component means *being coupled to its release cycle and its dependency closure*. ## How you decide what is "used together" The empirical test is: look at the actual consumers. If most consumers use classes {A,B,C} and a disjoint set of consumers uses {X,Y,Z}, and almost no one uses both, the component is violating CRP and should become two components. If instead nearly every consumer that touches A also touches B and C, they belong together. A useful structural heuristic: classes bound by strong static relationships — inheritance, an interface and its main implementations, a class and its parameter/return types, a builder and the thing it builds — are almost always used together, so they should live in the same component. Splitting *those* apart is the opposite failure: it forces every consumer to assemble a jigsaw of packages before anything works. ## Classic violations - A `common`/`utils`/`shared` component that accumulates string helpers, date math, an HTTP client, a retry policy, and a feature-flag reader. Nobody uses all of it; everybody depends on all of it. - A monolithic cloud SDK where using the object-store client makes you carry clients for forty other services. (Vendors eventually split these into per-service artifacts precisely because of CRP pressure.) - A "kitchen-sink" framework artifact instead of core/web/data/test artifacts. ## Classic over-corrections CRP taken to the extreme yields dozens of one-class packages: an exploded dependency graph, version-lockstep pain, publishing overhead, and discoverability problems. CRP is one force among three, not an absolute. ## How to phrase it in an interview "CRP is ISP at component granularity. ISP says don't make a client depend on methods it doesn't use; CRP says don't make a client depend on *classes* it doesn't use — and the reason is that a component is the unit of release, so an unused class still couples you to its releases, its transitive dependencies and its vulnerability surface."
- If a class is never called at runtime, why does depending on it still cost me anything?Because a dependency couples you to the component's release cycle and dependency closure, not just its call graph: you inherit its transitive dependencies, its version constraints, its CVEs, its artifact size, and the obligation to re-integrate and re-test on every release, including releases driven entirely by code you never call.
- Is CRP about source folders or about published artifacts?Published artifacts — components are units of release. Reorganising folders inside one jar/package satisfies nothing, because consumers still take the whole artifact. Folder structure only matters as a precursor to an actual publishing split.
- What is the opposite failure mode if you apply CRP too aggressively?Package explosion: dozens of micro-components, a wide and brittle dependency graph, version lockstep between artifacts that must be released together anyway, publishing/CI overhead, and poor discoverability. CRP must be balanced against REP and CCP.
Buying a cable-TV bundle. You wanted one sports channel, but the package includes ninety others: you pay for them, the price changes when any of them renegotiates, and when the provider reshuffles the lineup you have to re-evaluate your whole subscription. CRP is insisting the provider sell channels in bundles people actually watch together.
saying these in an interview costs you the question
- "Unused code is free — I just don't import it." Ignores release coupling, transitive dependencies and CVE surface.
- Confusing CRP with CCP: CRP is about classes *used* together (consumer's view); CCP is about classes that *change* together (maintainer's view).
- Treating CRP as a rule about namespaces/folders rather than about units of release.
- Claiming CRP means "one class per package" — that is an over-application, not the principle.
- Stating only the positive form ("used together, packaged together") and never the operational negative form, then being unable to say what to split.