DEV Community

Ahmad khattab
Ahmad khattab

Posted on

Representing boolean conditions as domain concepts

Booleans. These program control switches, they introduce branching in code and do not allow to have linear path for code execution. That is fine, because in the real world, everything is the result of another action prior to itself.

A boolean method is a method that returns a boolean result, either true or false, or truthy or falsey values. You rarely see methods being created just for the sake of in/equality checks. It is preferred to perform the check inside the code as it's more obvious what attribute are we checking the equality for.

Writing equality checks inside boolean statements does not give any context as to why this attribute is checked and not another attribute. In other words, the domain knowledge is lost.

The domain is the problem which the code tries to solve.

Imagine a set of objects that looks like this,

class Person
  attr_accessor :name

  def initialize(name)
    @name = name
  end
end

class Car
  attr_accessor :person

  def initialize(person)
    @person = person
  end
end
Enter fullscreen mode Exit fullscreen mode

If we want to determine if a car is owned by a business. We usually perform a check against the person attribute of the Car object.

john = Person.new "John Doe"
alyn = Person.new "Alyn Oakenfist"
ford = Car.new person
Enter fullscreen mode Exit fullscreen mode

Given these three objects above. To determine if ford belongs to john or alyn, we can write a conditional that looks like this.

if ford.person.name == john.name
  puts "owned by John"
else
  puts "owned by Alyn"
end
Enter fullscreen mode Exit fullscreen mode

Notice the condition we're checking. There is no context given around the code as to why we're checking the person attribute. Some domain logic is lost here, we know why we're checking. But what about someone who did not write the code, or even, our future self.

I've grown very fond of the idea of representing these conditions as domain concepts--methods of an object. I like to ask myself why the check is performed? The answer is: the check is performed to determine if car is owned by a person. In the answer, i can extract the domain concept, i.e ownership of a car, and give it a (method) name.

Let's do that, let's add a method inside the Car object that determines if this Car is owned by a person or not.

class Car
 def owned_by?(person)
    self.person.name == person.name
 end
end
Enter fullscreen mode Exit fullscreen mode

The condition above then, can be simplified into

if ford.owned_by? john
  puts "owned by John"
else
  puts "owned by Alyn"
end
Enter fullscreen mode Exit fullscreen mode

Not only the resulting code is simpler. It also saves the caller the pain of having to memorize attribute names .person.name, they can simply call the owned_by? method without having any knowledge of it's underlaying implementation. Another bonus is that this code is more maintainable. Because the reference to person is only made inside the Car object, in the future, if this attribute is changed, or the attribute we check the equality for changes, it's a simple one-line change instead of finding and replacing everywhere we're accessing person.name.

Some domain concepts are bidirectional. Both objects can perform the equality check. In this example, a Car can be owned by a Person. Also, a Person can own a Car. In these cases, i like to add the method to both objects. At first glance it might seem like duplication. But as with everyday life, the context in which the code is written differs. So, having different methods exposed on different objects enriches the vocabulary used inside the code.

class Person
 def owns?(car)
    name == car.person.name
 end
end
Enter fullscreen mode Exit fullscreen mode

Instance methods that return a boolean results are an important part of the program. They allow to capture domain concepts and gives the a name inside the code as opposed to letting them float inside the codebase.

Hope you enjoying this. Have a wonderful time coding.

Top comments (2)

Collapse
 
dorianmariefr_22 profile image
Dorian Marié • Edited

I would do:

class Person
   def owns?(car)
     self == car.person
  end
end
Enter fullscreen mode Exit fullscreen mode

And override eql? or == never sure which

Collapse
 
rockwell profile image
Ahmad khattab

That what i also would do! But i wanted the code snippets to be as simple as possible so that newbies(who haven't seen the spaceship operator, or people who don't use Ruby) could follow along easily.

Thanks for the note!