Ruby - - By Fred Heath

How to Solve Coding Anti-Patterns for Ruby Rookies

Ruby Rookies

As a freelance developer, I often work with developers from different programming backgrounds and of different skill-levels. Over time, I’ve learned to tell a programmer’s Ruby level of experience by looking at the code they’ve written. Not only that, I can also place a safe bet on the programmer’s coding background based on certain patterns within the code that give subtle clues as to the coder’s previous programming experiences. There are certain coding practices that Ruby Rookies tend to employ often. Let’s call these anti-patterns. This post enumerates the most common of these anti-patterns.

Concatenate, Not Interpolate

Ruby is one of a few languages that allow you to do string interpolation directly into the string without using any special methods. Unfortunately, many Ruby rookies prefer to concatenate strings:

puts “error description” + e.cause.to_s + “happened on: ” + Time.now.to_s

When, instead, they could write:

puts “error description #{e.cause} happened on: #{Time.now}”

Not only is this easier on the eye, but the #to_s method is automatically called for you on interpolated objects. Failure to explicitly call #to_s when concatenating will result in a TypeError. So, interpolation results in shorter, prettier, and safer code.

Also, if you want to show off to your concatenating friends, why not use Ruby’s formatted interpolation?

pets = %w{dog cat rabbit}
puts "My first pet is a %s, my second one a %s and my third is a %s" % pets
=> My first pet is a dog, my second one a cat and my third is a rabbit

The Truth be told

Many people coming into Ruby from other languages fail to realize how simple Ruby’s truth model really is. As such, they write code like this:

if an_object != nil && an_object.method? == true
  #do something
end

In Ruby, only the values false and nil evaluate to false (actually they’re both objects). Everything else evaluates to true. Thus, our conditional becomes much simpler:

if an_object && an_object.method?
  #do something
end

While we’re on the subject of boolean logic, here’s another anti-pattern I often come across.

if !an_object.active?
  #do something
end

There’s nothing wrong with negative assertions as such, apart from one thing: readability. That exclamation mark is too easy to miss, giving the reader the opposite idea about what the code does. Luckily, Ruby offers some nice syntactic sugar in the form of the unless statement:

unless an_object.active?
  #do something
end

This is a nice, emphatic statement that’s hard to miss.

AndNotOr

Many Ruby Rookies are so overjoyed by the English-like equivalent of the logical operators that they use them all the time, replacing the symbolic ones. However, they don’t always realize that [!, &&, || ] are not the same as [not, and, or]. The difference is one of precedence.

puts "hello" && "world"
 => world

In this instance, the && operator is evaluated before the puts, effectively reducing the expression to puts "world"

If we use the and operator instead, the expression is evaluated as (puts "hello") and ("world")

puts "hello" and "world"
 => hello

Observe that the symbolic operators are more ‘sticky’ than their natural-language counterparts. This can lead to subtle bugs:

arr = [1,2,3]
!arr[0].is_a?(String) && arr.length > 3
 => false
not arr[0].is_a?(String) && arr.length > 3
 => true

As a rule of thumb, only use the natural-language operators for controlling the flow of execution, while sticking to the symbolic ones for boolean operations.

Don’t Fear the (Duck Typing) Reaper

Ruby doesn’t care much about what class an object says it belongs to, it cares more about what the object can do. If I want to order a pizza, I want the pizza place to provide me with a way to order a pizza. The pizza place can call itself “Pizza Parlor”, “The Pizza Emporium”, or whatever it likes, I really don’t care that much about its name. I just want it to support the order_pizza method. Many developers from a statically-typed background have trouble coming to terms with this notion.

The average static-typing programmer who just started coding in Ruby thinks a bit like that. Here’s an example:

def my_method(arg)
  #hold on, as there are no type declarations in Ruby, arg could be anything at all
  #better be defensive about this
  if arg.is_a? MyClass #that will ensure I’m dealing with the right type
     # oh, but in Ruby one can delete methods from objects at run-time
     # better protect against that too
     if arg.respond_to? :my_method
        arg.my_method  # now I can safely call my method
     else
        # panic!
     end
  end
  #that’s nice, solid code. Well done me.
end

In reality, this is just bloated, redundant code. Just because something can happen doesn’t mean that it will. If you expect your object to behave in a certain way, chances are that it will. If the unexpected happens, well, most programming languages tend to deal with the unexpected by providing exception handling, and Ruby does just that. An exception will be raised if the object doesn’t support a method.

So, if you want to play it safe, just catch the exception:

def m(arg)
  begin
    arg.my_method
  rescue => e
  # handle exception here
  end
end

That’s defensive enough without going overboard. Furthermore, a more experienced Ruby developer would know that the method declaration itself can act as the begin block, so the code can be made even simpler.

def m(arg)
  arg.my_method
rescue => e
 # handle exception here
end

That’s safe code without the bloat. Ain’t Ruby brilliant?

A Nest of Ifs

Too many ifs and elsifs make our code look ugly and hard to read, especially if they’re nested. For Rookies, if..else statements are often used as a hammer to crack any kind of nut. There are many ways to avoid the duplication and messiness of multiple conditionals. A very common solution is to simply refactor at least one of the conditional statements as a separate method. However, one of my favorite techniques is the use of Hashes as business rules objects. Simply put, we can abstract some of our conditional logic in a Hash object, thus making our code easier on the eye and reusable.

Consider this:

if @document.save
   if current_user.role == "admin"
     redirect_to admin_path
   elsif current_user.role == "reviewer"
     redirect_to reviewer_path
   elsif current_user.role == "collaborator"
     redirect_to collaborator
   else
     redirect_to user_path
   end
 else
   render :new
 end

We can use a Logic Hash object and the ternary operator to make this code more concise and maintainable:

redirection_path = Hash.new{|hash,key| hash[key] = user_path}
redirection_path[:admin] =  admin_path
redirection_path[:reviewer] =  reviewer_path
redirection_path[:collaborator] =  collaborator_path

@document.save ? redirect_to redirection_path[current_user.role.to_sym]
    :  (render :new)

We whittled the 5 potential execution paths down to 2. But, wait a minute…we can take advantage of Ruby’s metaprogramming abilities to take this to the next level of terseness.

redirection_path = Hash.new{|hash,key| hash[key] = ( %w(reviewer admin collaborator).include?(key.to_s) ?
                                instance_eval("#{key}_path") : instance_eval('user_path') )}

@document.save ? redirect_to redirection_path[current_user.role.to_sym]
    :  (render :new)

OK, I admit we’re drifting a little bit off Rookie territory right now. The point is: Ruby gives the power to choose succinct and unambiguous statements over nested conditionals, so just go ahead and use it.

List In-comprehensions

The functional aspects of Ruby, realized by blocks, are woefully underused by Ruby Rookies, especially when it comes to constructing lists based on other lists. Say, we need to extract the even numbers from a list and multiply them by 3. Then, we only want to keep the numbers below a certain threshold. Many Rookies will take this approach:

arr =[1,2,3,4,5,6,7,8,9,10]

new_arr = []
arr.each do |x|
  if x % 2 == 0
    new_arr << x * 3 if x * 3 < 20
  end
end

Which works fine, but doesn’t quite compare to the expressiveness of:

arr.select{|x| x % 2 == 0 }.map{|x| x * 3}.reject{|x| x > 19}

By using blocks, we are effectively passing functions to functions. Many Ruby methods take advantage of this to allow for some concise, powerful, yet very readable code. A Ruby Rookie might write some code that conditionally loads a number of Rake tasks like this:

load "project/tasks/annotations.rake"
load "project/tasks/dev.rake"
load "project/tasks/framework.rake"
load "project/tasks/initializers.rake"
load "project/tasks/log.rake"
load "project/tasks/middleware.rake"
load "project/tasks/misc.rake"
load "project/tasks/restart.rake"
load "project/tasks/routes.rake"
load "project/tasks/tmp.rake"
load "project/tasks/statistics.rake" if Rake.application.current_scope.empty?

Now take a look at how Rails is achieving the same thing in one single, elegant swoop:

%w(
  annotations
  dev
  framework
  initializers
  log
  middleware
  misc
  restart
  routes
  tmp
).tap { |arr|
  arr << 'statistics' if Rake.application.current_scope.empty?
}.each do |task|
  load "rails/tasks/#{task}.rake"
end

Leveraging the self-referential tap method and an Enumerator (each) to produce clean and maintainable code. Beautiful!

What’s Next?

This article covered coding anti-patterns. In an upcoming article, I’ll take a look at design anti-patterns, that is the way Ruby Rookies structure their code in a not-so-savvy manner.

Sponsors
Login or Create Account to Comment
Login Create Account