design patterns

ifception – if/else design patterns

2620 VIEWS

I HATE when my code looks like a monstrosity of conditions.

If this, do that;
else if this + 5, do this;
else if this - 5, do this*5;
else don't do anything.

There’s way too much logic involved, and no effort to simplify it. It’s hard for someone else to understand this, let alone you yourself when you look back at this later on. There has to be a quantified way to solve this code spaghetti! (There’s a great talk by Sandi Metz at Rails Conf 2014, All the Little Things, that addresses this.)

Her approach is very simple — Take your huge monolithic logic and break it down to smaller, manageable, and reusable components. The rest will sort out over time.

Here are a few tricks I use when reducing complexity.

Return From The Top

… If you have an ultra-simple function that compares a condition to return a value if it matches, and another if it doesn’t. Fairly straightforward scenario.

def pretty_time(time)
 if time
   time.strftime("%B %e, %Y")
 else
   nil.to_s
 end
end

Sure, it’s not too bad, but it can look even better if you do this:

def pretty_time(time)
return nil.to_s unless time
time.strftime("%B %e, %Y")
end

You can bid goodbye to your hanging else statements!

Pipes Are Awesome

The pipe symbol || is a favorite among Ruby devs. You can quickly assign default values when a variable is nil.

> nil || 6
> 6

But it’s not too friendly with strings.

> "" || "six"
=> ""

When you use this with the presence method, magic happens!

> “”.presence || “six”
> “six”

Try, Try again

If I got a dollar every time i ran into the nil:NilClass error, I’d be a millionaire! This made me paranoid to stick a if obj.present? everywhere.

if user.present?
 return user.name
end

Yuck! There’s a better way to do this. You just have to keep trying.

user.try(:name)

Simple! Now when your user doesn’t exist, it will fail quietly and return a nil.

This works with methods, too. If you have a method called fullname, try this out:

user.try(:fullname)

Send Off

I have this horrible habit of organizing background workers as if they were logical classes. What I mean is that if I have a few tasks that need to be processed for the user in the background, I put them all in one worker (if it’s not too big). It’s easier to manage, and I don’t have a mess of workers when others collaborate.

Since I have different tasks to be processed for each call of the worker, I pass an “identifier”— something that tells the worker class what function to perform.

class UserWorker
 include Sidekiq::Worker

 def perform(action, options={})
   if action == :welcome
     user.welcome_email(options)
   elsif action == :process
     user.process_profile_picture(options)
   elsif ....
   elsif ....
   end
 end
end

UGLY!

Fortunately, there’s a cleaner way of doing this. This is where Ruby’s powerful meta-programming comes in handy. Use send().

What is send?

send( ) is an instance method of the Object class. The first argument to send( ) is the message that you’re sending to the object—That is, the name of a method. You can use a string or a symbol, but > symbols are preferred. Any remaining arguments are simply passed on to the method.

So from the above mess, this is what my reduced worker looks like.

class UserWorker
 include Sidekiq::Worker

 def perform(action, options={})
   send(action, options)
 end
 def welcome(options)
   user.welcome_email(options)
 end
 def process(options)
   user.process_profile_picture(options)
 end
end

So much cleaner, and so much easier to extend.

Override

This particular tip needs to be taken with a pinch of salt. It’s not for everyone, and it might seriously damage your logic if not used carefully.

The great thing about Ruby being OOPs compliant is its ease of extending base classes like String, TrueClass, and Integer. When used carefully, you can do wonders.

I hate doing an if condition for booleans. It just feels wrong. Something like this:

if user.admin
 return “You are an admin”
else
 return “You are not an admin”
end

I’d have to create a specific function in the User class just to handle something as simple as printing out different messages for a boolean value. Here’s a neat trick:

class TrueClass
 def message(true_message, false_message)
   return true_message
 end
end

class FalseClass
 def message(true_message, false_message)
   return false_message
 end
end

This overrides TrueClass and FalseClass so you can do something as simple as this:

user.admin.message(“You are an admin”)

So simple! There are so many tiny adjustments you can make with this little trick!


Swaathi Kakarla is the co-founder and CTO at Skcript She enjoys talking and writing about code efficiency, performance and startups. In her free time she finds solace in yoga, bicycling and contributing to open source. Swaathi is a regular contributor at Fixate IO.


Discussion

Click on a tab to select how you'd like to leave your comment

Leave a Comment

Your email address will not be published. Required fields are marked *

Menu
Skip to toolbar