If you aren’t familiar with the term, refactoring is the act of improving the quality of code without changing what it does. This will make your code a lot easier to work with. In this post you will learn some common refactoring techniques, let’s get started!
One of the most common refactorings is the one known as ‘extract method’. In this refactoring you move some code from an old method into a new method. This will allow you to have smaller methods with descriptive names.
Let’s take a look at an example:
1 2 3 4 5 6 7 |
@sold_items = %w( onions garlic potatoes ) def print_report puts "*** Sales Report for #{Time.new.strftime("%d/%m/%Y")} ***" @sold_items.each { |i| puts i } puts "*** End of Sales Report ***" end |
We can start by extracting the ugliest part of this method, the current date generation.
1 2 3 4 5 6 7 8 9 |
def print_report puts "*** Sales Report for #{current_date} ***" @sold_items.each { |i| puts i } puts "*** End of Sales Report ***" end def current_date Time.new.strftime("%d/%m/%Y") end |
This already reads better, but we can go a bit further. Let’s extract a few more methods to end up with this code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
def print_report print_header print_items print_footer end def print_header puts "*** Sales Report for #{current_date} ***" end def current_date Time.new.strftime("%d/%m/%Y") end def print_items @sold_items.each { |i| puts i } end def print_footer puts "*** End of Sales Report ***" end |
Yes, the code is longer now, but isn’t this easier to read? Don’t be afraid of small methods, they are good for your code.
You can also refactor complicated conditionals into methods to make them more readable.
Example:
1 2 3 4 5 |
def check_temperature if temperature > 30 && (Time.now.hour >= 9 && Time.now.hour <= 17) air_conditioner.enable! end end |
The second part of this if
statement is not super-readable, so let’s extract it into a method:
1 2 3 4 5 6 7 8 9 |
def check_temperature if temperature > 30 && working_hours air_conditioner.enable! end end def working_hours Time.now.hour >= 9 && Time.now.hour <= 17 end |
What we have done here is to give our condition a descriptive name, which makes things a lot easier for future readers of this code (including you!).
Sometimes you have a big method that got out of control. In this case it might be hard to refactor because big methods tend to have many local variables. One solution is to use the ‘Method Object’ refactoring.
“Big methods are where classes go to hide.” – Uncle Bob
Let’s see an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
require 'socket' class MailSender def initialize @sent_messages = [] end def send_message(msg, recipient = "blackbytes.info") raise ArgumentError, "message too small" if msg.size < 5 formatted_msg = "[New Message] #{msg}" TCPSocket.open(recipient, 80) do |socket| socket.write(formatted_msg) end @sent_messages << [msg, recipient] puts "Message sent." end end sender = MailSender.new sender.send_message("testing") |
To perform the refactoring we can create a new class and promote the local variables into instance variables. This will allow us to further refactor this code without having to worry about passing data around.
Hey! Want to improve your Ruby skills in a big way? Check out my New Ruby Course
This is the MailSender
class after the refactoring:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class MailSender def initialize @sent_messages = [] end def deliver_message(message) send(message) @sent_messages << message puts "Message sent." end def send(msg) TCPSocket.open(msg.recipient, 80) { |socket| socket.write(msg.formatted_msg) } end end |
And this is the new class we introduced:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Message attr_reader :msg, :recipient def initialize(msg, recipient = "blackbytes.info") raise ArgumentError, "message too small" if msg.size < 5 @msg = msg @recipient = recipient end def formatted_msg "[New Message] #{msg}" end end sender = MailSender.new msg = Message.new("testing") sender.deliver_message(msg) |
Using these refactoring techniques will help you adhere to the Single Responsibility Principle and keep your classes and methods under control.
If you enjoyed this article please share it with your friends so they can enjoy it too