Primitive Obsession

Primitive Obsession

As developers, we all strive to write clean, maintainable code. But every now and then, a sneaky little code smell creeps in, making our lives harder. One such smell is Primitive Obsession. It might sound like a bad habit, and in a way, it is—but don't worry. It's something we can fix.

What Is Primitive Obsession?

Primitive Obsession occurs when you rely too heavily on primitive types (like int, String, or double) to represent concepts in your code. Instead of creating meaningful abstractions, we use these primitives and hope for the best.

For example, imagine you’re writing an e-commerce application. To represent a product's price, you use a double. Sounds fine, right? But here’s the catch: what if you need to validate that the price can’t be negative or that it should always have two decimal places? Every place where you use double for price now needs that same validation logic.

This can lead to:

  • Repeated logic
  • Hard-to-read code
  • Bugs when someone forgets to validate

Spotting Primitive Obsession

Here are a few red flags:

  • You’re passing multiple related primitives as method arguments (addOrder(String name, String address, String email, int quantity)).
  • You’re using a String to represent something structured, like an email or a URL.
  • You’re repeatedly applying the same validation logic across your codebase.

How to Solve Primitive Obsession

The solution? Create small, focused classes that encapsulate the behavior of these primitives. This is often referred to as creating a value object. Let’s look at some examples in Java.

Before: Primitive Obsession

public class Product {
    private String name;
    private double price;

    public Product(String name, double price) {
        if (price < 0) {
            throw new IllegalArgumentException("Price cannot be negative");
        }
        this.name = name;
        this.price = price;
    }

    public double getPrice() {
        return price;
    }
}

Here, price is just a double, but we’ve added some validation. What happens when other classes need to manipulate or validate the price? We’ll end up duplicating this logic.

After: Encapsulating Behavior

public class Price {
    private final double amount;

    public Price(double amount) {
        if (amount < 0) {
            throw new IllegalArgumentException("Price cannot be negative");
        }
        this.amount = Math.round(amount * 100.0) / 100.0; // Ensure two decimal places
    }

    public double getAmount() {
        return amount;
    }

    @Override
    public String toString() {
        return String.format("$%.2f", amount);
    }
}

public class Product {
    private String name;
    private Price price;

    public Product(String name, Price price) {
        this.name = name;
        this.price = price;
    }

    public Price getPrice() {
        return price;
    }
}

Now, the Price class encapsulates all behavior and validation logic related to a product’s price. It’s reusable, testable, and clear.

Bonus: Using a Value Object for Email

Another common example of Primitive Obsession is using a String to represent an email address. Here’s how you can improve it:

public class Email {
    private final String address;

    public Email(String address) {
        if (!address.matches("^[\\w.%+-]+@[\\w.-]+\\.[a-zA-Z]{2,6}$")) {
            throw new IllegalArgumentException("Invalid email address");
        }
        this.address = address;
    }

    public String getAddress() {
        return address;
    }

    @Override
    public String toString() {
        return address;
    }
}

public class User {
    private String name;
    private Email email;

    public User(String name, Email email) {
        this.name = name;
        this.email = email;
    }

    public Email getEmail() {
        return email;
    }
}

Why Does This Matter?

Encapsulating primitives makes your code:

  • More readable: A Price class is more meaningful than a double.
  • Easier to maintain: Validation logic is centralized.
  • Less error-prone: It’s harder to misuse a Price object than a double.

When I first encountered Primitive Obsession, I didn’t think it was a big deal. "What’s the harm in using a String?" I thought. But after spending hours fixing bugs caused by inconsistent validation logic, I realized the power of encapsulation. Now, whenever I see primitives in my code, I ask myself, “Should this be its own class?”

By tackling Primitive Obsession head-on, you’ll find that your code becomes not just cleaner, but also a joy to work with.