The 4 Rules of East-oriented Code: Rule 4

Often the rules we create are defined by their exceptions.

It is difficult to create a program which continually passes objects and never returns data. Often the first rule of "Always return self" is met with immediate rejection because it's easy to see the difficulty you'd encounter if that rule is continually followed for every object.

In my presentation for RubyConf, I showed how we break the rules to allow value objects to handle data for a template. I previously wrote about the approach I used in the presentation to push data into a value object.

class Address
      def display(template)
        if protect_privacy?
          template.display_address(private_version)
        else
          template.display_address(public_version)
        end
        self
      end
    end

In the sample above, an Address instance commands a template to display_address with different versions of data: private_version or public_version. This makes a flexible interface that allows Address to create any number of different versions if necessary. Perhaps the requirements will demand a semi_public_version in the future; our design of the template need not change.

This is a great way to break the rules. Value objects allow us to parameterize a collection of data in a single object. The alternative to this approach would be to use setter methods on the template object:

class Address
      def display(template)
        unless protect_privacy?
          template.street = street
          template.apartment = apartment
          template.postal_code = postal_code
        end
        template.city = city
        template.province = province
        template.display_address
        self
      end
    end

We can plainly see that although the code follows the rules by commanding the template object, there's also quite a lot happening in this display method on Address. If the requirements change we might feel encouraged to complicate the unless block or "refactor" it into a case statement. While that might solve our problem, the resulting code could lead to some difficult to read and understand implementation details.

By breaking the rules with a value object we can better encapsulate the ideas in a private address object or public or any other type we desire.

But we're not just breaking the rules inside the Address methods; the template breaks the rules too. Rule 2 says that objects may query themselves and subsequently means they should not query other objects. But by choosing to break the rules we make a design decision at a specific location to make things better.

No matter what rules you follow, you decide not only to follow them, but decide to break them as well. To make your program easy to understand and to create reasonable expectations, you can lean on creating barriers. Preventing yourself from doing one thing frees you to do another.

Embrace constraints.

How do you add constraints to your programs? What are you better able to do by adding restrictions?