Navs
Code Smells Catalog
Feature Envy

Last Revision — April 19, 2022

2 Min Read


  • Also Known As

    ---

  • Obstruction

    Couplers

  • Occurrence

    Responsibility

  • Expanse

    Between Classes

  • Related Smells

    - Fate over Action (caused)
    - Insider Trading (co-exist)

  • History

    Martin Fowler in book (1999): "Refactoring: Improving the Design of Existing Code"

Feature Envy

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

Low Testability

Difficult to create proper test or tests in separation. Mocking is required.

Inability to Reuse

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.

Bijection Problems

Example

Smelly

@dataclass(frozen=True)
class ShoppingItem:
    name: str
    price: float
    tax: float


class Order:
    ...
    def get_bill_total(self, items: list[ShoppingItem]) -> float:
        return sum([item.price * item.tax for item in items])

    def get_receipt_string(self, items: list[ShoppingItem]) -> list[str]:
        return [f"{item.name}: {item.price * item.tax}$" for item in items]

    def create_receipt(self, items: list[ShoppingItem]) -> float:
        bill = self.get_bill_total(items)
        receipt = self.get_receipt_string(items).join('\n')
        return f"{receipt}\nBill {bill}"

Solution

@dataclass(frozen=True)
class ShoppingItem:
    name: str
    price: float
    tax: float

    @property
    def taxed_price(self) -> float:
        return self.price * self.tax

    def get_receipt_string(self) -> str:
        return f"{self.name}: {self.price * self.tax}$"

class Order:
    ...
    def get_bill_total(items: list[ShoppingItem]) -> float:
        return sum([item.taxed_price for item in items])

    def get_receipt_string(items: list[ShoppingItem]) -> list[str]:
        return [item.get_receipt_string() for item in items]

    def create_receipt(items: list[ShoppingItem]) -> float:
        bill = self.get_bill_total(items)
        receipt = self.get_receipt_string(items).join('\n')
        return f"{receipt}\nBill: {bill}$"

Refactoring:

  • Move Method
  • Move Field
  • Extract Method

Sources
  • [1] - Mika Mäntylä, "Bad Smells in Software - a Taxonomy and an Empirical Study" (2003)
  • [Origin] - Martin Fowler, "Refactoring: Improving the Design of Existing Code" (1999)