Refactoring
Basic VMC Refactoring
Basic refactoring follows a pattern of extracting variables, then methods then classes, or VMC. By slowly extricating different pieces of our code we can start to created more cohesive encapsulated programs with well organised modules of functionality.
Variable Extraction
Code that is all compressed onto one line without the use of variables can confuse the different pieces of functionality that a method might have.
# combines result of adding two words and puts them with a message.
def combine_words(word_1, word_2)
puts("The two words together are " + word_1 + word_2)
end
In the code above, the combining functionality and the printing functionality are combined onto a single line. Even though these are separate responsibilities they appear entwined when presented this way in the body of our method. Furthermore the lack of variables means that the code comments are widely redundant, because even though we makes reference to a #result
and #header
these are never referenced in our actual code.
# combines result of adding two words and puts them with a message.
def combine_words(word_1, word_2)
message = "The two words together are "
result = word_1 + word_2
puts(message + result)
end
We can see in the code above the elements have been refactored into separate variables making the functionality of this method more clear.
Method Extraction
After clarifying the above examples functionality with variables we can see that this method has more than one responsibility:
- Adding the words together
- Outputting the result.
This means that we could refactor the code further to have a method that outputs words rather than doing the combining and the printing inside the combine_words
method. In the below example this refactoring has been completed and because the print_words
method is not used by any code outside of the combine_words
method it can be safely set as private
. Note: I’ve also amended this code to now be wrapped in a class Words
, however this was intended to be implicit in the above examples.
class Words
def combine_words(word_1, word_2)
result = word_1 + word_2
print_words(result)
end
private
def print_words(result)
message = "The two words together are "
puts(message + result)
end
end
We now have clear responsibilities based on different methods within this class, each piece of code has a clear responsibility and does exactly what it says it does.
Don’t be afraid to extract a line with multiple clauses into multiple methods.
def checker
time < 5 && location == "home"
end
The method above has two evaluation clauses inside it that can be refactored into two separated predicate methods. Such that responsibilities are made clear again.
def checker
time? && home?
end
def time?
time < 5
end
def home?
location == "home"
end
Class Extraction
If you can extract a method within a class into a separate private
method which the class delegates responsibility to, there is a good chance that you can extract this method into its own class.
In our example we could probably refactor the print_words
method into its own class entirely as printing stuff doesn’t really have anything to do with managing how words interact.
class Words
def combine_words(word_1, word_2)
result = word_1 + word_2
Printer.new.print_words(result)
end
end
class Printer
def print_words(result)
message = "The two words together are "
puts(message + result)
end
end
Now out code is clearly managed by two separate classes that interact keeping responsibilities entirely separated.
Other extractions
We can go a little further with the above example. Currently the message in the Printer
class is always the same, so it can be safely extracted into a constant. Furthermore the Words
class could implement an instance variable that contains an instance of the Printer
class so it doesn’t need to create a new printer every time the combine_words
method is called.
class Words
def initialize
@printer = Printer.new
def combine_words(word_1, word_2)
result = word_1 + word_2
@printer.print_words(result)
end
end
class Printer
MESSAGE = "The two words together are "
def print_words(result)
puts(MESSAGE+ result)
end
end
General Refactoring Patterns
Three instances of repetition
When should you refactor? There is a balance to be struck between refactoring too early by second guessing how your code base will evolve and refactoring too late when there are so many problems with your code that changes become difficult. The rule of thumb is to refactor when you see three or more instances of code duplication. You can then be sure that you are seeing a code pattern that will need to dried and extracted.
Code review
Code reviews are usually conducted once a pull request has been made on github as a way of ensuring the quality of code before it is merged into the main project.
To offer feedback on pull request code visit the pull request
of your repo and select the request you want to review. You can see new additions in the request’s code by going to changed files
tab which displays additions and removals is green and red respectively.
You can leave comments on single or multiple lines by using the +
symbol next to code lines and “adding a comment”.
Written with StackEdit.