There is a high risk with introducing new patterns that something might go wrong, and you'll be forced to throw away the solution. This has happened to me many times a long time before when I was junior dev; however there are habits you can chose to adapt instead which is much more effective, and it is all about good refactoring habits.
Err... habits?
Yes! Okay this'll take some time to explain, but here we go:
It may be tempting to refactor towards a pattern whenever there is downtime, unfortunately it is at these times that you'll add a design that will eventually break in the future. There is nothing wrong with refactoring towards a pattern, but there is a high risk of it ending up to be a sophisticated process of code bloat. You won't notice it until you have to tear that structure down again. I'm pretty sure we've all hit that brick wall at times: the one where your code appeared so beautiful actually made it more difficult to work with the week after.
This is from a business perspective quite wasteful "gold plating". It is better to refactor parts of the code that changes often, so that you use a pattern that makes the changes easier to implement. Thus you should think about refactoring whenever you get a ticket such as a Change Request or a Defect Report to do on a part of the system that has recurring changes. Do a design UML-sketch and estimate the time for you to refactor so it can easily deal with the repeating changes. If you want want a good first step to refactor the right way then figure out what often changes or varies in your code and encapsulate that behavior. Hence the first habit that your team should take:
Habit No. 1 — Refactor that what changes often
Design Patterns are inherently complex, so we have to be careful about applying them. The claim that design patterns are complex is heavily disputed since patterns really are meant to provide a common language between developers. Unfortunately it is very common in team settings that the team will perceive a pattern as complex. Just because you know about that pattern doesn't mean that everyone else in your team knows about it.
Let me repeat that over the common programmer ego (I sure wish someone would when I was some years younger):
Just because YOU know it, doesn't mean that EVERYONE ELSE does.
It is better to just take it little at a time, as more Change Requests come in you can easily home into a pattern that the team will be able to use. Some refactorings later and you'll end up with a useable pattern, that is easy to explain, because you did it one step at a time, and that most of the team has already seen it coming, because you did it in small steps at a time. Here are some conversational examples, just in case you need some inspiration on how small the steps would be:
- "Hey, does anyone mind if I rename this method into something more descriptive?"
- "So this class can be an enum instead. Makes it more type safe y'know."
- "Do you mind if I introduce a parameter class for this instead?"
- "This code is duplicate of this over here. Is it okay for me to put that (encapsulate) it to it's own class?"
- "Lemme just let the methods return the
Iterable<T>
interface instead."
- "Does anyone mind if I did a Visitor for this switch case instead?"
...and so on. Next thing you know, you have introduced a design pattern and everyone has learnt it in small steps and are happy about it. Hence:
Habit No. 2 — Refactor in small steps
Next habit has to do with trust and confidence. To feel confident about refactoring, you need to be sure that you don't break anything whenever you refactor and change the code. In order to reassure that you don't break behaviors, start writing new units of code together with unit tests to make sure you have code that actually works.
There is no excuse anymore to ignore unit testing really. All programming languages and environments have some kind of a unit testing functionality or a framework. If not then I hope your language is Turing Complete enough to create a testing framework yourself.
I have an anecdotal example about this
The most complex codebase I had to work with (at a well-known company that dealt with confectionary) was Javascript where I was put in charge to add a couple of new functionalities into a spaghetti mess. They didn't have any modules nor any unit tests on their own codebase, even though their code guidelines was full of wishful thinking on it all existing.
Naïve that I was and to be sure I didn't break anything I wrote some unit tests to check how the codebase worked. I could see how the inputs worked and have some validation on what the outputs became.
I then proceeded to write my own javascript modules that I did TDD-style that became well tested (with both unit and functional testing). This benefited me the most where some hairy string handling was required, and unit tests made sure I did the right thing.
When I handed over the finished result it left a lot of tech leads with their jaws dropped to the floor because here was a bloke that actually followed their coding standards and delivered code with some proof of confidence. The testing code was later given to a team that were given the task to refactor the code even further.
The habit of writing tests always pays off in the end, it will make things easier to refactor as you can see during development if you broke something. It can also be used to verify that your new code base works just as well as the old code base. Most importantly, it can be used to locate and mark bugs for other developers to fix. Do make sure you estimate enough time for you to work on it while you research the code base.
Anyhow, boiling down to a tl;dr version we have the following habit:
Habit No. 3 — For new components and classes, write unit tests
So how do you refactor towards a design pattern? Well, a lot of pattern design fashionistas tend to forget that the patterns were built upon principles. Now there are many to take in, but the basics is something you definitely need to learn:
The SOLID principles. Trust me, you'll become be a better person because of it. Also there are the two basic ones from the beginning chapter of the GoF book on Design Patterns:
- "Program to an 'interface', not an 'implementation'." (Gang of Four 1995:18)
- "Favor 'object composition' over 'class inheritance'." (Gang of Four 1995:20)
Once you know all of them and their applications, you'll be a design pattern guru, delivering consistently awesome and clean code. Also, when you write unit tests for your new code, you will eventually find out that well-made test code actually forces the design of your code base to follow SOLID principles. Because you follow the principles behind the patterns; next thing you know you've indadvertedly applied some kind of a pattern that fits with your code base.
Habit No. 4 — Prefer (SOLID) principles over patterns
Hope these habits puts you into the right path of applying patterns.