Refactoring code is when you restructure existing code without changing its external behavior.Basically, your aim is to make “bad” code better without changing the underlying functionality.
There are plenty of guides out there on refactoring your code. However, I find many of them talk about the ideology of well written and structured code without actually showing you what it looks like to refactor your “bad” code into “good” code. They might talk about high-level concepts like readability, extensibility, maintainability, testability, reduced complexity etc., which are all valid aims of the refactoring process, but they often fail to show examples of what this looks like in reality.
In this article I’m not going to talk about when you should refactor (personally I believe you should do it whenever you come across bad code), neither am I going to talk about why you should refactor (it reduces technical debt). Rather I want to look at some common, practical principles you can apply when refactoring and give examples of what they look like with real code examples. For the purposes of this article I’ll be using PHP code (as WordPress is written in PHP) but these principles will apply to any programming language.
Don’t Repeat Yourself (DRY)
Probably the most popular programming principle you’re likely to hear is “Don’t Repeat Yourself” (DRY). If you find yourself duplicating the same code a couple of times, then you should probably encapsulate the functionality in its own class or function and use said class or function to avoid repetition. This means you only need to fix the code in one place when the inevitable bug arises months down the line.
A good example of this is when two different but similar classes require some of the same functionality. Instead of duplicating the code across two different classes, you could create an abstract class which holds the common functionality and then make the other two classes
extend
the abstract class.
For example:
Becomes:
This is just a simple example of how you can structure your code to avoid duplication.
Splitting up Complex Functions
Another common programming problem that can lead to technical debt and hard-to-read code is complex functions or methods.
“Programs must be written for people to read, and only incidentally for machines to execute.” – Harold Abelson
Making your code easy for other people to read and understand is of utmost importance, so one way to deal with complex functions is to break them up into smaller more understandable (and testable) chunks.
Here’s an example of a complex function. Don’t worry about understanding everything it does. Just notice how complex it looks to glance at.
Wouldn’t it be much easier if that entire function looked like this:
This is much easier to read and understand. It’s amazing the difference simply splitting up large, complex bits of code can make to a codebase.
Another thing to note here is that you shouldn’t worry too much about having long, descriptive function names when splitting up complex functions like this. Remember that the aim is human readability, so trying to be too concise with your function names can actually make your code harder to understand. For example:
$this->get_att_inf( $post_id );
Is harder to understand than:
$this->get_attachment_s3_info( $post_id );
Splitting Up Complex Conditionals
Have you ever seen big conditionals that look something like:
Long pieces of code with conditionals are hard to read and understand. A simple solution to this is to extract the conditional code into clearly named methods. For example:
This makes your code far easier to understand for a future maintainer. As long as the conditional method is clearly named it should be easy to understand what it does without inspecting the actual method code. This practice is also known as Declarative Programming.
Replacing Nested Conditionals with Guard Clauses
Another way to refactor complex conditionals is to use what are known as “guard clauses”. Guard clauses simply extract all conditionals that lead to calling an exception or immediate return of a value from the method and place it at the beginning of the method. For example:
Here you can see how quickly you could end up “conditional hell” if the method got any more complex. However if we refactored this method to use guard clauses it would look like this:
Now even if the method gets more complex, it’s not going to be a maintenance headache down the road.
Refactoring Loops and Conditionals Using Functional Methods
This is a slightly more advanced type of refactoring that is used more heavily in functional programming languages and libraries (these kind of functions are used a lot in JavaScript land). You may have heard of functions like
map
and reduce
and wondered what they are and how to use them. It turns out that these methods can dramatically improve the readability of your code.
This example is inspired by Adam Wathan’s great screencast on the subject (you should check out his upcoming book) and is based on using Laravel Collections. However, I’ve adapted the example to work with standard PHP functions.
Let’s look at two common scenarios and see how they can be improved using functional methods. Our example below fetches a bunch of
$events
from an API and calculates a score based on the event type:
The first thing we can improve is replacing the
foreach
loop with a map
function. A map function can be used when you want to create a new array from an existing array. In our example we’re creating a $types
array from the $events
array. PHP has an array_map
function that will let us write the first foreach
in the above code like this:
Note: You need to be running PHP 5.3+ to be able to use anonymous functions like this.
The second thing we can do is break down the big
switch
statement so that it’s a bit easier to process.
But we can actually go one step further here and use PHP’s
array_reduce
function to calculate the $score
in a single function. A reduce function takes an array of values and reduces them to a single value. We’re going to do that here to calculate our $score
.
Putting it all together we now have:
Much better. No loops or conditionals in sight. You could imagine how, if more complexity was required to find
$types
or calculate the $score
down the line, it would be easy to refactor the map
and reduce
function calls into separate methods. This is now possible because we’ve already reduced the complexity down to a function.
No comments:
Post a Comment