Friday, September 7, 2007

Patterns of Misdirection

A while ago, I was reading a discussion group that I infrequent and I saw a post about design patterns. This particular post -- I remember -- was asking questions about the Visitor pattern. It seems that it is 'unpopular' to use this pattern, the author noted.

At the time, I didn't pay much attention to it other than to realize that I wanted to stay far away from that discussion, but I didn't know why.

For those few who are unfamiliar with them, design patterns come from a great reference book by the same name that was released in the middle of the 90s. Design Patterns has become so popular that it has its own set of Wikipedia entries. It has become a cult thing now.

The book starts with a programming example and then lists out a large number of 'design patterns'. It explains them in detail, describing how each one can be implemented and shows some sample code. These are common 'patterns' that the authors felt occurred frequently while programming in Object Oriented languages.

When I first read that book, I found it amazing.

I had always reused a collection of what I liked to call 'mechanisms' -- frequently used programming idioms -- that I had learned over the years. That was not uncommon, as most programmers I worked with at that time, had similar approaches. We all reused the best tried and true solutions for common coding problems.

Design Patterns took that a step further, putting some structure onto this common practice and making it possible for programmers to share their various knowledge with each other.

More importantly, at the time 'real world' objects were becoming more increasingly popular. It was a backwards approach to Object Orient that integrated brute force style programming right into the object orient model. The idea was to build behemoth objects that behaved as closely as possible to the real world. Instead of thinking about it, you just pound in as much knowledge of real life circumstances as possible directly into the code. These mega-objects could then be used anywhere. In theory.

Design Patterns was a shift back towards abstraction.

Seeking to find small abstract representations for generically implementing reusable solutions. Thus reducing the problems into something manageable. It was a good thing.

The ironic part of programming culture is that while programmers are always styling themselves as artisans, which is primarily subjective, they tend towards thinking in black and white, and thus, rather objectively. I saw an example of this when I was younger with one of my fellow programmers telling me that there was only 'one' true object oriented design for any programming problem. It is funny for people to believe it is a black art on one hand, but that there is only 'one' right way to do it on the other. But that contradiction has always been part of the culture.

Years ago, I knew something had gone terribly wrong with Design Patterns when in an interview, I was asked: which design patterns would you use to build an email server? That question blew my mind. I'd been away from Object Oriented programming for a while, but I had no idea that things had become so distorted.

By now, some of you are probably wondering what I am babbling about. You've been taught that Design Patterns are the primitives out of which you assemble your system. A few singletons, a couple of factories, a facade, a visitor and presto, blamo, you have a working system.

Well, it is exactly that approach that is the problem. First, the original idea behind design patterns was that they were places to start, not building blocks to assemble. While you are building your system you could refer to the patterns for some design, but the idea behind a pattern is that you morph it into what you need. It is not a matter of doing it right; it isn't a 'complete' thing; it is just a 'pattern' to get you started.

So, you don't 'design an email server' with patterns. That would be madness.

But there is more to it than that. Most of the Design Patterns are oriented around 'how' objects interact in the system: they are verbs; not nouns. Designing an easily understandable system comes from dealing with the underlying problem in the most simple and elegant manner. With respect to that constraint, software is just a tool to manipulate data. The code we write manipulates data. The data we manipulate is stored in objects.

We do apply 'functionality', or verbs to the data, but it is all about the nouns. That is why it is called "object" oriented, not "function" oriented programming.

When we decompose a system into 'how' it interacts with itself, we enhance the complexity by huge orders. This sideways view causes us to have to create a lot of unnecessary plumbing. If we decompose it by 'what' it is working on, we can simplify it. The only thing software does is grab data and manipulate it. If we structure our code to match what it actually does, the code is simple. And as an added bonus we can abstract and generalize the data, allowing us to apply the same code to a broader context.

If you read a lot of code, you will come across examples of Object Oriented programming that are written to strictly adhere to the existing patterns.

They are always more complex than necessary. And often, there is a secondary layer of complexity built on top, caused by the authors stuffing in strange pieces between the patterns. This happens because the patterns themselves don't form a complete set of primitives. Programmers 'invert' the structure, just to piece together incompatible patterns, causing quite the mess.

So, sticking exactly to design patterns, and worse, sticking to only design patterns is really bad practice. While that statement might 'irk' a few people, its truth lies in the basis of what we are trying to do.

Programming, is about telling the computer to perform a set of instructions. Usually they are a complicated set of instructions.

Programming takes so long and is so slow that we need to minimize the amount of work in order to get it done in a reasonable amount of time.

Thinking of instructions for a computer to perform is actually not very hard. The problems come once they get entered into the machine. Life, and humans, being what they are, mistakes happen often. Listing out instructions for a computer to follow is not difficult, but finding where they deviate from our original intent can be nearly impossible. Or at the very least phenomenally time consuming.

So, a key reason we decompose the steps into smaller pieces is to make it easier to fix problems when it goes wrong. It also makes it easier to enter into computer and can help in making it reusable, but those are secondary attributes.

The point behind Object Oriented programming is not just some arbitrary way of factoring the code into smaller bits. The idea is that those things in the program, visual or otherwise get intuitively mapped back to sections of code. There should be a direct relationship between what the program does and where the code is located.

So, for instance if there is a problem with one of you programs that displays the number of visitors to your web site, what you need to do to find the problem is to go back to the web-site visitor's counter object and make sure it is working as expected. From there you can work your way back up to the display of the data.

You should be able to trace your problems to the code directly, and "easily".

A good factoring of the code makes it possible. Poor factoring makes that hard. You can't connect problems with the 'data' as you see it being manipulated in the system, if the code is structured around 'how' it interacts with itself. Relating one decomposition back to the other is problematic.

Ultimately the problem with a counter may be how it is calculated during a traversal as it 'visits' various places to be summed up, but you would look into that type of algorithmic problem only after you were assured that there wasn't something wrong with the data itself. Why go through an long elaborate debug process, if the problem was a missing increment operator?

Finding a bug is time consuming, so we need to reduce that time to its minimum.

The key to a good factoring is to make it easier to debug the code. Why else would you go through all of that extra work? It should save you time, not waste it. It should relate the code back to the way it runs.

The most important techniques for programming make it easier to fix your code. It is all about readability, and about being clean and consistent. It is about making sure you are not trying to be clever. And it is about finding ways to express the code so that other programmers will understand too.

When you consider this, the idea of a pattern being popular or unpopular is just another bit of the madness. It shouldn't matter; if a pattern is close enough to what you need, then you should change it to make it fit in properly. If you are focusing on getting the data into the system, the patterns, when they infrequently occur are just on-the-side verb issues. They fit between the nouns.

A common way of sharing 'patterns' for solving problems is a great idea. An awkward way of decomposing problems into an inconsistent set of primitives is a bad idea.

What started out as getting people back on track, turned and lead them astray in a different direction. Because of this, so much of the code is littered with outrageously complex abstractions that don't cover their domain problems well.

Not that I think we should abandon the use of design patterns. They just shouldn't be used as primitives to try and frame the problems. They are things that come in handy now and again, but if the base pattern is not readable, then you should change it to be so. You always need to make the code as simple and clear as possible. That is the highest priority.

At the time I was reading the discussion group, I instinctively felt there were serous problems with the whole 'Design Patterns' thing that I was reading, but it took me quite a while to put it into words. It was wise, I think, to have stayed away from posting a reply in that discussion group. It would have taken so long to explain that someone would have accused me of being a being evil before I got finished.