Welcome to Designing Patterns and welcome to View from the Acropolis! I will use this blog to discuss high level patterns in software engineering, with the goal that readers be able to apply these patterns in order to produce higher quality work more efficiently. By patterns, I do not mean design patterns; or rather, design patterns are a specific example of the more general idea. Patterns simply are recurring concepts, in whichever context they appear. Recognizing, formalizing, classifying, and applying patterns has utility far beyond object-oriented design or even software engineering. From my experience working at a large software company, I believe that many programmers fail to appreciate the patterns in their work and thus never judge those patterns according to fundamental design principles, missing existing negative patterns, failing to apply beneficial patterns, and so producing lower quality output than they otherwise would. For example, most professional programmers appreciate that putting common code into functions is an excellent pattern, because it avoids duplication, a core software engineering principle. Many of these programmers do not understand that similar patterns must apply to every aspect of their work, including build systems, program logic, data, documentation, and configuration. They do not realize, for instance, that their build system is based on the recursive build system pattern, a negative pattern that relies on duplication in order to function properly, either building the same object multiple times or having the same dependency information encoded into multiple places within the makefiles. They are annoyed when their recursive build system breaks because they failed to duplicate the dependency information in one place, but they do not realize that the problem is not their incorrect updating of the makefiles, but rather that they have based their build system on a negative pattern. Pattern recognition, formalization, classification, and application underlies software engineering, and the extent to which a software engineer actively internalizes and applies patterns determines the quality of his work.
Software is an abstraction of the operation of very complicated machines, and so programming, more than most other disciplines, demands that its practitioners think abstractly, which in turn requires recognizing and thinking in patterns. One excellent example of this is learning new programming languages. One’s first programming language always is the hardest to learn. During this process, one’s mind does not have a store of patterns with which to interpret the new language, with which to understand and differentiate programming concepts and language concepts. Instead, programming initially is a series of magical formulas, which, given proper application and the right phase of the moon, produce the right output. For example, when learning C, pointers often are introduced simply as syntax allowing a function to modify its arguments. Such formulas are not properly patterns at all, since they cannot be applied outside of their native context. As one executes work with the language and begins to understand some theory, however, one starts to generalize the magical formulas into patterns and, armed with these, develops the ability to work and thrive in new contexts with the language. Understanding the relationship between pointers and pointees in C (indirection), for instance, allows one to work competently with C’s memory allocation facilities and to do things like construct a binary search tree. Learning a second language accelerates this process because understanding the patterns underlying it allows one to factor out common patterns in the two languages, which likely have far more general application. An understanding of C’s pointers might become an understanding of value and reference semantics after learning Java, for example. Applying the patterns of one language to learning another also helps one learn the new language much more quickly than one would have otherwise, as the new language’s patterns are highlighted by the old language’s patterns. I learned Ruby several weeks ago, and I found it very useful to compare every new aspect of Ruby with C++, even though Ruby and C++ are very, very different languages. Understanding C++ helped me to understand the concepts underlying Ruby programming. My learning process can be seen in this Wiki page, which I updated with my observations while studying the language; it consistently compares and contrasts Ruby with C++.
Most programmers probably would agree with the above assertion, that recognizing and formalizing patterns is the key to software engineering, and that failure usually is a result of not grasping a particular pattern. Many, however, use patterns passively; that is, they apply them in the contexts in which they were taught to but do not generalize them to new contexts or formalize new patterns. That is, they see patterns as little more than magical formulas. They equally happily put common code into functions in order to avoid duplication and create build systems that rely on duplication simply because they were told to do so, never stopping to judge and classify each pattern. They apply canned design patterns in specific situations, but do not really understand that the underlying principles that make a particular pattern advantageous (looser class coupling, for instance) must be embodied by patterns in other contexts as well. These failures of understanding, while apparent in whatever work a programmer produces, become painfully obvious in a large software system where a programmer must apply patterns embodying good principles at multiple abstraction levels in order to be successful. Cyclical dependencies, for instance, do not cause much damage in a small system but, when the patterns applied to a large software system result in cyclical link dependencies among dozens of libraries or cyclical operational dependencies among dozens of machines, they create a maintenance nightmare.
Whatever one is doing, whether learning a language, implementing a module, designing a class hierarchy, creating configuration files, drafting requirement specifications, or architecting a system, one constantly must step back and assess one’s work in order to discern the patterns that underlie the work. After recognizing and formalizing the patterns, judge them according to good design principles. When others offer criticism of one’s work, assess the criticism in light of its patterns, and if necessary, change the work and your understanding of its patterns at the same time. When one notices a problem in a system, do not simply correct the problem but instead probe into what allowed it to exist in the first place. When someone asks for help, do not simply answer their question but instead try to explain the pattern underlying the answer and the principles that you use to judge the pattern. When working on something that feels wrong, seek to understand what aspects of the project violate beneficial patterns or manifest negative patterns.
I am passionate about programming, and I want to improve. I have no doubts that continually formalizing and applying patterns will make me a stronger programmer. This blog, by forcing me to commit patterns to virtual paper, will help me do to so. Hopefully, others will learn from my experience. In addition, as I have benefited greatly from learning from others, I also want this blog to stimulate discussion, so that the understanding of others will improve my own.