Search This Blog

Tech Book Face Off: Refactoring Vs. Refactoring to Patterns

I've read a number of books that talk about and encourage refactoring—the practice of modifying code to make it cleaner and clearer without changing its function—but I've never read a book about refactoring until now. I decided to flesh out my knowledge of various refactoring techniques with Martin Fowler's classic Refactoring: Improving the Design of Existing Code, and because refactoring is often used to convert existing code into various software design patterns, I paired it with Joshua Kerievsky's Refactoring to Patterns. I normally read two related books on a subject to cover the subject in more detail and from different perspectives, and in this case the two books turned out to be intimately related, with Refactoring to Patterns building on and referencing Refactoring extensively. Here is an in-depth review of these two books.

Refactoring: Improving the Design of Existing Code front coverVS.Refactoring to Patterns front cover

Refactoring


Martin Fowler decided to write this book because at the time many books were being written about the new agile design movement, refactoring was an integral part of programming in agile design methodology, and he felt that no one else was going to write about the details of refactoring. The result is this clear and thorough book describing 68 low-level refactorings and 4 higher-level refactorings.

Before getting into the nuts and bolts, he spent a few chapters introducing refactoring, discussing when and why you would refactor, and how to identify code that could benefit from refactoring with a list of "code smells" that reek of bad code. These introductory chapters were an excellent condensed treatment of most of the agile principles found in other agile books, and Fowler had some well-reasoned arguments for his recommendations.

His advice includes such things as minimizing published interfaces within a team because interfaces create friction to code changes; starting with the simplest design if there is a clear path to refactoring to other designs; analyzing programs before optimizing for performance because the wasted time is not where you think it is; and concentrating tests where the most risk of defects are because comprehensive testing has diminishing returns. He also has a lot to say about flexibility in design, and starts the discussion by cautioning against making designs too flexible too early:
Building flexibility in all these places makes the overall system a lot more complex and expensive to maintain. The big frustration, of course, is that all this flexibility is not needed. Some of it is, but it’s impossible to predict which pieces those are. To gain flexibility, you are forced to put in a lot more flexibility than you actually need.
He proposes that refactoring provides a better path that becomes a productive habit, and sums up KISS and YAGNI in two brief sentences:
Refactoring can lead to simpler designs without sacrificing flexibility. This makes the design process easier and less stressful. Once you have a broad sense of things that refactor easily, you don’t even think of the flexible solutions. You have the confidence to refactor if the time comes. You build the simplest thing that can possibly work. As for the flexible, complex design, most of the time you aren’t going to need it.
Later he brings it all together by explaining how refactoring can actually make the design process more flexible, even if the code initially seems less flexible:
As refactoring becomes less expensive, design mistakes become less costly. Because it is less expensive to fix design mistakes, less design needs to be done up front. Upfront design is a predictive activity because the requirements will be incomplete. Because the code is not available, the correct way to design to simplify the code is not obvious.
While most of the introduction was well written and a good review, the introductory content could largely be found in other excellent books (such as The Pragmatic Programmer or Clean Code). The real meat of Refactoring is in the set of refactorings that are named, explained, and shown with examples. Each refactoring is given a descriptive name like Extract Method or Replace Magic Number with Symbolic Constant that are mostly self-explanatory and easily recognizable. This name is followed by a short description of when to use the refactoring and a diagram of what it looks like. Then there is a motivation section describing in more detail when you would want to use it and a mechanics section describing the recommended steps to perform the refactoring. Finally, an example is shown for the refactoring with code so you can see what it looks like in practice.

I found the examples to be the most valuable part of the book, and I would have been quite happy with just the name, short description, and example for nearly all of the refactorings. The mechanics sections were almost universally confusing because it's hard to describe code changes in words. The terminology and descriptions of moving code around end up becoming a muddled mess no matter how you organize the prose. As for the examples, I was thinking as I read through the refactorings that they could have been even further improved if they were set up as a set of check-ins to Git with code diffs for each step of the refactoring. Such a setup would be quite slick and very useful as a reference.

It quickly became apparent that most of the refactorings have dual refactorings that are complimentary. In some situations you want to refactor in one direction, and in other situations you'll want to refactor in the opposite direction. This leads to refactoring pairs like Extract Method and Inline Method, Hide Delegate and Remove Middleman, and Change Value to Reference and Change Reference to Value. Sometimes going through these pairs felt tedious since it seemed like repetition for completeness' sake.

By the end of the refactorings, I was wondering if most of them could be sufficiently summed up by one generic refactoring called Extract Behavior/Data and its dual Inline Behavior/Data—or maybe even the single Move Behavior/Data—with a few other oddball refactorings that don't quite fit that mold. Of course, that classification would be too generic to be especially useful for teaching, but it was an obvious way to think about what was being accomplished in most cases.

I can appreciate where Jeff Atwood was coming from when he described Refactoring as too prescriptive, although I don't think that criticism is entirely true to Fowler's intent. He meant for Refactoring to be a catalog of refactorings that you could peruse and select, with suggested implementations. The programmer is free to follow a different path while using the catalog as a guide or store of ideas.

I do wonder who would get a lot of value out of this book, though. I felt that most of the refactorings were trivial and obvious. Any programmer with a few years of experience should be able to come up with them on their own, and the handful of refactorings that were novel didn't require a 460 page book to present them. A programmer that needs to learn most of these refactorings is probably more of a novice and would be better served by gaining real programming experience and discovering refactorings on their own. They'll remember how to refactor much better from experimentation and exploration than from reading through a catalog.

Refactoring to Patterns


This book clearly builds on Refactoring with numerous references to the original refactorings and bridges the gap between basic refactorings and design patterns. The book's format is also very similar to Refactoring, with a few introductory chapters explaining what refactoring is, what design patterns are, and what code smells are. The rest of the book is the catalog of refactorings that lead from ad hoc designs to specific design patterns with the same series of description, diagram, motivation, mechanics, and example sections for each refactoring as Fowler's book.

Like Refactoring, the whole book is quite clear and readable, maybe even more so. Kerievsky is a good writer, and I enjoyed reading his many anecdotes, especially in the introductory chapters. He also had several snappy comments warning programmers not to use patterns too much:
The patterns-happy malady isn’t limited to beginner programmers. Intermediate and advanced programmers fall prey to it too, particularly after they read sophisticated patterns books or articles.
And not to optimize to patterns prematurely:
If you want to be a good software designer, don’t optimize code prematurely. Prematurely optimized code is harder to refactor than code that hasn’t been optimized. In general, you’ll discover more alternatives for improving your code before it has been optimized than after.
He was also careful to explain what the reader should really be getting from the book:
The true value of this book lies not in the actual steps to achieve a particular pattern but in understanding the thought processes that lead to those steps. By learning to think in the algebra of refactoring, you learn to solve design problems in behavior-preserving steps, and you are not bound by the small subset of actual problems that this book represents.
I totally agree with this sentiment. To learn anything well, it's best to understand the fundamental reasons why and how something works. Once you've mastered the fundamentals, then the higher level stuff that used to be confusing and hard now has a place in your brain to hook in to, and it becomes much easier. Once you have internalized the methods for refactoring to patterns and clearly understand how to do it, it becomes trivial to solve a much wider array of refactorings to any pattern you so desire. In fact, to better learn how to refactor efficiently it's probably best to not try to memorize these refactorings, but to work out the steps on your own to gain a much more solid understanding through practice and discovery. That kind of understanding is difficult to achieve purely through reading a book. It's a lot like a mechanical puzzle, like a Rubik's Cube, where the satisfaction and real learning takes place by figuring it out for yourself. It's almost impossible to learn and remember how to solve a Rubik's Cube by watching someone else do it, and refactoring has the same sort of feel to it.

Having the same format as Refactoring, this book suffers from the same shortcomings. The mechanics sections are again filled with complicated terminology and confusing descriptions, and it's much better to skim them or skip them. The examples are the most important part of the refactorings, but they would be much improved by checking the code into Git as a series of code changes that could be browsed and compared to more clearly see the evolution from less structured code to design pattern. Despite these issues Refactoring to Patterns was an interesting read. I wouldn't say it's a must read, but it's worth a look if you're curious and like reading about code transformations.

Refactoring is Something You Do


I tend to judge books based on how much I learn from them. The more sparks of insight I get and the more buzzing my brain does, the more engaged I am in the book. Unfortunately, with Refactoring and Refactoring to Patterns, I did not feel like I learned much. It was a good review and a nice overview of available options when cleaning code, but what I basically learned was that the Creation Method pattern I had worked out on my own a couple years ago is actually a thing (and has been for a while) and that refactoring is something you do, not something you read about. The best way to get good at refactoring is to write a lot of code, realize it's ugly and dirty, study some books on patterns and writing clean code, and then figure out the puzzle of refactoring for yourself by cleaning up your code and making it beautiful. Studying refactoring won't get you very far. Practicing refactoring will.

No comments:

Post a Comment