Refactoring code is not only inevitable but it’s necessary in software development. When I look back at my best designs and implementations, I find the same pattern: iteration of design, iteration of implementation, iteration of test cases. The more I iterate, the better the end result seems to be. When I first started earning my living as a software developer, I used to think that I was an inexperienced developer, so I had to iterate to find the right design, right implementation, right test case. “If I have the experience and wisdom of a seasoned software developer, I don’t have to iterate on anything” I used to think. I don’t hold that view anymore because I have enough experience in software development now and I’ve also been around what’s called “seasoned” software developers. One thing I learned is that experience does not necessarily translate into better design and implementation but iteration almost always produces better results, no matter what the experience level of the software developer is.
Now that we established iteration is essential for software development, one necessary ingredient for iteration is refactoring. You have to be ready to refactor your design, your implementation, and even your tests during software life cycle, so you get to the best but how do you that? One wrong way is to blindly refactor your code and hope that what you come up with is better in some sense. This could work in some cases, especially if the developer has a good intuition, but it’s not a guaranteed process. There is another and more scientific way.
First, come up with the initial design and implementation. At this point, you know the design and implementation is not perfect, you know maintaining the code won’t be easy as it stands, you know the performance is not good but the product works as outlined by the spec. Second, write solid tests for the current implementation. By solid, I mean tests that can run consistently and predictably over and over again. This is a crucial step. In complex projects, tests usually end up being even more complex (sometimes necessarily, sometimes not so much) and with that, tests have breadth but they lose what i call their “solidity”. There are many reasons for this that I can talk about in another post. Third, start the refactor-test-fix iteration. In this step, you take small chunks of the code, refactor into ideal code, test to make sure everything works, fix if you broke something, and iterate. This is the most enjoyable part of software development for me. The initial design is hard because you need to make sure you get it right from the beginning. The initial implementation is tedious and full of uncertainty because you usually start from scratch and you’re not sure about many things until it works. The test implementation is not only tedious but boring (for me). But then comes the refactor-test-fix cycle and I’m back to life again. With the confidence provided by tests, I can finally pour my creativity into the code. I can change ugly, hacky code into beautiful code. I can rename things, add/remove methods, change visibility, do whatever I want to turn prototype code into clean, maintainable, testable and well documented code. This is where I thrive as a developer and it is only possible because I go through the tedious and boring test development cycle where I make sure I have solid tests for my initial code.
So, my philosophy in software development is the following. Get the initial implementation done quickly, write solid tests for iy, go through refactor-test-fix cycle and I guarantee that you will have a better design and implementation in the end. But make sure your tests are solid, and make sure you allocate time for refactoring, otherwise you end up with prototype code in production!