Refactoring for Beginners

Refactoring.png

As a beginner programmer, you've probably heard the term "refactoring". Maybe it came up at a meetup or during an interview and you didn't take the opportunity to ask what it really means. Keep reading to learn what refactoring is and how to practice it as a beginner.

What Is Refactoring?

Refactoring, simply put, is the process of improving code that you already wrote to make it more organized, easier to add on to and more in-line with common design principles.

Why Should I Refactor My Code?

By making refactoring part of the way you develop a project, you're ensuring two things:

  • You focus on solving the right problem at the right time. In other words, you start by writing some code that works, and you improve it later on.
  • You end up with clean, well-designed code that is easy to add on to and easy for other developers to collaborate on.

Refactoring is a common part of the development cycle. You may have heard the term "red, green, refactor". This describes the process of first creating a test for code you haven't written yet. If you run that test, it will fail, or be "red". Then, you write some code (any code!) just to get that test passing, or make your test run result in "green". Once you get your tests passing, you know you have code that works. Only then should you look over your code and clean it up.

Whether or not you practice this test-driven approach, the basic idea of starting with some code that behaves how you want it to behave and solves the problem at hand is the right one. If you focus too much on implementing the best solution, you might end up with no solution at all. Get something working, then make it better.

How Can I Refactor My Code?

Exactly how you go about refactoring your code depends on the project you're working on, but there are three general approaches

  • Making our code more semantic and declarative
  • Improving performance
  • Improving design

Let's take a look at some brief examples of how we could refactor some code using each of these approaches.

Scenario 1: Making Our Code Semantic and Declarative

Semantic code is code that speaks for itself. Declarative code is code that tells your program what to do, not how to do it.

Here's a basic example of some code that isn't very semantic or declarative in Ruby:

names = ["Sophie", "Zoe", "Victoria", "Richard"]

greetings = []

names.each do |name|
  greetings << "Hi, #{name}!"
end

greetings 
# => ["Hi, Sophie!", "Hi, Zoe!", "Hi, Victoria!", "Hi, Richard!"]

This approach required us to do a lot of work! In order to create a new array that is the result of doing something to each element in the original array, we first created a new variable, greetings. Then we had to manually add each new element to the greetings array as we iterated over the names array.

We had to explicitly tell our code how to make the greetings array, i.e. push each new element into the array using <<. data-preserve-html-node="true" This is an imperative approach.

We want to make our code less imperative and more declarative. In other words, we just want to tell our code what to do, not how to do it.

We can make our code more declarative, and more readable, by using another iteration method: collect. Collect allows us to iterate over our array and collect (get it?) the result of each step of the iteration, all in one go.

greetings = names.collect {|name| "Hi, #{name}!"}

This refactor gave us clear and readable, i.e. semantic code. Even if you don't know exactly how the collect method works, you could probably puzzle it out just from reading this one line.

Scenario 2: Making Your Code More Efficient

Another easy improvement to make when refactoring your code is to make any database queries more efficient. Let's take a look at a simple example using Active Record.

You have a wildly popular web app, PetBook, which allows dogs and cats to grow their social networks. If a pet browses a list of other pets to befriend at www.petbook.com/pets, they see the name of the pet and the name of the pets' owners. The code that backs it looks like this:

# PetController
pets = Pet.all 

# pets.html
<% pet.each do |pet| %>
  name:  <%= pet.name %>
  owner: <%= pet.owner.name %>
<% end %>

This means that every time we call pet.owner.name, we are making another database query. Instead, we can refactor our code by using the includes method to get the owners back from the database, along with our pets in the original query. That way, when we call pet.owner, we are not making another database query.

pets = Pet.includes(:owner)

Scenario 3: Improving Design

Different languages and frameworks have their own design patterns. Additionally, what is considered the "right" design may change over time. However, there are some principles you can always apply to improve the design of your code.

Helper Methods

If you have one method or function that is super long, try breaking up the code into a few helper methods.

Keep it DRY

If you find yourself using the same few lines of code again and again, that is a red flag. Instead of having repetitive code, wrap up that repeated code in its own method. Then, call that method everywhere you were using the repeated code.

Single Responsibility Principle

The SRP states that a method, function or class should have one job. For example, if you have a Pet model in your Rails app, let that model be in charge of pet-specific information only. It should be a wrapper for pet data that is fetched from the database. If your app has another feature that allows pets to search for nearby dog parks, the Pet model is NOT the right place for that code.