Is this deadly sin killing your Ruby code?

Nothing kills clean, object-oriented code faster than feature envy. What’s feature envy? It’s a code smell that looks harmless, but quickly spreads throughout your code, making it impossible to decouple components from one another.

Identify and eliminate feature envy, and you’ll end up with a cleanly decoupled system that’s easy to change. Let it slip under the radar, and your code will become brittle and break any time you try to change it.

Fortunately, you can spot feature envy easily: any time a caller asks for data from another object, there’s chance it has feature envy. If the caller asks for two pieces of data, it almost certainly does. And if it asks for three pieces of data, it’s in full-on envy mode.

Let’s look at an example. Our system will calculate shipping based on weight – or sometimes use flat shipping.

class Seller
  def initialize(shipper)
    @shipper = shipper
  end

  def shipping_cost(weight)
    if @shipper.flat_rate
      @shipper.flat_rate
    else
      weight * @shipper.per_lb
    end
  end
end

Seller#shipping_cost asks the @seller for two pieces of data – flat_rate and per_lb. It’s not the worst thing in the world… but what if we want to provide free shipping for orders under 50 lbs? It quickly gets messy.

Besides, why should a seller know how to calculate shipping cost? Isn’t that something a shipper would know?

We can make the code easier to understand and change by moving the calculation into the shipper object. Check it out…

class Seller
  def initialize(shipper)
    @shipper = shipper
  end

  def shipping_cost(weight)
    @shipper.cost weight
  end
end

class Shipper
  def initialize(flat_rate: nil, per_lb: nil)
    @flat_rate = flat_rate
    @per_lb = per_lb
  end

  def cost(weight)
    @flat_rate ? @flat_rate : (weight * @per_lb)
  end
end

Now any object that implements #cost(weight) can calculate the shipping cost. It’s a perfect example of a domain method.

Challenge: Replace conditional with polymorphism

Shipper#cost is definitely an improvement over what we had before… but it has some unnecessary complexity – it has @flat_rate and @per_lb even though it probably only uses one of them.

Can you create two different classes – FlatRateShipper and WeightBasedShipper – to separate those two pieces of behavior? What changes do you need to make to Seller to make that work?

Eradicate feature envy with East-Oriented Code

There’s one sure-fire way to eradicate feature envy, and that’s to use the East-Oriented principle that my friend James taught me. I’ve got a whole chapter on it in RubySteps.

Do you want to write better Ruby code? Do you recognize words like “encapsulate”, “decouple”, and “polymorphism” but have no idea how to use them in practice? Get RubySteps and you’ll learn all about how to use object-oriented programming, refactoring, and testing to create high-quality, maintainable Ruby applications.

"How do I become a better Ruby developer?"

Blogs, books, and bootcamps all promise to make you a better Ruby developer, but end up confusing you more.

What if you had step-by-step instructions on how to become a better Ruby developer?

Enter your name and email below and I’ll show you how to...

  • get better at Ruby in just five minutes each day
  • use testing, OOP, and refactoring to write professional-level Ruby
  • identify and learn new programming skills quickly