Refactoring is a simple concept, and yet it takes some learning to come to a point where refactoring is beneficial. Refactoring means making small adjustments to code throughout the life of a system in order to improve many aspects and give better long-term results.
Why Refactor Your Code?
The purpose for refactoring code can be for readability, creating a more modular design for ease of upgrading or extending, performance improvements, or things like experimentation with new methodologies and technologies. Refactoring code does not change behavior or output. It is only a reworking of the implementation details.
In programming, there is a practice called Red, Green, Refactor, which is a practice of Test Driven Development.
The purpose of the red phase is to define an aspect of the code you want to implement and prove that the test is being executed via its failure message. If you skip this step, you may end up with a test that doesn't get run when all your “other” tests show up as green (dead code).
The green step is rather simple: it's when you write the code to make that test pass.
The refactor step is a very valuable step in which you apply your learned knowledge of good code practices and make small adjustments without changing the codes behavior.
Code that doesn't get refactored as a system evolves is the common culprit for technical debt. Technical debt is a term used for the increasing amount of work that should be done to make a system more manageable. Without having a good understanding of many design patterns and the simple discipline of small refactoring as you go along, you can end up with quite a kludge of code that needs to be “fixed.”
When a person starts learning how to program, they start learning a process of how to think through problems. They get an initial understanding of setting state, evaluating it, and conditional behavior. This may seem good for learning a language but none of the good coding practices are instilled in them at this stage. This level of knowledge will likely lead to high code coupling and deeply nested conditionals that get harder to wrap your head around or maintain.
There are design patterns that help decouple and remove most of the heavy query method usage, such as Tell Don't Ask, Eastward Oriented Code, and Monads. Learning to apply patterns such as these will help relieve you of many of the headaches you experienced as a new programmer.
When a person has spent a lot of time with writing heavily coupled code and changing state across objects, it takes a bit of effort to change and think as one should about writing better code.
This is further evident when it comes to writing threaded programs. When a program can have several things try to access or change the same item, you end up with problems. Mutating state with coupling or threading is the source of many issues.
The Benefit of Decoupling
Coupling in programming is when objects know specific things about how to call and use other objects.
For an example of high coupling, imagine a method call like
user.profile.address.phone and then imagine the program gets changed so that
phone will now be a method call on a
communication object on
profile instead of
address. If you're calling methods like this, there is a high chance that this deeply nested call is written many times over your code base. The change to using the
communication object will require hundreds of changes throughout that code base.
To help decouple, it's better to move toward one method call on any one object when possible. So in the above example, we can write a method on
phone, which calls
profile will likewise call
address gets the
phone object back. So now to call the method, you simply use
user.phone. To make the change, you only need to change the code in one place: change
profile.phone to call
communication.phone instead of
address.phone and every place in the program that calls
user.phone will just work without needing to be updated.
So now the benefit of decoupling is becoming quite clear. It allows you to maintain large code bases, making change easier to manage.
Decoupling is important when integrating external dependencies. It may seem counterintuitive at first, but “creating more objects” to act as middle man is a great way to decouple code for easier changes.
Instead of calling the API/methods of external dependencies directly from various places in your code base, you define your own wrapper object(s) which will have each method call mapped to the appropriate method on your external dependency. This way you can swap your external dependencies one for another as easily as writing another wrapper object with the same methods on it.
!Sign up for a free Codeship Account
Refactoring in Rails
You may have heard stories, or you've experienced them, where the
User model ends up with a massive amount of code in it and the views have overly complex rendering logic in them. These “code smells” are a good hint that some refactoring may be in order.
By design, Rails has a way to organize data and behavior into specific areas to help maintanability and functionality. There are also additional design patterns you may implement along with with their standards. In the above scenario, the code may be implemented something like:
User Model - data store, organize data for presentation
Controller - selecting data for presentation and rendering the view
View - the layout with lots of conditional logic based on the user data
Here, the view and the user model are both doing more than they should; they are not following the single responsibility principle. The user model shouldn't be responsible for organizing the data for presentation, and the view shouldn't contain too much conditional logic for how it's rendered. These should be separated out into different objects. A better organization of your code would look like:
User Model - data store
Controller - selecting data and wrap in presenter and render view
Presenter - organize model data for presentation (no HTML)
Helper - any conditional view logic that will be reused (HTML allowed)
View - the layout with simple method calls directly on presenter objects and any helpers (conditional logic should be mostly moved out to either the presenter or helper)
Everything has been simplified towards single responsibility, and all the code should be much easier to read. Models will be smaller, and you don't have to guess what the view is doing.
In the controller, it may be wise to employ the Null Object Pattern for a
UserPresenter, where you create a
Guest object that has all of the same methods as the
User model and you can call your presenter with something like
UserPresenter.new(current_user || Guest.new). This way if no one is logged in to your site, the view can still call the same methods for your user and get the appropriate results for a guest.
There are many resources available to learn about refactoring and design patterns. Here are some excellent resources, many of which I enjoy revisiting to better reinforce my understanding of good practices and design. Many practices are best learned through demonstration, so I highly recommend a look at these.
Presenter Pattern (Blog Post) — Presenters in Rails by Nithin Bekal
Red, Green, Refactor (Video) — Roman Numerals Kata by Jim Weirich
Refactoring (Video) — Refactoring from Good to Great by Ben Orenstein
Design Patterns (Book) — Design Patterns in Ruby by Russ Olsen
Refactoring by Example (Book) — 99 Bottles of OOP by Sandi Metz
Refactoring Tips (Email Newsletter) — Refactoring Tips by Avdi Grimm
Eastward Oriented Code (Video) — Eastward Ho! A Clear Path Through Ruby With OO by Jim Gay
Monads (Video) — Refactoring Ruby with Monads by Tom Stuart
When it comes to refactoring code for easier understanding and maintenance, it's often done by creating extra simple representational objects to abstract away complexity.
Smaller amounts of refactoring can be done by following something like a style guide with something as simple as swapping out a nested conditional for guard clauses. What that does for you is to keep the code from having overly nested indentation toward the right, but rather to have the organization of code more aligned for easier comprehension and a little bit of elegance.
Refactoring is a must for any programmer who wants to be worth their salt. It's understandable if you haven't been in the practice of doing it up to now. But once you know better, it's your responsibility, both to yourself and to others, to better educate yourself in this discipline and to live by it. You should strive to write code in a way that future changes will not be met with great difficulty. So like any athlete, take some time to train yourself and hone your skill. The world thanks you in advance.