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 if
s and elsif
s 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!
More from this author
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.
-
http://careersreport.com Raymond Dubois
-
http://careersreport.com Raymond Dubois
-
http://careersreport.com Raymond Dubois
-
http://careersreport.com Raymond Dubois
-
http://careersreport.com Raymond Dubois
-
http://careersreport.com Raymond Dubois
-
http://careersreport.com Raymond Dubois
-
http://careersreport.com Raymond Dubois
-
http://careersreport.com Raymond Dubois
-
http://careersreport.com Raymond Dubois
-
http://careersreport.com Raymond Dubois
-
http://careersreport.com Raymond Dubois
-
http://careersreport.com Raymond Dubois
-
Test McTest
-
Fred@Bootstrap
-
-
biff
-
Fred@Bootstrap
-
-
Fred
-
Fred@Bootstrap
-
-
http://seawolfsanctuary.com/ ben
-
Fred@Bootstrap
-
http://seawolfsanctuary.com/ ben
-
-
-
Tueksta
-
Tueksta
-
Fred@Bootstrap
-
Tueksta
-
-
-
Fred@Bootstrap
-
-
Brian
-
Michel Pigassou
-