Iamvery

The musings of a nerd


Boolean Method Symmetry

— Nov 22, 2013

Symmetry is beauty. This applies to lots of things, programming included.

Boolean methods answer a question with “yes” or “no”. To adequately test such a method, you must assert on the positive and negative cases. Chances are you’ll want the complementary method eventually, so let’s start with symmetry and see where it takes us.

People, be active!

Let’s say we have a Person class as follows:

Person = Struct.new(:awake, :moving)

We’re presented with a feature that states a Person should be able to report if they are active. Let’s start with a spec:

describe Person do
  describe '#active?' do
    it 'is true when they are awake and moving' do
      person = described_class.new(true, true)

      expect(person).to be_active
    end
  end
end

And now for a very high-value implementation:

Person = Struct.new(:awake, :moving) do
  def active?
    true
  end
end

Yay! All green :). But, ahem, not exactly feature complete. Alright, so we should probably test the negative case. At this point we’re presented with a choice. Do we test the negative cases of active? or introduce the complementary method inactive?. We’ll choose symmetry:

describe Person do
  describe '#active?' do
    # ...
  end

  describe '#inactive?' do
    it 'is true when they are asleep' do
      person = described_class.new(false, true)

      expect(person).to be_inactive
    end

    it 'is true when they are still' do
      person = described_class.new(true, false)

      expect(person).to be_inactive
    end
  end
end

Cool, these new specs are erroring with a NoMethodError. We’ll implement this complementary method as a function of active?:

Person = Struct.new(:awake, :moving) do
  def active?
    true
  end

  def inactive?
    !active?
  end
end

Nice, we see a properly failing spec. The final implementation is straight forward (althrough if we’re practicing TDD, we may get there in a couple steps):

Person = Struct.new(:awake, :moving) do
  def active?
    awake && moving
  end

  def inactive?
    !active?
  end
end

Alright, so what have we accomplished? We have implemented a simple boolean method along with its complement, and we done this with tests providing adequate coverage for both methods. The complementary method could be argued YAGNI (“You ain’t gonna need it”), but in most cases you’ll end up negating your method. Having a complementary method feels symmetric… and beautiful. Further our specs cover the implementation well without duplication.

Wrapping up

Symmetry not only looks good, it makes logical sense to client developers and makes your test suite more expressive. When you encounter a boolean method you probably assume its logical complement is defined. Perhaps in this case YAGNI may be set aside for expressiveness.

How do you feel about symmetry? How important is it to the interfaces you define?