Refactoring Ruby: Introduce Null Object

jonlunsford

Jon Lunsford

Posted on March 1, 2024

Refactoring Ruby: Introduce Null Object

Introduce Null Object is a great refactoring tool when you find yourself checking for nil too often. nil checks are often signs of much larger problems, as it violates Tell, Don’t Ask, and results in defensive, bug prone programming. Let’s take a look at a simple example of refactoring nil checks:

class Movie
  attr_accessor :account

  def initialize(price)
    @price = price
  end

  def rent
    unless account.nil?
      account.charge(@price)
    end
  end

  def rentable?
    account && account.rentable?
  end

  def late_fees
    account.try(:late_fees) || 0
  end
end
Enter fullscreen mode Exit fullscreen mode

This Movie class is incredibly defensive, it goes to great lengths to ensure its account actually exists. Why should that responsibility fall on Movie? It would be much better for Movie to just assume it has an account OR something resembling an account. Let’s Introduce Null Object:

class Movie
  attr_accessor :account

  def initialize(price)
    @price = price
  end

  def rent
    account.charge(@price)
  end

  def rentable?
    account.rentable?
  end

  def late_fees
    account.late_fees
  end

  def account
    # Introduce Null Object
    @account ||= NullAccount.new
  end
end

# Object that resembles an 'Account'
class NullAccount
  def charge(_price)
    "No Charge"
  end

  def rantable?
    false
  end

  def late_fees
    0
  end
end
Enter fullscreen mode Exit fullscreen mode

We have gotten rid of all conditions defending against nil and introduced one that always returns an Account like object:

def account
  @account ||= NullAccount.new
end
Enter fullscreen mode Exit fullscreen mode

Let’s run through the exact steps to take when applying Introduce Null Object:

  1. Identify conditions that check for nil, that are defending against objects potentially not existing, for example:
def rent
  # nil check
  unless account.nil?
    account.charge(@price)
  end
end
Enter fullscreen mode Exit fullscreen mode
  1. Create an object that acts like the one you are checking for as nil. Specifically, create a new class and define all of the methods that are expected to be there, for example:
class NullAccount
  def charge(_price)
    "No Charge" 
  end
end
Enter fullscreen mode Exit fullscreen mode
  1. Finally, substitute the null object when the expected one does not exist, for example:
Class Movie
  ...
  def account
    @account ||= NullAccount.new
  end
end
Enter fullscreen mode Exit fullscreen mode

Null objects are great at replacing conditional logic and making code more readable, as you don’t have to think through all of the different branches conditions introduce.

💖 💪 🙅 🚩
jonlunsford
Jon Lunsford

Posted on March 1, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

What was your win this week?
weeklyretro What was your win this week?

November 29, 2024

Where GitOps Meets ClickOps
devops Where GitOps Meets ClickOps

November 29, 2024

How to Use KitOps with MLflow
beginners How to Use KitOps with MLflow

November 29, 2024

Modern C++ for LeetCode 🧑‍💻🚀
leetcode Modern C++ for LeetCode 🧑‍💻🚀

November 29, 2024