There are a lot of refactoring patterns available out there for us. I assume that most of us use these patterns, at certain times without being aware that those refactoring steps are defined as a pattern in the past. In this post, I will take you through an example of refactoring Ruby code with the Extract Class pattern by using Test-Driven Development.
Let’s dive in!
What went wrong at the test?
Say we are professors in university and we compile a CSV file with our students’ grades. Some of them are good, some are not that good. We want to have a small informal meeting with the students that did not pass the exam, in a classroom and review the tests and discuss what went wrong. Given that there are a lot of students in our class, writing an email for each of the students is painful. Well, good we are programming professors, so we can write our own little program to automate sending those emails:
As you can see, it’s quite a simple class. It takes a CSV file path and parses it. For each row it checks if the points are less then the minimum amount to pass the exam. If so, then a customised email is sent to each of the students.
Now, if we take a moment to think about the design of this class, there’s a question that we can ask ourselves: what does this class do? Although the question is simple, the answer can reveal something very interesting.
The class parses a CSV file and sends an email to each of the students that failed the test. You see, the word “and” is bolded because it points out to a code smell - violation of the Single Responsibility Principle. If you don’t know what SRP is, I recommend you check out my blog post on SOLID principles.
So, how can we refactor our class? Well, one of the options is using the Extract Class pattern. Let’s check it out.
Extract Class pattern
This pattern falls into a group of patterns that are used for moving features (or functionalities) between objects. The benefit of this refactor is to make your classes adhere to the SRP. Classes that adhere to the SRP are much easier to understand and expand onto.
Now,back to the design of the class. Since we agreed that it breaks the SRP, we
need to decide which functionality we will move out into a new class. For our
example, we will let the
Parser class just parse the CSV, while we will create
a new class called
Exam which will contain all of the data and behaviour
around the exam data that we parse from the CSV. After that, we will create a
Mailer class, whose role will be just to send the email.
With that, we will have three classes into play - all having just one
responsibility, whether is representing an
Exam, parsing a CSV or sending
The first step will be to wish the interface of the
and use them in the
Parser class. This is called “programming by
Good. As you can see, we’ve added the new classes in
which radically simplified the readiness and simplicity of it. Since we now
have the classes in place, let’s take a step back and use TDD to create these
Introducing the Exam
As we said before, the
Exam class will hold the data and represent the
behaviour of an exam. But what does that mean? Well, simply put, it will have
methods that will decide if an exam is failed based on the data (points). Let’s
TDD our way through this and write some tests before we implement the class:
I assume the tests are quite self explanatory. The constructor will take a hash
as an argument and extract the points from it. Then the
Exam#failed? methods will check if the exam is passed or failed. The rule here
is that an exam is failed if it has less than 50 points scored. Let’s implement
Really easy, right? If we run the tests, they will pass. Additionally we will need the exam to contain the data for the student which has taken the exam. Some of you might think that a student should be a separate object and I would say that you are right. Also, others might say that there can be separate classes for a failed and passed exams.
But, for this example we can keep it really simple. Let’s add some tests for the attribute readers:
Implementing these methods is quite easy as well:
That is our
Exam class with all of it’s glory. Did you think about what we did
in this step? Really simple stuff - noticing that there is room for a class to
encapsulate all of the required data so it can answer to the question “is this
exam failed or not?”. We first wished what the interfaces of our classes should
be and then we created the first class, using TDD.
This is the Extract Class pattern at it’s core - reducing complexity and improving readibility by extracting data and behaviour to a new class.
That would be it at this moment. The
Exam holds enough behaviour and data so
we can continue with the other classes.
Building the mailer
The next step is the
ExamMailer. The mailer should contain only the behaviour
required to send the emails - nothing else. As we decided in the first step, the
mailer will have two methods:
mailer class will use the
Mail gem to send emails.
Let’s write some tests around them first and implement them after:
Now, there’s a bit of boilerplate code here, which deals with the Mail gem. In
the beginning of the file, we set the delivery method of the Mail gem to be
:test. This means that the gem will hold all of the emails that are sent in
memory, without trying to make an actual delivery. Also, in our
we invoke the
Mail::TestMailer.deliveries.clear method, which clears the
deliveries collection before running each test. This is done so we have a fresh
collection of sent emails for each test, which helps with avoiding any
As you can notice in the tests, we assert on the recipient email, on the e-mail subject and on the e-mail body. With this step, we want to make sure that the most essential parts of the e-mail (recipient, body and subject) are correct.
Extracting the mailer logic from the initial script to a new mailer class is rather simple:
Most of the magic lies within the
send_mail! method. It builds a new
As you saw in this step, we extracted the mailing logic from the script to a new
well-defined class. The next (and last step) is reviewing the original
class and pondering a bit with it.
Back to the parser
After we did all of the hard work around extracting two new classes and writing
tests for them, it is time to take a look at the
Parser class. I would say
that improving it further would be over-engineering it, at least in the scope of
this article. The only thing that I would like us to think about is the name of
the class. Does
Parser do it?
This is quite subjective and I would guess some of you wouldn’t agree with me,
but in my mind the
Parser is quite broad term. In this instance, I think that
Grader is a better name for it.
Taking a step back
As you could see in this article, there’s quite a bit of work around refactoring existing code. There are more than a two dozen patterns around refactoring, with Extract Class being one of the simplest ones. Having this said, I would like to ask you a question:
Do you really need to think in patterns? Do we really need to have a “predefined sequence of steps” to make our code better?
I think that it’s not really about applying a certain pattern, it’s more about caring about the design of the code and writing tests for it. As a rule of thumb, I always try to think about the five SOLID principles when writing my code. And when you are refactoring - understand the motivation and the context behind the code that you are refactoring, without focusing on which pattern you will apply.