How to make your code imply responsibilities

by Jim

Readablity is an important aspect of creating code that you or others will need to understand later. Because I often need to communicate purpose to other developers, I opt to avoid using if/else blocks of code, particularly in view templates.

For example, some users of a system might have certain details available to them based upon their role or some particular bit of data. Here’s how I simplified my views using presenters.

Where you might have something like this in your views

<%= if profile.has_experience? && profile.experience_public? %>
  <p><strong>Experience:</strong> <%= user_profile.experience %></p>
<% end %>

There is a better way…

Tell objects to execute blocks, let them decide if they really should

I want my views to handle conditional options, but I want the conditions to be clearer than querying for values.

<h2><%= user_profile.full_name %></h2>
<%= user_profile.bio_html %>
<% user_profile.with_experience do %>
  <p><strong>Experience:</strong> <%= user_profile.experience %></p>
<% end %>
<% user_profile.with_hobbies do %>
  <p><strong>Hobbies:<strong> <%= user_profile.hobbies %></p>
<% end %>

Here I specify what I want to display when the user profile has details for experience or details for hobbies. The implementation is simple:

class ProfilePresenter < ::Presenter
  def with_experience(&block)
    if profile.has_experience? && profile.experience_public?
      block.call(view)
    end
  end
end

The sample code is contrived, but what this allows me to do is move the details of what having experience means. Presently the code checks if the profile has_experence? and experience_public?.

This could easily be moved into a separate method for use in the view:

class ProfilePresenter < ::Presenter
  def has_public_experience?
    profile.has_experience? && profile.experience_public?
  end
end

The problem with a method like this is that 1) its name is dependent upon the implementation and 2) its intent is different from the intent used in the view.

Make your code imply responsibilites

First, the name merely combines two values. What will happen when requirements change and we need to ensure that the provided experience includes at least 3 years of activity? The meaning of has_public_experience? will change along with its behavior and lead to surprises for developers unfamiliar with these particular details. It will no longer merely be existince of experience allowed for the pubilc.

This leads us to the second problem: the intent of the method is to display features, not query for values. Were we to stick with a query method like has_public_experience? we would end up considering the content inside the view along with the meaning of the method every time we read this code.

By creating a method which accepts a block, our view template implies that the responsibility for determining display is elsewhere: in the presenter. The view will display what is configured, but determining that is upto the object we’re showing. Leaving query methods lying around in your views is just asking for the next developer to change the view code to profile.has_public_experience? && profile.has_3_years_experience?, and then the value of your presenter is lost.

The name with_experience helped us force developers to consider where changes should be made. How do you prepare your code for change? How does your team ensure that resposibilities are well-managed?

Readers of my Clean Ruby newsletter get regular tips like this. Sign-up below.

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.



Find more in the archives

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

Call Jim Gay at 571 403 0338