Blog

The 4 Rules of East-oriented Code: Rule 3

by Jim

When I set out to create my presentation for RubyConf, I wanted to provide the audience with something they could easily try. By doing that, one could walk away and put themselves in a position to think about their code differently. While, James Ladd, the creator of East-oriented Code made some basic rules, I decide to take them and frame it in the specific context of Ruby:

  1. Always return self
  2. Objects may query themselves
  3. Factories are exempt
  4. Break the rules sparingly

After writing about Rule 1 and Rule 2 I’m very eager to get to Rule 3. It’s an easy way to break the intent of this style without breaking the rules.

Factories are Exempt

They must be. If you returned self from Object.new you’d just get Object back, not an instance of an object. So factories are exempt from returning self.

The best way to get around any of these rules is to just make something into a factory. But here lies the danger. It’s important to first think about what these objects are doing. For what are they responsible?

We could create a class to sweep our messy code under the rug.

user = User.new
signup = UserSignupProcess.new
signup.create_with(user)

The code above, we could say, is East-oriented. The factories create instances, and the signup object is told to create_with and given the user object.

Beyond this (inside create_with), it could easily be an enormous mess. While we can and probably should use different programming techniques for different situations, taking a load of if statements and sweeping it into a class could still be problematic.

Now, the sample code above is completely made up to show how you can take part of your program and say “this part is East-oriented, but over here I used this other technique. I call it If-oriented.”

Examining your domain and creating a program to support it requires that you carefully evaluate what objects should exist, what their responsibilities are, and what you will name them.

East-orientation is all about designating responsibilities.

This leads us to breaking the rules…

We’ll be getting to that later. There’s likely very good reasons to break any particular programming rule, but it probably depends on the context.

I wrote Clean Ruby and the chapter on East-oriented Code before I set up the 4 rules for my presentation, but the same lessons are there. I’ll be adding more to it, particularly as discussion and ideas around DCI evolve, but I’m putting effort toward wrapping up the Ruby DSL Handbook. It will soon be complete and the $12 price will go up to $24, so pick it up now if you’re interested.

Ruby DSL Handbook is about how to create a DSL without headaches from metaprogramming and I just released an update with a chapter about creating a DSL without metaprogramming at all. Much like this discussion today, it’s all about managing responsibilities.

Clean Up Your Code

If you liked this post and want more like it, get periodic tips in your inbox by leaving your email address below.

To get more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.



Permalink…

The 4 Rules of East-oriented Code: Rule 2

by Jim

In a previous article I wrote about the first rule of East-oriented Code.

Here again are the rules I set forth in my presentation at RubyConf:

  1. Always return self
  2. Objects may query themselves
  3. Factories are exempt
  4. Break the rules sparingly

The second rule, that “Objects may query themselves”, allows the design of objects to work with their own attributes.

When we design our systems of interacting objects we can use the Tell, Don’t Ask approach to limit the decisions in the code to objects which are responsible for the data used to make them.

The Tell, Don’t Ask article begins by quoting Alec Sharp:

Procedural code gets information then makes decisions. Object-oriented code tells objects to do things.

In order for objects to do things, they may need to ask questions about their own data. Though the first rule of East-oriented Code says that you should return self, internal private methods don’t need to follow this rule. We can and might need to create query methods to allow the object to make decisions.

It’s easy to begin designing an object by specifying what it’s attributes are:

class Person
  attr_reader :name, :nickname, :gender
end

When we do that, we also implicitly allow other objects to use these attributes and make decisions:

if person.nickname =~ /^DJ/
  person.setup_playlist_preferences('house')
else
  person.setup_playlist_preferences('classical')
end

In the sample code above we’re using a method setup_playlist_preferences which accepts a single argument. The decision about what value to set is made outside of the person object. As additional options are added to the system, this if may have elsif clauses added to it or it may turn into a case statement. With public attributes, those changes can appear in multiple places in your system, which can lead to headaches when the structures of your objects change.

Alternatively, we could command the object to do what we want:

person.setup_playlist_preferences

Any decision about what to do to setup preferences can be made inside the setup_playlist_preferences method.

Here’s a summary from the C2 wiki

Very very short summary: It is okay to use accessors to get the state of an object, as long as you don’t use the result to make decisions outside the object. Any decisions based entirely upon the state of one object should be made ‘inside’ the object itself.

One way to prevent decisions about an object from being made outside that object is to limit the public information:

class Person
  private
  attr_reader :name, :nickname, :gender
end

If you like this, check out the new book I’m writing: Ruby DSL Handbook which is currently half-off the final price. It’s designed to be a guide to help you make decisions about how to build your own DSL and compressions of concepts.

Permalink…

The 4 Rules of East-oriented Code: Rule 1

by Jim

4 simple rules are pretty easy to remember, but a bit harder to understand and apply.

A key concept of East-oriented Code is to enforce the use of commands by returning the object receiving a message.

Here’s a simple example of what that looks like:

def do_something
  # logic for doing something omitted...
  self
end

It’s incredibly simple to follow.

Here are the rules I set forth in my presentation at RubyConf:

  1. Always return self
  2. Objects may query themselves
  3. Factories are exempt
  4. Break the rules sparingly

The first three are hard rules. The fourth, obviously, is more lenient. We’ll get to some guidance on breaking the rules in the future but for now let’s look at applying this to your code.

Rule 1: Always return self

Although this rule is simple at first, it inevitably leads to the queston of getter methods.

What if your objects had no getters? What if an object’s name attribute simply was inaccessible to an external object?

You can make your data private by either marking your attr_accessors as private:

attr_accessor :name
private :name

Or you can use the private method to mark all of the following defined methods to be private:

private
attr_accessor :name

How you choose to do it will depend upon your code, but this would help you remove any getter methods.

Now this leaves you with a conundrum. How do you use the information?

If you have a need for that name, what can you do?

The only answer is to create a command which will apply the data to the thing you need.

def apply_name_to(form)
  form.name = name
  self
end

The restricitions we put in our code are often self-imposed.

We can make whatever we want, so what’s to stop us from putting Rails model data manipulation in it’s view template? Nothing concrete stops us from doing so.

The same goes for getter methods like name. If it is publicly accessible by external objects, then we can create whatever if and case statements we want. We can put logic wherever we want.

If we create our own restrictions, we can guide ourselves and other programmers to the direction we intend for our application’s structure.

Creating restrictions

I’ve written about the Forwardable library in the past not only because of it’s usefulness, but because we can copy the same pattern to create our own DSL.

Forwardable provides methods which create getter methods for related objects. But what if we created our own DSL for commands to related objects? What if we could pass the messages on, but allow the related object to handle the values?

Here’s what that could look like:

class Person
  command :send_email => :emailer
end
person = Person.find(1) # get some record
person.emailer = Emailer.get # get some object to handle the emailing
person.send_email

That’s a lot of pseudo-code but the parts we care about are sending the command to a related object. Commands return the receiving object, queries will return a value.

Here’s what that code would look like without our (yet unimplemented) command DSL.

class Person
  def send_email
    emailer.send_email
    self
  end
end

Any code which uses a Person will have to rely on the command to do its own thing. This prevents a programmer from leaking logic out of the person.

What should happen when the email is sent? With the structure above, this code, can’t make decisions:

if person.send_email
  # do one thing
else
  # this will never work now
end

If you find that you often write code like the if statement above, you might wonder “where does that logic go now?” Now, you’ll be forced to write this code:

person.send_email

And this means that your send_email now has the job of handling what to do:

class Person
  def send_email
    emailer.send_email
    # do some other things...
    self
  end
end

That might provide you with better cohesion; the related behaviors remain together.

Getting back to that command DSL we used above…

This was the final point of my presentation at RubyConf: you can build guidlines like this for yourself.

I created a gem called direction to handle enforcing this East-oriented approach. I’ll write more about that later, but it shows that I can create signals to other developers on my team. I can take a simple concept like a command and simplify my code to show other developers what’s happening:

class Person
  command :send_email => :emailer
end

Building a DSL can aid in communication. The language and terminology we use can compress ideas into easily digestible parts.

If you like this, check out my new book: Ruby DSL Handbook designed to be a guide to help you build your own compressions of concepts.

Permalink…

Ruby Forwardable deep dive

by Jim

The Forwardable library is one of my favorite tools from Ruby’s standard library both for simplifying my own code and learning how to make simple libraries.

I find that the best way to understand how code works is to first understand why it exists and how you use it. In a previous article I wrote about the value of using Forwardable. It takes code like this:

def street
  address.street
end

def city
  address.city
end

def state
  address.state
end

And makes it as short as this:

delegate [:street, :city, :state] => :address

Shrinking our code without losing behavior is a great feature which Forwardable provides. So how does it work?

Modules and their context

Forwardable is a module, which can be used to add behavior to an object. Most of the of modules I see tend to be used like this:

class Person
  include SuperSpecial
end

But Forwardable is different and is designed to be used with the extend method.

require 'forwardable'
class Person
  extend Forwardable
end

Using extend includes the module into the singleton_class of the current object. There’s a bit more to it than that, but here’s a simple model to keep in mind: use include in your class to add instance methods; use extend in your class to add class methods.

Now that we have that out of the way, to use Forwardable, use extend.

Defining forwarding rules

My most often used feature of Forwardable is the one you saw above: delegate. It accepts a hash where the keys can be symbol or string method names, or an array of symbols and strings. The values provided are accessors for the object to which you’ll be forwarding the method names.

class Person
  extend Forwardable

  delegate [:message_to_forward, :another_method_name] => :object_to_receive_message,
            :single_method => :other_object
end

Other shortcuts

Forwardable provides a few methods, and most commonly you’ll see their shortened versions: delegate, def_delegator, and def_delegators. These are actually alias methods of the originals.

alias delegate instance_delegate
alias def_delegators def_instance_delegators
alias def_delegator def_instance_delegator

The delegate method we reviewed above is a bit of a shortcut for similar behavior that other methods provide in Forwardable.

The def_delegators method accepts multiple arguments but it’s sometimes hard for me to remember that one argument in particular is important. The first argument is the reference to the related object, the next arguments are used to create methods to forward.

class SpecialCollection
  extend Forwardable

  def_delegators :@collection, :clear, :first, :push, :shift, :size
  # The above is equivalent to:
  delegate [:clear, :first, :push, :shift, :size] => :@collection
end

As you can see, with delegate there’s a visual separation between the accessor and the list of methods.

There’s more of a difference between delegate and def_delegators too.

def instance_delegate(hash) # aliased as delegate
  hash.each{ |methods, accessor|
    methods = [methods] unless methods.respond_to?(:each)
    methods.each{ |method|
      def_instance_delegator(accessor, method)
    }
  }
end

Here the code loops through the hash argument changing the keys into arrays of methods if they aren’t already arrays, and then calls the def_instance_delegator method for each item in the array. Here’s what def_instance_delegators looks like. Note that this is the plural version:

def def_instance_delegators(accessor, *methods) # aliased as def_delegators
  methods.delete("__send__")
  methods.delete("__id__")
  for method in methods
    def_instance_delegator(accessor, method)
  end
end

This method speficially restricts the use of __send__ and __id__ in forwarded messages. These methods are particularly important in communicating with the forwarding object and determining its identity. If you only used delegate and (for some strange reason) you specify either of __send__ or __id__ then those methods will pass right through. That might do exactly what you want or it might introduce some buggy behavior. This is mostly easy to avoid since you’ll likely specify all the methods you need.

The different behavior is important to know, however, if you want to do a blanket forward for all methods from another class of objects:

class SpecialCollection
  extend Forwardable

  def_delegators :@collection, *Array.instance_methods
  # The above is equivalent to:
  delegate [*Array.instance_methods] => :@collection
end

If you do that, you’ll likely see warnings from Ruby like this:

warning: redefining `__send__' may cause serious problems

Don’t say Ruby didn’t warn you!

But def_delegators is a plural version of def_delegator which provides more options than the two we’ve been reviewing.

class SpecialCollection
  extend Forwardable

  def_delegator :@collection, :clear, :remove
  def_delegator :@collection, :first
end

The method def_delegator accepts only three arguments. The first is the accessor for the related object (which will receive the forwarded message) and the second is the name of the message to be sent to the related object. The third argument is the name of the method to be created on the current class and is optional; if you don’t specify it then the second argument will be used.

Here’s what the above def_delegator configurations would look like if you wrote out the feature yourself:

class SpecialCollection
  extend Forwardable

  # def_delegator :@collection, :clear, :remove
  def remove
    @collection.clear
  end

  # def_delegator :@collection, :first
  def first
    @collection.first
  end
end

You can see how the optional third argument is used as the name of the method on your class (e.g. remove instead of clear).

How the methods are created

We looked at how Forwardable adds class methods to your class. Let’s look at the most important one:

def def_instance_delegator(accessor, method, ali = method)
  line_no = __LINE__; str = %{
    def #{ali}(*args, &block)
      begin
        #{accessor}.__send__(:#{method}, *args, &block)
      rescue Exception
        $@.delete_if{|s| Forwardable::FILE_REGEXP =~ s} unless Forwardable::debug
        ::Kernel::raise
      end
    end
  }
  # If it's not a class or module, it's an instance
  begin
    module_eval(str, __FILE__, line_no)
  rescue
    instance_eval(str, __FILE__, line_no)
  end
end

It looks like a lot, and it is, but let’s strip it down to it’s simplest form rather than review everything at once. Here’s a simpler version:

def def_instance_delegator(accessor, method, ali = method)
  str = %{
    def #{ali}(*args, &block)
      #{accessor}.__send__(:#{method}, *args, &block)
    end
  }
  module_eval(str, __FILE__, __LINE__)
end

Remembering, of course, that def_instance_delegator is aliased as def_delegator we can see that a string is created which represents what the method definition will be and saved to the str variable. Then then that variable is passed into module_eval.

It’s good to know that module_eval is the same as class_eval because I know I often see class_eval but rarely see the other. Regardless, class_eval is merely an alias for module_eval.

The string for the generated method is used by module_eval to create the actual instance method. It evaluates the string and turns it into Ruby code.

Taking this command def_delegator :@collection, :clear, :remove here’s what string will be generated:

%{
  def remove(*args, &block)
    @collection.__send__(:clear, *args, &block)
  end
}

Now it’s a bit clearer what’s going to be created.

If you’re not familiar with __send__, know that it’s also aliased as send. If you need to use the send method to match your domain language, you can use it and rely on __send__ for the original behavior. Here, the Forwardable code is cautiously avoiding any clashes with your domain language just in case you do use “send” as a behavior for some object in your system.

Maybe you’re scratching your head about what either of those methods are at all. What the heck is send anyway!?

The simplest way to describe it is to show it. This @collection.__send__(:clear, *args, &block) is equivalent to:

@collection.clear(*args, &block)

All Ruby objects accept messages via the __send__ method. It just so happens that you can use the dot notation to send messages too. For any method in your object, you could pass it’s name as a string or symbol to __send__ and it would work the same.

It’s important to note that using __send__ or send will run private methods as well. If the clear method on @collection is marked as private, the use of __send__ will circumvent that.

The methods defined by Forwardable will accept any arguments as specified by *args. And each method may optionally accept a block as referred to in &block.

It’s likely that the acceptance of any arguments and block will not affect your use of the forwarding method, but it’s good to know. If you send more arguments than the receiving method accepts, your forwarding method will happily pass them along and your receiving method will raise an ArgumentError.

Managing errors

Forwardable maintains a regular expression that it uses to strip out references to itself in error messages.

FILE_REGEXP = %r"#{Regexp.quote(__FILE__)}"

This creates a regular expression where the current file path as specified by __FILE__ is escaped for characters which might interfere with a regular expression.

That seems a bit useless by itself, but remembering the original implementation of def_instance_delegator we’ll see how it’s used:

str = %{
  def #{ali}(*args, &block)
    begin
      #{accessor}.__send__(:#{method}, *args, &block)
    rescue Exception
      $@.delete_if{|s| Forwardable::FILE_REGEXP =~ s} unless Forwardable::debug
      ::Kernel::raise
    end
  end
}

This code recues any exceptions from the forwarded message and removes references to the Forwardable file.

The $@ or “dollar-at” global variable in Ruby refers to the backtrace for the last exception raised. A backtrace is an array of filenames plus their relevant line numbers and other reference information. Forwardable defines these forwarding methods to remove any lines which mention Forwardable itself. When your receive an error, you’ll want the error to point to your code, and not the code from the library which generated it.

Looking at this implementation we can also see a reference to Forwardable::debug which when set to a truthy value will not remove the Forwardable lines from the backtrace. Just use Forwardable.debug = true if you run into trouble and want to see the full errors. I’ve never needed that myself, but at least it’s there.

The next thing to do, of course, is to re-raise the cleaned up backtrace. Again Forwardable will be careful to avoid any overrides you may have defined for a method named raise and explicitly uses ::Kernel::raise.

The double colon preceding Kernel tells the Ruby interpreter to search from the top-level namespace for Kernel. That means that if, for some crazy reason, you’ve defined a Kernel underneath some other module name (such as MyApp::Kernel) then Forwardable will use the standard behavior for raise as defined in Ruby’s Kernel and not yours. That makes for predictable behavior.

Applying the generated methods

After creating the strings for the forwarding methods, Forwardable will attempt to use module_eval to define the methods.

# If it's not a class or module, it's an instance
begin
  module_eval(str, __FILE__, line_no)
rescue
  instance_eval(str, __FILE__, line_no)
end

If the use of module_eval raises an error, then it will fallback to instance_eval.

I’ve yet to find a place where I’ve needed this instance eval feature, but it’s good to know about. What this means is that not only can you extend a class or module with Forwardable, but you can extend an individual object with it too.

object = Object.new
object.extend(Forwardable)
object.def_delegator ...

This code works, depending of course on what you put in your def_delegator call.

Forwarding at the class or module level

All these shortcuts for defining methods are great, but they only work for instances of objects.

Fortunately forwardable.rb also provides SingleForwardable, specifically designed for use with modules (classes are modules too).

class Person
  extend Forwardable
  extend SingleForwardable
end

In the above sample you can see that Person is extended with both Forwardable and SingleForwardable. This means that this class can use shortcuts for forwarding methods for both instances and the class itself.

The reason this library defines those longform methods like def_instance_delegator instead of just def_delegator is for a scenario like this. If you wanted to use def_delegator and those methods were not aliased, you’d need to choose only one part of this library.

class Person
  extend Forwardable
  extend SingleForwardable

  single_delegate [:store_exception] => :ExceptionTracker
  instance_delegate [:street, :city, :state] => :address
end

As you can probably guess from the above code, the names of each library’s methods matter.

alias delegate single_delegate
alias def_delegators def_single_delegators
alias def_delegator def_single_delegator

If you use both Forwardable and SingleForwardable, you’ll want to avoid the shortened versions like delegate and be more specific by using instance_delegate for Forwardable, or single_delegate for SingleForwardable.

If you liked this article, please join my mailing list at http://clean-ruby.com or pick up the book today!

Clean Up Your Code

If you liked this post and want more like it, get periodic tips in your inbox by leaving your email address below.

To get more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.



Permalink…

Avoiding clever mistakes when displaying data with missing values

by Jim

In a previous article I showed a snippet of code I’ve used for displaying address information. There were some tricks to getting it right that are valuable to know when you have to handle missing data.

Here’s the problem, and how to solve it.

Let’s setup some simple data to use:

street = "123 Main St."
city = "Arlington"
province = "VA"
postal_code = "222222"

The original implementation of displaying an address looked like this:

"".tap do |string|
  string << street unless street.nil?
  string << city unless city.nil?
  string << province unless province.nil?
  string << postal_code unless postal_code.nil?
end

While that code will skip missing data if there is any, it will just mash all the bits together creating this:

"123 Main St.ArlingtonVA22222"

That’s not particularly helpful for displaying a readable address. I’m looking for it to display like this:

"123 Main St.
Arlington, VA 22222"

Here’s what I tried next:

[street, [city, [province, postal_code].join(' ')].join(', ')].join("\n")

Thinking I was very clever, I ran the code with complete data and it worked quite well.

When I ran it with incomplete data I found that it didn’t work the way I expected. For example, if the city value was nil, I got this:

"123 Main St.
, VA 22222"

That leading comma is just visual noise, so we need to remove that. Realizing that I had nil values in my arrays, I knew I could reach for the compact method to strip them out. After compacting the array, the join wouldn’t have nil values to address; they’d be gone.

[street, [city, [province, postal_code].compact.join(' ')].compact.join(', ')].compact.join("\n")

This worked perfectly, to remove the leading comma:

"123 Main St.
VA 22222"

Just to be sure I got it right, I began checking other data. Next, with the city set to “Arlington” and this time with the province and postal_code set to nil I saw this:

"123 Main St.
Arlington, "

Ugh! Now I had a trailing comma. Why wasn’t this working!? Using compact should remove the nil values.

The problem was that I had an empty array for the province and postal_code. That meant that with both values removed from the array using compact, this was happening:

[].join(' ') #=> ""

And because that empty array returned a value of an empty string, I was joining the city value with an empty string. In this case, compact was doing nothing for me.

[city, [province, postal_code].compact.join(' ')].compact.join(', ')
# is the same as:

[city, ""].compact.join(', ')]
# which yields:

"Arlington, "

So there it was. Finally I found my problem that what I thought was nil, wasn’t.

I decided to change the values to nil if they were empty strings:

province_and_postal_code = [province, postal_code].compact.join(' ')
province_and_postal_code = nil if province_and_postal_code.empty?

city_province_postal_code = [city, province_and_postal_code].compact.join(', ')
city_province_postal_code = nil if city_province_postal_code.empty?

[street, city_province_postal_code].compact.join("\n")

Finally, I got the output I needed:

"123 Main St.
Arlington"

"123 Main St.
VA 22222"

"123 Main St.
Arlington, VA 22222"

My clever one-liner gave me unexpected behavior. While it was nice and short, it was also wrong. Eventually I ended up with code which is not just correct, but much more readable too. You might have your own preferences for how to handle these nil values. What would you do differently?

Clean Up Your Code

If you liked this post and want more like it, get periodic tips in your inbox by leaving your email address below.

To get more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.



Permalink…

Enforcing encapsulation with East-oriented Code

by Jim

Often our programs become complicated inadventently. We don’t intend to put things it the wrong place, it just seems to happen.

Most of the time it happens to me when I allow my objects to leak information and eventually their responsibilities.

In recent articles I showed code that handled displaying address details and how to separate the responsibility for formatting from the responsibility for data. But there’s still a problem which would allow me or someone else to unintentionally leak responsibility from these objects.

It’s a good idea to be guarded against how much you reveal from an object; you never know how someone might use it in the future.

In our Template code, we provide a number of values about the object: province, postalcode, city, street, apartment, provinceandpostalcode, cityprovincepostalcode, addresslines, and display_address. With each attribute provided, we introduce the ability to other objects to query the information and make decsions based upon the answer.

It’s far too easy to write these types of queries:

if template.province == "..."
if template.city == "..." && template.postal_code == "..."
if template.city_province_postal_code.include?("...")

But what would our code look like if we couldn’t do this? What if there were no questions to ask?

What if the only accessible information from our template was the display_address used to show the formatted data?

require 'forwardable'
class Template
  extend Forwardable

  def display_address
    address_lines.join("\n")
  end

  def with_address(address)
    @address = address
  end

  private
  delegate [:province, :postal_code, :city, :street, :apartment] => :@address

  def province_and_postal_code
    # ...
  end

  def city_province_postal_code
    # ...
  end

  def address_lines
    [street, apartment, city_province_postal_code].compact
  end
end

By moving most of our methods under the private keyword, our Template interface has shrunk significantly. Now all we’ll have to handle and all other objects will need to know about is the display_address and with_address methods.

East vs. West

The changes we made make a significant restriction on the questions that we can ask about an object. This is where the idea of East-orientation comes in.

If we imagine a compass applied to our source code we’d see that any query, any if, will send the information flowing westward.

# <---- information travels West
if template.city == "..."

The if handles the execution of the algorithm. But by removing methods from the public interface which provide attributes like above, we better encapsulate the data in the target object. Our template here could not answer a question about its city attribute.

Instead, the code which uses the template would be forced to command the template to perform a particular action. The body of the if could instead become a method on the template object.

# ----> information travels East
template.perform_action

The template can make it’s own decisions about what to do when told to perform some action.

Enforce encapsulation with return values

An easy way to ensure that our code encourages commands, discourages queries, and enforces encapsulation is to control the return values of our methods.

The best thing to return is not necessarily the result of the method, but the object performing the method. It’s as simple as adding self to the end of the method block.

Here’s what that might look like:

class Template
  def with_address(address)
    @address = address
    self
  end
end

By adding self there, each time we set the address value object using with_address we are given the object itself back, instead of the value that we passed to it.

# Without appending "self"
template.with_address(address) #=> address

# After appending "self"
template.with_address(address) #=> template

This becomes a powerful change to the way we interact with the template object. It enforces the encapsulation of data of the template and it forces us to think more about sending messages to our objects and allowing them to implement the solution.

When we return the object itself, we can only continue operation on that object.

The added benefit is that our code will become more concise. We will prevent unintentional dependencies between objects. And we can chain our commands together; it’s all the same object:

template.with_address(address).display_address

See the flow at a glance

By using a visual compass to guide us through our code, it’s easy to step back and see exactly where we leave our objects leaking information and responsibility.

Each time we query an object, each time we set a variable, we should now see the westward flow of information.

By simply returning self from our methods, we will force the hand of every developer to think with East-orientation in mind. By only working with the same object we will get back to using objects in the way that tends to be the most useful: for handling messages and implementing the required algorithm.

One last issue is that of the display_address method. Currently it returns the string representation of the address and not the template itself.

We can change that. What you do depends on how you’re using a template. Perhaps our base Template will output details to STDOUT, or perhaps to a text file. Here’s how we’d take care of that:

class Template
  def display_address
    STDOUT.puts address_lines.join("\n")
    # or perhaps File.write ...
    self
  end
end

Try this with your code. Return “self” and see how it changes your thinking. Scan for westward flow of information and see how you can push responsibilities into the appropriate objects by heading East.

From now until the end of the year, you can get Clean Ruby for only $42 (the original pre-release price) by using this link. It'll only be valid this year (2014) and will go up automatically in January 2015. Merry Christmas!

Clean Up Your Code

If you liked this post and want more like it, get periodic tips in your inbox by leaving your email address below.

To get more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.



Permalink…

1999 - 2015 © Saturn Flyer LLC 2321 S. Buchanan St. Arlington, VA 22206

Call Jim Gay at 571 403 0338