§ April 25, 2008 16:28 by beefarino
Me: "Hello, my name is Jim, and I'm a pattern junkie."
Group: "Hello. Jim."
Yes, I humbly admit it. I read this book, that book, and this other one too, and now I'm pattern-tastically pattern-smitten with a pattern-obsession. I'm that guy on the team - the one who starts the design with strategies and factories and commands and decorators before a lick of code is compiled. The one who creates decorator and composite base classes for every interface because "I'll prolly need 'em." The one who, at the end of the project, has produced Faulknerean code for lolcat functionality.
But I confess: I am not the least bit ashamed. I acknowledge my approach has been overbearing and self-indulgent. I know I need to change to be a better engineer. Spending time as Scrum Master has shown me what pattern hysteria looks like from the outside. It's WTFBBQ smothered in bloat sauce.
But the experience of being a pattern junkie has been irreplaceable, for a number of reasons. Patterns are valuable to know, for reasons I'll expound on in a bit. Taking the time to (over-)apply them to real projects has been the best way for me to learn how they work and interact. My biggest problem is that I want to apply them as much as possible at the design stage of a project. I've come to terms with the fact that it's a bad idea, which has given me the chance to learn something and improve myself.
So, in the words of the good witch: "What have you learned Dorothy?"
First, let's talk about how misusing patterns has inhibited me.
Bad: Using a pattern leads me to using another.
Using a strategy pattern precipitates the use of decorators and adapters on the strategy. Using commands leads to the use of composites, iterators, and chain of responsibility. The complexity of managing the patterns and dependency injection leads to the use of facades, factories, builders, and singletons. Things become extraordinarily convoluted very quickly. When I design against patterns a priori, when they don't service an existing need, the code I have to write explodes, and once it's written, maintaining it becomes a real chore.
Bad: Thinking in patterns makes me lose focus of the problem.
Using patterns makes me itch to break down problems into very atomic units, which is generally good, but I take it to the point of comedy. Consider this example, which is an actual approach I used because I thought it was a good idea at the time. I was working on an object that operates on an XML document. To supply the XML document to my object, I chose to define the IXMLDocumentProvider interface as an abstraction for loading the XML. Why? Because I was thinking about patterns and not the problem I was trying to solve. My logic was roughly this: if I use another strategy to manage the load behavior the XML source could be a file at runtime and an in-memory document in my unit tests, and I could use a decorator on the strategy to validate an XMLDSIG on the document in production if I need to. In the end, all the program needed was the XML, which could have easily been supplied in a constructor or parameter. There is but one instance of IXMLDocumentProvider in the project, and all it does is hand out an instance of an XML document supplied to its constructor. I filled a non-existent need because I was focusing on the pattern and not the problem.
It isn't all bad; let's look at how using patterns has helped me.
Good: Using patterns yields testable code.
Using patterns extensively has helped me write highly testable code. Patterns and dependency injection go together like peanut butter and chocolate. Having patterns peppered throughout the design, my code is highly decoupled. Unit testing is a breeze in such a scenario, and unit tests are good.
Good: Using patterns makes complex code understandable.
Patterns isolate concerns. This makes large codebases more digestible, and it tends to break complex relationships into lots of smaller objects. I know many people would disagree with me here, but I find it easier to work with 50 small class definitions that a) follow well-understood patterns and b) adhere to the single responsibility principle than 5 classes that have been incrementally expanded to 20,000+ lines of code containing a succotash of concerns. A coherent class diagram will tell me more about a system than a list of 200+ method names.
Good: Using patterns makes complex systems extensible.
Again, patterns isolate concerns, which makes extending a system very simple once you are familiar with the system design. For example, adding a decorator is easier, in my opinion, to altering the behavior of an existing class. Folding new features into a well-designed strategy, command, or visitor pattern is cake. Patterns help you grow a system by extending it, not altering it, which is a good idea.
My two-step program to better pattern application
I've learned from my mistakes. I've come to the conclusion that patterns are a tool best applied to existing working and testable code. My personal commitment is to stop using patterns at the design phase, but continue employing patterns when they make sense. How will I do this?
My two steps are simple - when I work on a software feature, I promise to do the following:
- Design and code the feature to a working state as quickly and simply as possible. At this phase I promise not employ patterns a priori, although I may employ Dependency Injection to make testing easier.
- Refactor my code to separate concerns, remove duplication, and improve readability. At this phase, I will employ patterns NOT wherever possible, but only as necessary to meet my goal. That means I'll pull them in when I need to separate concerns, when I need to untangle spaghetti code, when I need to make the code understandable.
I'll let you know how the rehab goes. Until then, there's no place like code ... there's no place like code .... there's no place like code .....