N.B. This is one of the episode for which my original notes are very thin.
Part of The Inner Chapters Unbook.
Originally part of podcast episode number nineteen.
Dedicated audio available from Podiobooks.
- What is it?
- I'd been doing it already, without calling it re-factoring
- True of unit testing and to a lesser degree functional decomposition
- Not a new idea, value isn't so much in doing something new or different
- Value is derived from the good things we are doing, already, and learning how to do them better
- One thing early XP books got right
- Best way to keep cost of change down
- Only when coupled with good unit test coverage
- Doesn't matter if unit tests are written first
- Martin Fowler's book
- Crystalizes thought around he techniques we know
- Introduces us to techniques we perhaps do not know
- Like design patterns, introduces discipline into our approach
- Also gives us a common language for describing the practice
- Keeps a few principles in mind
- Singe Responsibility Principle
- Don't Repeat Yourself
- Avoid doing it without unit test coverage
- Keeps a few principles in mind
So the first natural question is, of course, "so what is refactoring?" if you're not already familiar with the notion. Now this, like functional decomposition or design to a lesser degree and automated testing are things that I personally had already been doing before I really heard of the term. Not a new idea, I think, and contrary to the way I think a lot of other authors and speakers have treated the subject of refactoring and many of the other subjects that I talk about in this series, it is not the newness or the novelty, but the recognition of things that we were already doing.
I think probably one of the better series of books that I have encountered that really does just that is The Pragmatic Programmer, and I think the early books on XP as well, to be fair. So I think the value here is recognising the fact that refactoring is something that we do maybe with less introspection, less consideration, but we are already doing it, maybe sporadically or maybe on a more regular basis.
If we understand what these good things are that we are already doing, and if we understand them better, then we will learn how to do them better and how to do them with intention moving forward. I think one of the things that early XP books got right was that refactoring in particular is the best way to keep the costs of change down — in particular, practice of continual refactoring, that rather than doing refactoring after the fact, after we have recognised a whole complex of problems that need to be addressed, resolved, and fixed, rather we do it ongoing as we are writing the code from the get-go, that it results in more fluid code, easier-to-adapt code, and increasing quality over time rather than decreasing quality as things get more complex and as we get more tired.
Now that being said, I think that there is another principle in particular in Kent Beck's Extreme Programming (the first book in an Extreme Programming bookshelf, that whole series) explained that has to be coupled with this with continual refactoring to realise the true value, is that refactoring has to be done in conjunction with good unit-test coverage, that without unit-test coverage you run some risk that the refactoring might result in code that reads better and looks better, maybe even functions better, but you are not entirely sure what regression bugs may have introduced, what things that were working that you broke or even wholesale new bugs you may have introduced for making those changes.
Now personally I do not think it makes a spot of difference whether the unit tests are written first or they are written after, or they are written in parallel. I am not a big advocate of test-driven development because unfortunately — I've talked about this in the unit-testing segment some weeks back — I think it does tend to encourage a bit of myopia, and I think that advocates of test-driven development or people who follow what those people say very literally sacrifice big-picture design, sacrifice some of their peripheral vision when doing their implementation work.
But that being said, you understand that it is just that weird sort of recipe that the TDD people put forward, "always write your unit test first, always write your unit test first, and then write the code that fails, and then write the code that passes". That particular nuance is what I am complaining about, what I have issue with. The practice of unit testing, of automated unit testing I have no issue with: I think it is a great tool, but like I said, it has to be used in conjunction with other practices that we know how to use, that we recognise that maybe we are already using.
I want to talk next week or the week after about practices in general before I get into the next set of chapters on principles, which I think is the other half of a personal approach to programming, a personal process or personal style, whichever you prefer to call it.
Getting back to refactoring: the definitive text on the subject, I would say, is definitely Martin Fowler's book that is just simply called Refactoring. And I think this dovetales very nicely with what I was saying earlier about just recognition, perhaps, of what we are doing already and how that recognition then enables us to understand what we are doing at a meta level and then do it better by applying that meta-knowledge. And I think Martin Fowler's book really helps us crystalise the thought around the techniques that we know and use, so you can read through the catalogue, read through the introductory and surrounding material on the book, and if it resonates it will enhance that recognition, will enhance that meta-knowledge. And then, being a catalogue-style book like the Gang of Four Design Patterns book and several of the other better books that I recommend and will recommend as the book reviews progress, gives us more lateral knowledge of maybe techniques that fall within the realm of refactoring that have not experienced first hand or variations of things that we have done. It really allows us to just reflect better. I think that one of the things that I try to talk about and really put forward and recommend more strongly throughout the history of this podcast is just that introspection, that reflection.
And then also it gives us a good common language for talking about the practice, the fact that he has broken it into a catalogue of several dozen specific techniques with very short, recognisable names. When I say "pull method up, push method down, extract interface, extract method, introduce parameter object," each of these things is very meaningful, and it kicks off a whole chain of a conceptual understanding of what that represents in terms of, if you are going to actually implement that particular refactoring technique, you know, I can describe that much more succinctly when we are having a discussion about how to improve code without having to get into the gory details of "oh, you have to change all the local references, you have to change all the type references, you have to do this, do that..." we can talk about it at a higher level, at a meta level again. And then when you go off to actually work at an implementation level you have that knowledge that you can then dig into, you have the book, this resource is what I am talking about. If I mention or reference something from that book, and I like to keep a good bookshelf at work specifically for that purpose of being able to reference things in there so we do not have to have an hour-long discussion of what "introduce parameter object" means. You know, it is just a good shorthand.
And then I would say, to wrap up on this subject because I do not have a lot more to say that has not already been said (outside of the fact that refactoring is one of my most favourite things to do!) is that I think there are just a couple of good guidelines here to keep in mind: that refactoring does have to be focused and targeted at the end goal. And I think there are a couple of end things to keep in mind that help us do a better job at doing that.
The two principles in particular that I personally adhere to when I am doing my refactoring work, and it does not matter whether it is first-pass work or I am refactoring somebody else, is the single-responsibility principle. And I forget which Agile advocate first brought this to my attention, but it makes a lot of sense and I have heard it articulated in other ways, and it is just the notion that your code module or your class is responsible for one thing, one conceptual set of behaviours or functions or capabilities in the system, and nothing else. And if you find that you are writing code for two orthogonal responsibilities, that is your cue to start to break that apart into smaller pieces and figure out collaboration patterns to wire them back together, so that you can get good encapsulation, good decoupling and separation of concerns. So you can change the way your parsing code works, for instance, without affecting runtime behaviour of the parsed objects that are extracted out of that.
And then the other one — I think this one is definitely a Kent Beck-ism — is the "do not repeat yourself" principle. And I am a huge, huge fan of this one, folks, in terms of: every time you repeat yourself, that is twice as many places that the same bug could pop up, and that means you are twice as much to miss fixing one of those places. So refactoring can do a good job of allowing us to identify where we are starting to repeat ourselves, and then with design patterns in hand, with good design philosophies and principles in hand, figure out ways to eliminate that repetition, but still have a good implementation and a good sign after the fact.
And then the only other thing that I have to say in terms of guidelines and tips or guidance that I can give you is just to reiterate what I said earlier, as far as, you should not do refactoring without some form of automated test coverage to give you that safety net, to make sure that the refactored code works at least as well as the code before you started the refactoring endeavor. And then ideally, I think that you can start to get into a nice feedback loop. And I think I talked about this a little bit in the Inner Chapters installment when I talked about unit testing in particular, that very often the act of refactoring can give you some insight in how to improve your unit tests and might provide you with failure cases that you did not think about originally, it might provide you with variations, boundary conditions of additional permutations, again, that you did not think of originally. So beyond it being a safety net, I think these things work well hand-in-hand in terms of refactoring can give you insight into unit tests and how they exercise your code, can maybe give you insights into where your code is a little clunky, where there is some brittleness, where there are problems that it can hint at, where some judicious refactoring might be well served.
So hopefully you found that helpful. As I said, in the next installment of the Inner Chapters I am going to talk about these last four installments, all of which speak about practices, things that we can do, and then before I get into the next stretch where I think I am going to talk a little more abstractly about principles, I think things that you should keep in mind while you are exercising these practices, things you can use to guide your career, guide your the technical development and becoming a better programmer, but also in becoming a better professional, you know, working better in a team environment, earning the respect of your peers, things of that nature.
So I am going to take a break in the next one to just compare and contrast practice and principles to kind of set up to get into that more, I think, philosophical or higher-level discussion of the principles. So hopefully you have enjoyed the Inner Chapters up to this point, and hopefully you will continue to enjoy it for much the same reasons, maybe some new ones, moving forward.