Feature Envy¶
Flag a method whose cross-class field and method accesses to another class outnumber accesses to its own, signalling that the behavior lives at the wrong address and should be relocated to the class whose data it operates on.
Core Idea¶
Feature envy is the object-oriented code smell in which a method on one class is more interested in another class's data than in its own: its body is dominated by repeated accesses to the other class's fields and methods, it does its substantive work by orchestrating the other class's interior, and its coupling to the other class's interface exceeds its coupling to its own class's interface. The method is in the wrong place. Named by Martin Fowler in Refactoring (1999), the smell names a locally detectable symptom of responsibility misplacement that manifests not as a runtime error but as a maintenance tax — every change to the data owner's interior propagates into the envious method, even though that method is nominally the other class's concern.
The structural signature has four components: a data-owner class with internal state and behavior; an outsider method whose body is dominated by accesses against that state; a locality mismatch in which the behavior resides at one class's address but the data it operates on resides at another's; and a change-propagation signal in which modifications to the data owner disproportionately force changes to the envious method. The standard diagnostic is fan-out: if a method's cross-class field and method accesses outnumber its in-class accesses by a wide margin, the method belongs on the class it is envying. The standard remediation is Move Method — relocate the envious method, or the envious portion of it, to the data owner — after which the coupling becomes internal and the change-propagation path closes.
The smell commonly manifests in anemic domain models (business logic accumulates in service classes that reach into passive data classes), god controllers in MVC designs, and utility classes whose helper methods take domain objects and extract their internals rather than delegating back to them. The connecting principle across these cases is the Tell-Don't-Ask rule (Pragmatic Programmer) and the Law of Demeter: a method should tell an object to do work, not ask for its internals in order to do the work externally.
Structural Signature¶
Sig role-phrases:
- the data-owner class — class B, holding the internal state and behavior the envious method actually operates on
- the outsider method — a method nominally on class A whose body is dominated by repeated accesses into B's fields and methods
- the locality mismatch — the behavior resides at A's address but the data it works on resides at B's: the method is in the wrong place
- the access fan-out — the countable diagnostic: cross-class field/method accesses outnumbering in-class accesses by a wide margin, surfaceable by a static analyzer
- the correctness-vs-placement split — the working, compiling method produces right output while living at the wrong address, so the defect is no bug and no test catches it
- the change-propagation tax — the cost paid over time, not over runs: every change to B's interior drags the envious method along, though it is nominally A's concern
- the Move-Method remedy — relocate the method (or extract its envious portion and move that) onto B, after which the coupling becomes internal and the propagation path closes
- the cohesion-axis placement — one named violation of behavior-data co-location, sibling to god class and shotgun surgery, valued precisely for being local, countable, and refactoring-addressable
What It Is Not¶
- Not a bug. Feature envy produces no wrong output and fails no test; the code is correct and compiles. It is a maintainability defect — a tax paid over time, not a malfunction over runs. Hunting for it among test failures or runtime errors is futile, because correctness and responsibility placement are different axes: a method can be exactly right in its output and exactly wrong in its address.
- Not about social envy. The name's echo with interpersonal "envy" is a false friend. The smell is purely a matter of code-organization locality — a method operating mostly on another class's state — with no psychological or emotional content. Embedding-neighbor hits in economics and social psychology share only the word, not the mechanism.
- Not general coupling. Coupling names the dimension — mutual dependency between modules — but says nothing about which method belongs where. Feature envy is one named, locally countable shape on that dimension: a method whose cross-class access fan-out dominates its in-class fan-out. It is a specific pattern with a specific refactoring, not the broad property of inter-module dependency.
- Not a god class. A god class is one class owning too much responsibility. Feature envy is the inverse locality: one method envying another class's state, nominally living on class A while doing its work inside class B. They are sibling violations on the same cohesion axis but point in opposite directions — too much concentrated here versus behavior placed at the wrong address.
- Not
locality_of_reference. Despite the shared "locality," this is a design-time placement concern, not the runtime memory-hierarchy access pattern that prime names. The two rhyme on one abstract insight — work should happen near the data it touches — but applied at entirely different layers; conflating them mistakes a code-organization smell for a cache-behavior phenomenon. - Not any cross-class access. A method touching another class's fields is not by itself envious; some cross-class access is normal and necessary. The smell is specifically when cross-class accesses outnumber in-class accesses by a wide margin — the method does its substantive work orchestrating another class's interior. The diagnostic is the lopsided fan-out, not the mere presence of a boundary-crossing call.
Scope of Application¶
Feature envy lives across object-oriented software design and maintainability — wherever responsibility is allocated across classes and a method can end up at the wrong address; its reach is within that domain. The cross-domain "misplaced responsibility" cousins (org design, legal drafting) require re-abstraction up to the parent cohesion, not a literal feature-envy habitat here, and the Move-Method/fan-out apparatus does not travel to them.
- Anemic domain models — business logic pooled in service or helper classes that reach into a passive data class's fields, leaving the data class a bag of getters and setters.
- God controllers in MVC — a controller method reaching deep into model state to construct its view, where the logic belongs on the model.
- Manager-class accretion — an
OrderManagerreaching intoOrder's fields to compute totals, discounts, and tax thatOrdershould own. - Utility-class drift — helper methods in a
Utilsclass that take a domain object and pick at its internals rather than delegating back to it. - Cross-service reads in microservices — one service making many small reads against another's state to compute what the second could compute and return in one call (the same smell across process boundaries).
- Test-double over-stubbing — a test that must stub many of class B's methods to exercise a method on class A, the stub count itself reading the envy off the same fan-out.
Clarity¶
Naming feature envy converts a reviewer's vague unease — "this method works and the classes compile, but something about the design is off" — into a locally detectable diagnosis with a specific remedy attached: this method is envious of class B, and the move is to relocate it to B. That is the smell catalogue's characteristic gift, and it matters because the defect is otherwise hard to point at. There is no runtime error to catch and no test that fails; the code is correct. What the label makes legible is that correctness and good responsibility placement are different axes — the method can produce right output while living at the wrong address — so the problem registers not as a bug but as a maintenance tax, paid every time the data owner's interior evolves and drags the envious method along with it. The name turns an aesthetic reaction into a named pattern with a named refactoring, Move Method, pointed at a definite target.
The concept's sharper work is to give coupling a direction and a locality that the bare notion lacks. Coupling names the dimension — mutual dependency between modules — but says nothing about which method belongs where; feature envy adds the crisp claim that when a method's field- and method-accesses to another class outnumber the accesses to its own by a wide margin, the behavior is simply misplaced, and that margin is a countable fan-out a static analyzer can surface. Holding "the behavior's address" distinct from "the data's address" lets a designer ask the sharp question the working code hides: not is this method correct, but does it operate mostly on its own class's state or on another's — and if the latter, the locality mismatch names both the diagnosis and the fix. It also sets the boundary against the cohesion principle it instantiates: feature envy is one named violation of behavior-data co-location, a sibling of god class and shotgun surgery on the same axis, which is exactly why it is a recognizable smell rather than the general principle — the value of the term is that it is local, detectable, and refactoring-addressable, not that it is fundamental.
Manages Complexity¶
Assessing whether responsibilities are well placed across a large codebase is, in the raw, a whole-graph problem: in principle a reviewer would have to reconstruct the module dependency graph, trace which behavior touches which state across every class, and judge the global allocation before deciding any one method is mislocated. Feature envy collapses that global judgment to a local one. The smell makes responsibility misplacement detectable from a single method's body, without first reconstructing the surrounding design, and it does so by reducing the diagnosis to one countable scalar — the method's fan-out to another class's fields and methods versus its fan-out to its own. When the cross-class accesses outnumber the in-class accesses by a wide margin, the method is misplaced; the analyst reads that verdict off the access count rather than re-deriving it from the architecture. This is the code-smell catalogue's characteristic compression operating on one axis: a named pattern paired with a named refactoring (Move Method), so recognition and remedy arrive together and a static analyzer can surface candidates mechanically by counting boundary-crossing accesses per method. The compression's load-bearing split is correctness versus responsibility placement — two axes the working, compiling code fuses. Holding the behavior's address distinct from the data's address lets the reviewer track a single quantity, the locality mismatch, and read the cost off it directly: the defect registers not as a bug (there is no failing test, no wrong output) but as a maintenance tax paid each time the data owner's interior evolves and drags the envious method along. So the reviewer reasons about a small set of locally measurable signals — the access fan-out, the locality mismatch, the change-propagation coupling — instead of re-deriving the design's responsibility allocation from scratch, and the qualitative outcome (is this method in the wrong place, and where does it go?) follows from the fan-out alone. Situating the smell as one named violation of behavior-data co-location, a sibling of god class and shotgun surgery on the cohesion axis, keeps the branch structure tidy: each violation has its own local indicator and its own refactoring, so the maintainer applies a small, fixed repertoire of recognize-and-relocate moves across the codebase rather than a fresh architectural analysis per method. The value is precisely that the term is local, countable, and refactoring-addressable — a general cohesion principle compressed into a fan-out threshold a tool can flag and a single move can fix.
Abstract Reasoning¶
Feature envy licenses a recognize-locate-relocate chain of reasoning, all of it readable from a single method's body without reconstructing the surrounding design. The diagnostic move is a counting move: the analyst tallies the method's access fan-out — how many of another class's fields and methods it touches — against its in-class fan-out, and when the cross-class accesses outnumber the in-class ones by a wide margin, infers that the behavior is misplaced. This is a local inference about a global property: the method's address (the class it lives on) is wrong relative to the data's address (the class it operates on), and the analyst reads that locality mismatch off the count rather than re-deriving it from the architecture. Because the diagnostic is a countable scalar, it is mechanizable — a static analyzer surfaces candidates by counting boundary-crossing accesses per method — and the reasoning extends to indirect signals that co-vary with the fan-out: a test that must stub many of class B's methods to exercise a method on class A is reading the same envy off the stub count, and a Demeter-violating access chain into another object's interior is the same misplacement showing through a different surface.
The most distinctive move is a boundary-drawing one that separates two axes the working, compiling code fuses: correctness and responsibility placement. The analyst reasons that a method can produce exactly the right output while living at exactly the wrong address, so the defect is explicitly not a bug — there is no failing test, no wrong result — and looking for it among test failures or runtime errors is reasoned to be futile. Instead the cost is recast as a maintenance tax: the analyst predicts that because the envious method's coupling runs to the data owner's interior, every change to that interior will propagate into the method, dragging it along even though it is nominally another class's concern. This is predictive reasoning over time rather than over runs — the failure is read as a future stream of forced edits, located by the change-propagation coupling, not as a present malfunction.
The interventionist move reasons forward from the locality mismatch to a definite refactoring with a predicted effect. Because the data the method actually uses lives on class B, the move is to relocate the method (or extract its envious portion and move that) to B, and the prediction is specific: after the move the coupling becomes internal and the change-propagation path closes, so the maintenance tax the diagnosis identified is paid off rather than carried. The recognition and the remedy arrive together — the smell name binds to the Move Method refactoring — so naming the pattern is already half of fixing it. The boundary-drawing move also fixes the concept's scope against its siblings on the same cohesion axis: feature envy is one named violation of behavior-data co-location, distinct from a god class (one class owning too much) and from shotgun surgery (one change scattered across many classes), and reasoning from this placement the analyst applies a small fixed repertoire of recognize-and-relocate moves — each violation paired with its own local indicator and its own refactoring — across the codebase, rather than mounting a fresh architectural analysis per method. The recurring habit the concept installs is to ask of any method not "is it correct?" but "does it operate mostly on its own class's state or on another's?" — and to let the answer name both the diagnosis and the destination.
Knowledge Transfer¶
Within software design the smell transfers as mechanism, because the four-part signature — a data-owner class, an outsider method dominated by accesses against the owner's state, a behavior-address-versus-data-address locality mismatch, and a change-propagation coupling — and its recognize-locate-relocate chain apply wherever object-oriented responsibility is allocated. The countable diagnostic (cross-class access fan-out versus in-class fan-out), the correctness-versus-responsibility-placement split, and the bound remedy (Move Method, or Extract then Move) all carry intact across anemic domain models (logic pooled in services that reach into passive data classes), god controllers in MVC, manager-class accretion (an OrderManager computing what Order should own), utility-class drift, and even across process boundaries in microservices (one service making many small reads against another's state that the second could compute and return in one call). The supporting principles travel with it: Fowler's smell-plus-refactoring unit, Tell-Don't-Ask (an outsider asking for state and computing externally instead of telling the owner to do the work), and the Law of Demeter (envy frequently co-occurring with a.getB().getC().getD() access chains). Even the test-double signal — needing to stub many of class B's methods to exercise a method on class A — reads the same envy off the stub count. Within the domain this is mechanism transfer, not analogy.
Beyond software the honest report is (B) shared abstract mechanism, with the candidate's own reach overstated. The genuinely portable structure is the parent principle the smell instantiates: cohesion — behavior should live with the data (or authority, or capability) it operates on, and a design pays in change-propagation cost when it does not. That principle does recur across domains, but mapping "feature envy" onto organisational design, legal drafting, pedagogy, or infrastructure planning requires substantial re-abstraction up to cohesion in each case (a team repeatedly reaching into another team's information to do work that team should own; a contract clause operating on definitions housed elsewhere) — which is the tell that what travels is the parent, not the named smell. So the cross-domain lesson should be carried by cohesion / responsibility-data alignment, of which feature envy is one named software violation, a sibling of god class, shotgun surgery, divergent change, and data clumps on the same axis; promoting any one of those would multiply rather than consolidate. Stripped of object-oriented vocabulary, feature envy collapses to exactly "behavior is at a different address than the data it operates on, and the design pays for the misalignment in change-propagation cost" — which is the cohesion principle.
What stays home-bound is the entry's OO cargo: the Move Method / Extract Method refactorings, the cross-class-versus-in-class fan-out metric and the static analyzer that counts it, the class-and-method-boundary model, and the Tell-Don't-Ask / Demeter idiom. These presuppose classes with methods and fields, and they do not survive extraction to an org chart or a statute. So invoking "feature envy" for a misplaced organisational responsibility is (A) analogy in naming — the cohesion mechanism really is shared, but the code-smell apparatus does not come along. Two boundaries keep this exact: feature envy is a design locality, distinct from the existing locality_of_reference prime, which is a runtime memory-hierarchy pattern — though the two share one abstract insight (work should be done near the data it touches) applied at different layers, a genuine cross-layer rhyme worth noting; and it is distinct from coupling (the general dependency dimension; feature envy is one named, locally-countable shape on it) and from impedance_mismatch_and_coupling_efficiency (cross-paradigm friction; feature envy is within one paradigm). Finally, the name's verbal echo with social "envy" is a false friend — the embedding-neighbor hits in economics and social psychology share only the word, not the mechanism. When the lesson generalizes, carry cohesion; "feature envy" is its object-oriented named violation. See Structural Core vs. Domain Accent for why this profile places the entry as a domain-specific instance of cohesion rather than a prime.
Examples¶
Canonical¶
Fowler's Refactoring (1999) gives the textbook shape. Suppose a PriceCalculator class carries a method getTotalPrice(Order order) whose body reads order.getBasePrice(), order.getQuantity(), order.getDiscountRate(), and order.getTaxRegion(), multiplies and sums them, and returns the result. Count the accesses: four go to Order's interface and essentially none to PriceCalculator's own state. The method is more interested in Order than in the class it lives on — feature envy. The prescribed remedy is Move Method: relocate getTotalPrice onto Order itself (as order.getTotalPrice()), where the base price, quantity, discount, and tax now sit right beside the logic that uses them. After the move the four boundary-crossing accesses become internal field references, and any future change to how Order stores its price data no longer forces an edit to a distant calculator.
Mapped back: Order is the data-owner class; getTotalPrice on PriceCalculator is the outsider method, and its four-to-zero access count is the access fan-out diagnostic. Behavior living on PriceCalculator while its data lives on Order is the locality mismatch. Relocating it is the Move-Method remedy, after which the coupling turns internal and the change-propagation tax is paid off rather than carried.
Applied / In Practice¶
The smell is endemic to the "anemic domain model" style common in enterprise Java and similar layered frameworks. There, entity classes (Customer, Invoice, Account) are reduced to bags of getters and setters holding data, while all behavior accumulates in service classes (CustomerService, InvoiceService) whose methods reach into those entities' fields to do the actual work. Martin Fowler explicitly labels this an anti-pattern precisely because the services are riddled with feature envy: InvoiceService.calculateBalance(invoice) orchestrates Invoice's internals from the outside. The maintenance cost is real and recurring — a change to how Invoice represents its line items propagates out into every service method that reaches into it — and the standard corrective is to push behavior back onto the entities so that each object owns the logic over its own data (Tell-Don't-Ask).
Mapped back: The passive entity is the data-owner class; the service method dominated by its accesses is the outsider method exhibiting the locality mismatch. Nothing here is a bug — the balances compute correctly — which is the correctness-vs-placement split. The cost surfaces only as the change-propagation tax whenever an entity's internals evolve, exactly the future stream of forced edits the diagnosis predicts.
Structural Tensions¶
T1: Correctness versus responsibility placement (an invisible defect competing for attention with real bugs). Feature envy's defining insight is that a method can produce exactly the right output while living at exactly the wrong address, so the defect is no bug — no test fails, nothing malfunctions. That is what makes the smell worth naming, but it is also what makes it perpetually deprioritizable: a defect with no runtime signal competes for scarce attention against failures that do, and the maintenance tax it names is a future stream of forced edits that may never be paid if the data owner's interior never evolves. The tension is that the cost is real but deferred and invisible, so fixing feature envy means spending present effort against a hypothetical future burden while correctness-affecting bugs sit in the same queue. Diagnostic: Is the envious method's data owner actually going to churn — making the propagation tax real — or is this a placement purist's refactor competing with defects that affect behavior now?
T2: A local countable diagnostic versus the global judgment it proxies (legitimate orchestrators flagged as envious). The fan-out count — cross-class accesses outnumbering in-class accesses by a wide margin — is a local, mechanizable signal that lets a static analyzer surface candidates without reconstructing the design. But "this method belongs on class B" is a global claim about correct responsibility allocation, and the local count only proxies it. Some methods legitimately orchestrate several classes' data by design — coordinators, mappers, assemblers, report builders — and score high cross-class fan-out precisely because coordination is their correct job. The tension is that the cheap countable diagnostic that makes the smell tool-detectable also generates false positives on exactly the methods whose high fan-out is warranted, so the count recognizes a shape without knowing whether the shape is misplaced. Diagnostic: Is this method's high cross-class fan-out a symptom of misplaced behavior, or the signature of a legitimate coordinator whose job is to operate across classes it does not own?
T3: Local recognize-and-relocate versus global re-decomposition (Move Method can shift the envy or entrench bad boundaries). The smell's value is that recognition and remedy arrive together: name the envy, apply Move Method, and the coupling turns internal. But the local fix has two failure modes it cannot see from the method alone. Relocating the method to class B can simply make B envious of A (if the method also leaned on A's state), moving the misplacement rather than resolving it; and repeatedly relocating methods one at a time can entrench a class decomposition that is itself wrong, treating a symptom of bad boundaries as a series of independent placement errors. The tension is that a locally-scoped, tool-flaggable move buys cheap fixes at the risk of never asking whether the whole responsibility allocation — not this one method — is the actual defect. Diagnostic: Does moving this method onto its data owner close the propagation path cleanly, or does it merely relocate the envy (or paper over class boundaries that are themselves drawn wrong)?
T4: Behavior-with-data discipline versus paradigm-bound legitimacy (Tell-Don't-Ask is not universal). Feature envy rests on the rich-domain-model philosophy — behavior should live with the data it operates on, per Tell-Don't-Ask and the Law of Demeter — and from that stance the anemic domain model is an anti-pattern. But that philosophy is paradigm-bound. Data-oriented design, functional programming, DTOs across service boundaries, the visitor pattern, and CQRS deliberately separate behavior from data as a virtue, and in those styles a method operating primarily on another structure's fields is idiomatic, not envious. The tension is that the smell's diagnosis presupposes an object-oriented commitment to co-locating behavior and state, so what reads as feature envy under one paradigm is the intended design under another. Diagnostic: Is the codebase's paradigm one where behavior belongs with data (making the envy a real defect), or one that deliberately separates them (making the high fan-out idiomatic rather than a smell)?
T5: Autonomy versus reduction (a named OO smell or one violation of the general cohesion principle). Feature envy is a specific, locally-countable, refactoring-addressable smell with proprietary OO cargo — the Move Method refactoring, the cross-class fan-out metric and its static analyzer, the class-and-method-boundary model, the Tell-Don't-Ask/Demeter idiom — and within software it transfers as mechanism across anemic models, god controllers, and cross-service reads. But stripped of object-oriented vocabulary it collapses to exactly the parent principle: cohesion — behavior should live with the data it operates on, and a design pays in change-propagation cost when it does not. Mapping it onto org design or legal drafting requires re-abstracting up to cohesion in each case, the tell that the parent is what travels, not the named smell (and note the boundaries: it is a design-time locality distinct from the runtime locality_of_reference prime, one named shape on the coupling dimension, and a false friend to social "envy"). Diagnostic: Resolve toward cohesion / responsibility-data alignment when carrying the lesson to teams, contracts, or any non-code substrate; toward feature envy when a method's fan-out shows behavior sitting at the wrong class address in object-oriented code.
Structural–Framed Character¶
Feature envy sits at framed-leaning on the structural–framed spectrum — further toward framed than its batch-mate feature creep, because its very status as a defect is contingent on a chosen design paradigm, yet held off the pole by the genuinely portable cohesion principle it instantiates. Four criteria point framed. On evaluative weight it is a design verdict: to call a method "envious" is to judge it misplaced, at the wrong address — and the entry's own T4 makes plain that this verdict is paradigm-relative, since the same high cross-class fan-out that reads as a smell under a rich-domain-model philosophy is idiomatic under data-oriented design, functional programming, or CQRS. A defect-status that flips with the adopted design philosophy is the mark of a framed judgment, not a neutral mechanism. On human-practice-bound it is framed: the smell presupposes object-oriented classes with methods and fields and a Tell-Don't-Ask / Law-of-Demeter commitment to co-locating behavior with state — a designed engineering practice — and it dissolves the moment that practice is dropped for a paradigm that separates behavior from data on purpose. On institutional origin it is framed: it is a code-organization convention named by Fowler in a specific refactoring tradition, not a fact that would obtain observer-free. On vocab-travels it is framed: the Move Method refactoring, the cross-class-versus-in-class fan-out metric, the static analyzer that counts it, and the class-and-method-boundary model are OO furniture that do not survive extraction to an org chart or a statute.
On import-vs-recognize the entry is careful, and the answer is bimodal in a way that keeps it off the framed pole. The parent principle — cohesion — genuinely recurs across domains and is recognized as the same mechanism (a team reaching into another team's information to do work that team should own pays the same change-propagation cost). But "feature envy" specifically travels only by analogy in naming: mapping it onto organizational design or legal drafting requires substantial re-abstraction up to cohesion in each case, and the code-smell apparatus does not come along — the tell that the parent, not the named smell, is what reaches.
The portable structural skeleton is that parent: cohesion — behavior should live with the data (or authority, or capability) it operates on, and a design pays in change-propagation cost when it does not. That skeleton is genuinely substrate-portable, which is what keeps feature envy from the bare framed pole where the relevance fallacies sit. But it does not lift the smell toward a prime, because the skeleton is exactly what feature envy instantiates from its umbrella cohesion — one named object-oriented violation of behavior-data co-location, a sibling of god class, shotgun surgery, and data clumps — not what makes "feature envy" itself travel: the cross-domain reach belongs to cohesion, while the fan-out metric, the Move Method remedy, and the paradigm-relative "wrong address" verdict stay home. Its character: a paradigm-constituted, verdict-bearing object-oriented code smell whose distinctive apparatus is home-bound, structural only in the cohesion skeleton it borrows from its parent and expresses as a locally-countable OO defect.
Structural Core vs. Domain Accent¶
This section decides why feature envy is a domain-specific abstraction and not a prime — and it carries the case for its domain-specificity.
What is skeletal (could lift toward a cross-domain prime). Strip the object-oriented vocabulary and a real relational structure survives: behavior sits at a different address than the data (or authority, or capability) it operates on, and the design pays for the misalignment in change-propagation cost — every change to the data's owner drags the misplaced behavior along. The portable pieces are abstract — a data owner, an outsider unit whose work is dominated by reaching into that owner, a locality mismatch between where behavior lives and where the data it uses lives, and a maintenance tax paid over time as the owner evolves. That skeleton is genuinely substrate-portable: it recurs as a team repeatedly reaching into another team's information to do work that team should own, or a contract clause operating on definitions housed elsewhere. Precisely because it recurs, it is carried by the parent feature envy instantiates — cohesion (behavior should live with the data it operates on). But that is the core the smell shares, not what makes it distinctive.
What is domain-bound. Everything that makes this specifically feature envy is object-oriented software furniture and none of it survives extraction. It presupposes classes with methods and fields and a Tell-Don't-Ask / Law-of-Demeter commitment to co-locating behavior with state. Its worked apparatus is discipline-internal: the cross-class-versus-in-class access fan-out metric and the static analyzer that counts it, the Move Method (or Extract-then-Move) refactoring bound to the diagnosis, the class-and-method-boundary model, and the empirical cases (the PriceCalculator.getTotalPrice relocation, the anemic-domain-model service classes). The decisive test is unusually sharp here because the defect status itself is paradigm-relative: the very same high cross-class fan-out that reads as a smell under a rich-domain-model philosophy is idiomatic under data-oriented design, functional programming, DTOs, or CQRS, which deliberately separate behavior from data — so remove the OO co-location commitment and there is no envy at all, only an ordinary cross-structure access. And mapping "feature envy" onto an org chart or a statute requires re-abstracting all the way up to cohesion, dropping the fan-out metric and Move Method entirely. The OO apparatus and the paradigm-relative "wrong address" verdict are the accent that stays home.
Why this does not clear the prime bar. A prime is a relational structure whose vocabulary travels and whose cross-domain transfer is recognition of the same mechanism, not analogy. Feature envy's transfer is bimodal. Within object-oriented software it moves intact as mechanism — the four-part signature and its recognize-locate-relocate chain carry without translation across anemic domain models, god controllers, manager-class accretion, utility-class drift, and even cross-service reads across process boundaries, with the fan-out diagnostic, the correctness-versus-placement split, and the Move Method remedy all applying to the same class-and-method machinery. Beyond software it travels only by analogy in naming: the underlying cohesion mechanism really is shared, but the code-smell apparatus does not come along, so calling a misplaced organizational responsibility "feature envy" borrows the label while dropping everything that individuates it. And when the bare structural lesson is needed cross-domain — behavior should live with the data it operates on, or the design pays in propagation cost — it is already carried, in more general form, by cohesion, of which feature envy is one named software violation among siblings (god class, shotgun surgery, divergent change, data clumps). The cross-domain reach belongs to that parent; "feature envy," as named, carries object-oriented baggage — and a paradigm-relative verdict — that should stay home.
Relationships to Other Abstractions¶
Current abstraction Feature Envy Domain-specific
Parents (2) — more general patterns this builds on
-
Feature Envy is a kind of Code Smell Domain-specific
Feature Envy is the code-smell species whose cross-class access pattern provides defeasible evidence that a method's responsibility resides on the wrong class.It inherits the trace-to-design-hypothesis, contextual test, predicted maintenance consequence, and behavior-preserving refactoring probe. The child adds cross-class access fan-out, locality mismatch, change propagation, and Move Method as the discriminating intervention.
-
Feature Envy is part of Coupling Prime
Outbound dependence on another class's state and behavior is the measurable coupling constituent from which feature envy is diagnosed.The smell compares a method's accesses to its own class with accesses across another class boundary. Remove those cross-boundary dependencies and the quantitative envy signal disappears; relocation follows the dependency toward the data it operates on.
Hierarchy paths (5) — routes to 5 parentless roots
- Feature Envy → Code Smell → Evidence → Provenance → Traceability → Observability
- Feature Envy → Coupling
- Feature Envy → Code Smell → Evidence → Provenance → Attestation → Authentication
- Feature Envy → Code Smell → Evidence → Provenance → Traceability → Transformation → Function (Mapping)
- Feature Envy → Code Smell → Evidence → Provenance → Custody Transfer → State and State Transition → Phase Space
Not to Be Confused With¶
-
Shotgun surgery. The sibling cohesion-axis smell that points the opposite way: a single conceptual change forces coordinated edits scattered across many classes. Feature envy is one method concentrated on one other class's data — misplacement at a single address, not dispersal across many. Tell: does one change ripple out to many classes that must all be edited together (shotgun surgery), or does one method's fan-out reveal it operating mostly inside a single other class (feature envy)?
-
Inappropriate intimacy. The sibling smell of two classes that each reach into the other's internals — a mutual, bidirectional over-coupling. Feature envy is characteristically one-directional: a method on A envies B, without B reciprocally depending on A. Tell: is the excessive internal access mutual between two classes (inappropriate intimacy), or a one-way reach by a single method into a class that does not reach back (feature envy)?
-
Message chains / Law of Demeter violation. A navigation smell —
a.getB().getC().getD(), drilling through a train of intermediary objects. The entry notes feature envy frequently co-occurs with such chains, but they are distinct: a message chain concerns the path an access traverses, feature envy the aggregate concentration of a method's field- and method-accesses on one class. Tell: is the smell a chain of calls tunnelling through a sequence of objects (message chain), or a method whose total access fan-out lands lopsidedly on one other class regardless of path (feature envy)? -
Middle man. The near-opposite smell: a class that over-delegates, forwarding almost every call to another and adding nothing of its own. Feature envy is the inverse imbalance — a method that under-delegates, doing another class's work externally by reaching into its state instead of telling it to act (the Tell-Don't-Ask violation). Tell: does the unit merely pass work through to another and contribute nothing (middle man), or does it do another class's work itself off that class's fields (feature envy)?
-
Anemic domain model. The architectural style in which entity classes are reduced to data bags of getters and setters while behaviour pools in service classes — the design context in which feature envy becomes endemic. Feature envy is the method-level symptom pervading that style, not the style itself: the anemic model is the habitat, the envious method the local, countable instance. Tell: is it the whole data-only-entities-plus-behaviour-in-services design pattern (anemic domain model), or the specific method whose fan-out shows it belongs on the entity it reaches into (feature envy)?
-
The
cohesionparent (the umbrella it instances). The substrate-neutral principle — behaviour should live with the data (or authority, or capability) it operates on, and a design pays change-propagation cost when it does not — that feature envy is one named object-oriented violation of, alongside siblings god class, shotgun surgery, and data clumps, not a peer to sort against. Tell: carried to a team, a contract, or any non-code substrate, the operative structure iscohesion; "feature envy" applies only where a method's countable fan-out shows behaviour sitting at the wrong class address. (Treated fully in the sections above.)
Neighborhood in Abstraction Space¶
Feature Envy sits in a sparse region of the domain-specific corpus (97th percentile for distinctiveness): few abstractions share its structure, so a faithful description tends to retrieve it precisely.
Family — Adversarial Exploits & Structural Boundaries (12 abstractions)
Nearest neighbors
- Data Class — 0.82
- Long Parameter List — 0.80
- Memory Management — 0.80
- Insecure Deserialization — 0.80
- Universal property — 0.79
Computed from structural-signature embeddings · 2026-07-12