Refactoring: Clean your ruby code with design patterns
Code refactoring can be defined as “the process of introducing small and incremental changes to leave the code in a better state than it was.”. When refactoring your code you have to consider two things: no new functionality should be added and the external behavior should not be affected.
One of the biggest challenges as a Ruby on Rails Developer is to keep your code clean, simple and easy to maintain and that is why we are always refactoring our code.
There are several techniques that a developer can follow to improve their code by code refactoring, such as extract method, move method, move field, switch statements, etc. If you are not familiarized with them, please visit the Refactoring Guru site .
Another technique developers try to follow is to apply good design patterns to their code. In this post we’ll try to go over some of the documented design patterns and how you can apply them to your Ruby code.
Design Patterns
A design pattern can be described as “typical solutions to commonly occurring problems in software design. They are blueprints, that can be taken and customized to solve a particular design problem in your code”. So design patterns are not rules but guides that can help you to find the best solution given a particular situation. That is why, as pragmatic developers, we have always a good design pattern on the hand.
Explaining each pattern in detail is out of this article’s scope, so if you are not familiar with the presented design patterns, a good reference is the book Design Patterns: Elements of Reusable Object-Oriented Software , written by the famous Gang of Four (GoF).
Since there are 23 design patterns cataloged in the book, our intention here is to cover the most used of them and how they can be useful to clean our Ruby code.
Factory Method
When we think about a factory, what comes to mind is a place that builds different products that share common characteristics: cars, electronics, toys, medicines, cakes and several others. You expect that all car factories will build cars and these cars will have different colors, sizes, shapes, etc, right?
Now, let’s say that we have to build an application and one of the functionalities is to create employees. The employees can be one of three different types: full-time, part-time and contractor and each type of employee will have a different hourly rate. So the requirement is to send a hash with the employee information and based on the type, we need to create the correct employee object.
Let’s create the Employee
class:
#lib/factory/employee.rb
class Employee
def self.create(params)
employee = Employee.new
type = params[:type]
case type
when "fulltime"
employee.type = :full_time
employee.hourly_rate = 60.00
when "parttime"
employee.type = :part_time
employee.hourly_rate = 50.00
when "contractor"
employee.type = :contractor
employee.hourly_rate = 20.00
end
employee
end
end
This code works. It creates an employee and fills its information based on the given type. But the code is not beautiful and imagine the trouble if we need to change an existing employee type or even add a new one.
To improve this code we could apply a refactor to extract the business logic of create employees to another class, then the Employee class will have only the responsibility to call the correct employee constructor. The process of extracting the creation business logic to specific classes and methods is the goal of the Factory Method.
The basic principle of this pattern is to have factories creating products, a metaphor to a real factory. With the Factory Method we will change our code to:
# lib/factory/employee.rb
class Employee
def self.create(params)
EmployeeFactory.create_employee(params)
end
end
# lib/factory/employee_factory.rb
class EmployeeFactory
def self.create_employee(params)
case params[:type]
when "fulltime"
FullTimeEmployee.new
when "parttime"
PartTimeEmployee.new
when "contractor"
FullTimeEmployee.new
end
end
This way the EmployeeFactory
doesn’t need to know how to create each type of employee, they are all created the same way. The logic to create employees is defined on the EmployeeFactory
class, that will call specialized constructors for each employee type. The responsibility of these constructors is to know only its own necessary information, like the hourly_rate, for example. That way if we ever need to add a new employee type it will be much easier.
Strategy
Now let’s say that our next functionality is to calculate the net salary of the employees. The net salary is the salary that they will receive once all the taxes are applied.
Since different countries have different taxes rules our code needs to handle this when perform the calculation.
Let’s implement the EmployeeSalary
class:
# lib/strategy_sample/employee_salary.rb
class EmployeeSalary
ARG_TAX = 0.5
USA_TAX = 1.3
BRA_TAX = 0.8
def self.calculate_net_salary(country, amount)
country_taxes = case country
when "Argentina"
(amount * ARG_TAX)
# Argentina tax calculation
when "USA"
(amount * USA_TAX) + 200
# USA tax calculation
when "Brazil"
(amount + 500) / BRA_TAX
# Brazil tax calculation
amount - taxes
end
end
We can easily see that the calculate_net_salary
will massively grow each time that we need to add a new country. This situation breaks the open/closed principle that says: “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”. This means that to make a class do new things you shouldn’t need to change the class itself.
If we can not extend the calculate_net_salary
method to make a new country calculation without a lot of modification we are not following this principle. To solve this situation we can apply the Strategy Pattern to refactor our code.
The Strategy is a behavioral design pattern that suggests to take a class that does something important in a lot of different ways and extract all these algorithms into separate classes called strategies. The original class, called context, will receive a field that references to one of the strategies.
With that concept in mind we can refactor the context class:
# lib/strategy_sample/employee_salary.rb
class EmployeeSalary
def initialize(strategy)
@strategy = strategy
end
def initialize
@strategies = {
'USA': UsaTaxes,
'ARG': ArgentinaTaxes,
'BRA': BrazilTaxes
}
end
def self.calculate_net_salary(amount, country)
strategy = @strategies[country]
strategy ? amount - strategy.taxes(amount) : amount
end
end
Then we will create one strategy for each one of the necessary country calculations.
# lib/strategy_sample/strategies/usa_taxes.rb
class UsaTaxes
def self.taxes(amount)
# USA tax calculation here
end
end
# lib/strategy_sample/strategies/argentina_taxes.rb
class ArgentinaTaxes
def self.taxes(amount)
# Argentina tax calculation here
end
end
# lib/strategy_sample/strategies/argentina_taxes.rb
class BrazilTaxes
def self.taxes(amount)
# Argentina tax calculation here
end
end
That way the single responsibility of the EmployeeSalary
class is to delegate the calculation work to a linked strategy instead of executing it on its own.
Wherever we need to get the net salary we should call the EmployeeSalary
class like this:
Taxes.new.net_salary(1000, "BRA")
Now if we need to add a new country we just need to create a new strategy and add to the context class. With this refactor we can keep all the concerns separated and the code is cleaner and easier to maintain.
Next steps
In this post we talked about two very useful design patterns and how we can use them to improve code quality. We hope this was helpful for you. Keep following our blog, we will talk more about this subject soon.