Refactoring to Patterns suggests that using patterns to improve an existing design is better than using patterns early in a new design. This is true whether code is years old or minutes old. We improve designs with patterns by applying sequences of low-level design transformations, known as refactorings.
This book was written to help you:
- Understand how to combine refactoring and patterns
- Improve the design of existing code with pattern-directed refactorings
- Identify areas of code in need of pattern-directed refactorings
- Learn why using patterns to improve existing code is better than using patterns early in a new design
This book assumes you are familiar with design concepts like tight coupling and loose coupling as well as object-oriented concepts like inheritance, polymorphism, encapsulation, composition, interfaces, abstract and concrete classes, abstract and static methods, and so forth.
Once my design skills had improved, I found myself using patterns in a different way: I began refactoring to, towards, and away from patterns, instead of using them for up-front design or introducing them too early into my code. My new way of working with patterns emerged from my adoption of extreme programming (XP) design practices, which helped me avoid both over- and under-engineering.
When you make your code more flexible or sophisticated than it needs to be, you over-engineer it. Some programmers do this because they believe they know their system’s future requirements. They reason that it’s best to make a design more flexible or sophisticated today, so it can accommodate the needs of tomorrow. That sounds reasonable—if you happen to be psychic.
Over-engineered code affects productivity because when programmers inherit an over-engineered design, they must spend time learning the nuances of that design before they can comfortably extend or maintain it.
Perhaps the main reason programmers over-engineer is that they don’t want to get stuck with a bad design. A bad design can weave its way so deeply into code that improving it becomes an enormous challenge. I’ve been there, and that’s why up-front design with patterns appealed to me so much.
When I first began learning patterns, they represented a flexible, sophisticated, even elegant way to do object-oriented design that I very much wanted to master. After thoroughly studying numerous patterns and pattern languages, I used them to improve systems I had already built and to formulate designs for systems I was about to build. Because the results of these efforts were promising, I felt sure I was on the right path.
But over time, the power of patterns led me to lose sight of simpler ways to write code. After learning that there were two or three different ways to do a calculation, I’d immediately race towards implementing the Strategy pattern, when, in fact, a simple conditional expression would have been easier and faster to program—a perfectly sufficient solution.
Experiences like this made me aware that I needed to stop thinking so much about patterns and refocus on writing small, simple, understandable code. I was at a crossroads: I’d worked hard to learn patterns to become a better software designer, but now I needed to relax my reliance on them in order to become truly better.
Under-engineering is far more common than over-engineering. We under-engineer when we produce poorly designed software. This may occur for several reasons.
- We don’t have time, don’t make time, or aren’t given time to refactor
- We aren’t knowledgeable about good software design
- We’re expected to quickly add new features to existing systems
- We’re made to work on too many projects at once
Continuous under-engineering leads to the “fast, slow, slower” rhythm of software development, which goes something like this.
- You quickly deliver release 1.0 of a system with junky code
- You deliver release 2.0 of the system, and the junky code slows you down
- As you attempt to deliver future releases, you go slower and slower as the junky code multiplies, until people lose faith in the system, the programmers, and even the process that got everyone into this position
- Somewhere during or after release 4.0, you realize you can’t win. You begin exploring the option of a total rewrite
Test-driven development (TDD) and continuous refactoring enable the efficient evolution of working code by turning programming into a dialogue.
- Ask: You ask a question of a system by writing a test
- Respond: You respond to the question by writing code to pass the test
- Refine: You refine your response by consolidating ideas, weeding out inessentials, and clarifying ambiguities
- Repeat: You keep the dialogue going by asking the next question
However, when using the Design Patterns book during design, many programmers, myself included, have read the Intent section of a pattern to see whether the pattern could provide a good fit for a given situation. This method of choosing a pattern doesn’t work as well as a method that helps you match a design problem to the problems addressed by a pattern. Why? Because patterns exist to solve problems, and learning whether they really can help in a given situation involves understanding what problems they help solve.
The refactoring literature tends to focus more on specific design problems than the patterns literature does. If you study the first page of a refactoring, you’ll see the kind of problem the refactoring helps solve. The catalog of pattern-directed refactorings presented in this book, which is a direct continuation of work started in Refactoring, is intended to help you see what kinds of specific problems the patterns help solve.
There is a natural relation between patterns and refactorings. Patterns are where you want to be; refactorings are ways to get there from somewhere else.
If you’d like to become a better software designer, studying the evolution of great software designs will be more valuable than studying the great designs themselves. For it is in the evolution that the real wisdom lies. The structures that result from the evolution can help you, but without knowing why they were evolved into a design, you’re more likely to misapply them or over-engineer with them on your next project.
If you want to get the most out of patterns, you must do the same thing: See patterns in the context of refactoring, not just as reusable elements that exist apart from refactoring. This is my primary reason for producing a catalog of pattern-directed refactorings.
By learning to evolve your designs, you can become a better software designer and reduce the amount of work you over- or under-engineer. TDD and continuous refactoring are the key practices of evolutionary design. Instill pattern-directed refactorings into your knowledge of how to refactor and you’ll find yourself even better equipped to evolve great designs.