Overview
If a method inside a class manipulates more features (be it fields or methods) of another class more than from its own, then this method has a Feature Envy. In Object-Oriented Programming, developers should tie the functionality and behavior close to the data it uses. The instance of this smell indicates that the method is in the wrong place and is more tightly coupled to the other class than to the one where it is currently located. [1]
This was the explanation based on Fowler’s book from 1999. In his recent “book update”, he rephrased the class into module, generalizing the concept from a zone perspective. Depending on the size of the system, the Feature Envy code smell may apply accordingly.
Causation
The root cause of this smell is misplaced responsibility.
Problems
Difficult to create proper test or tests in separation. Mocking is required.
Coupled objects have to be used together. This can cause lousy duplication issues if one tries to reuse applicable code by extracting and cutting off what he does not need.
Real-world domain concepts and their code representations drift apart when behavior lives far from the data it operates on.
Example
1@dataclass(frozen=True)
2class ShoppingItem:
3 name: str
4 price: float
5 tax: float
6
7
8class Order:
9 ...
10 def get_bill_total(self, items: list[ShoppingItem]) -> float:
11 return sum([item.price * item.tax for item in items])
12
13 def get_receipt_string(self, items: list[ShoppingItem]) -> list[str]:
14 return [f"{item.name}: {item.price * item.tax}$" for item in items]
15
16 def create_receipt(self, items: list[ShoppingItem]) -> float:
17 bill = self.get_bill_total(items)
18 receipt = self.get_receipt_string(items).join('\n')
19 return f"{receipt}\nBill {bill}"Refactoring
- Move Method
- Move Field
- Extract Method
Sources
- ORIGIN1999 · ISBN 978-0201485677