How to Solve Coding Anti-Patterns for Ruby Rookies

Share this article

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.

Frequently Asked Questions (FAQs) about Ruby Coding Anti-Patterns

What are some common anti-patterns in Ruby coding?

Anti-patterns are common practices that seem helpful initially but can lead to less efficient or problematic code in the long run. In Ruby, some common anti-patterns include using the wrong data structures, overusing global variables, and not following the DRY (Don’t Repeat Yourself) principle. For instance, using arrays when a hash would be more efficient can slow down your code. Similarly, overusing global variables can make your code harder to understand and maintain. Lastly, repeating code instead of reusing or refactoring can lead to bloated and less readable code.

How can I avoid anti-patterns in Ruby?

The best way to avoid anti-patterns in Ruby is to understand the principles of good coding practices. This includes understanding the right data structures to use, limiting the use of global variables, and following the DRY principle. Additionally, regular code reviews and refactoring can help identify and eliminate anti-patterns. Using automated testing tools can also help catch potential issues early.

What is the ternary operator in Ruby and how is it used?

The ternary operator in Ruby is a shorthand way of writing an if-else statement. It is used when you want to assign a variable based on a condition. The syntax is: condition ? value_if_true : value_if_false. For example, x = (a > b) ? a : b assigns the larger of a and b to x.

What are the potential pitfalls of using the ternary operator in Ruby?

While the ternary operator can make your code more concise, it can also make it harder to read if overused or used in complex conditions. It’s best used for simple, straightforward conditions. For more complex conditions, it’s usually better to use a full if-else statement for clarity.

How can I improve my Ruby coding skills?

Improving your Ruby coding skills involves a combination of learning and practice. Reading books, tutorials, and articles can help you understand the principles and best practices of Ruby coding. Practicing by writing code, solving problems, and working on projects can help you apply what you’ve learned and gain hands-on experience. Participating in code reviews and getting feedback from more experienced developers can also be very helpful.

What is the DRY principle in Ruby?

The DRY (Don’t Repeat Yourself) principle is a fundamental concept in Ruby and other programming languages. It states that you should avoid duplicating code and instead, aim to make every piece of code single-purpose. This makes your code more efficient, easier to maintain, and less prone to errors.

How can I apply the DRY principle in Ruby?

You can apply the DRY principle in Ruby by reusing code through methods, modules, and classes. Instead of repeating the same code, you can write a method that performs that task and call it whenever needed. Similarly, you can use modules and classes to group related methods and reuse them.

What are global variables in Ruby and why should they be avoided?

Global variables in Ruby are variables that are accessible from anywhere in the code. While they can be convenient, they can also make your code harder to understand and maintain, as it’s not clear where they are being changed. It’s generally better to use local variables, instance variables, or class variables, which have more limited scope.

What are the best practices for using data structures in Ruby?

The best practices for using data structures in Ruby depend on the specific needs of your code. Arrays are best for ordered collections of items, while hashes are best for key-value pairs. It’s important to choose the right data structure for each task to ensure your code is efficient and readable.

What are some resources for learning more about Ruby coding best practices?

There are many resources available for learning more about Ruby coding best practices. These include books like “The Well-Grounded Rubyist” and “Eloquent Ruby”, online tutorials and courses, and websites like RubyGuides and the official Ruby documentation. Participating in coding communities like StackOverflow and GitHub can also provide valuable insights and feedback.

Fred HeathFred Heath
View Author

Fred is a software jack of all trades, having worked at every stage of the software development life-cycle. He loves: solving tricky problems, Ruby, Agile methods, meta-programming, Behaviour-Driven Development, the semantic web. Fred works as a freelance developer, and consultant, speaks at conferences and blogs.

Best PracticesGlennG
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week