Although we often use patterns, we sometimes do so unconsciously -- not by design. Whenever we follow a pattern, we are applying experienced-based learning. Whenever we need to accomplish something that we've seen others do, we tend to mimic the strategies and actions that made that thing successful in the past.
Experienced software developers use patterns heavily in the design-level aspects of their work. Design patterns make us more productive, more likely to achieve success, and generally more valuable as project participants.
We might think that less-experienced engineers are disadvantaged in this respect, but rather than accepting the risks that lack of experience brings with it, effective organizations take an early and active role in sharing important domain knowledge with them. They gather the collective design wisdom of the group, descriptive language (manifest vocabulary) of what the important design patterns are, and when and how to apply them. A good example of this can be found in "Design Patterns: Elements of Reusable Object-Oriented Software", by Erich Gamma et al. (Addison-Wesley, 1995). In my experience, developers of all ranks drink up such design patterns and quickly begin to apply them effectively.
With respect to patterns of code, we can provide much more direct help and support than simply documenting best practices. We can build the use of those code patterns into the tools we use in the software development process itself. Cleanscape Sourcemill is a commercially available tool I designed for that purpose. In this article, I'll discuss the use of patterns in the coding aspect of software development, and describe how to use Cleanscape Sourcemill to define and instantiate code patterns.
Design Patterns and Code Patterns
Design patterns are logical in nature. They embody approaches and strategies that are relevant to a number of possible implementations. A design pattern is something you can visualize independent of a particular programming language.
For example, given that you need to manage the dynamic allocation and deallocation of objects, you can specify a logical strategy for minimizing memory utilization by keeping reference counts on objects and copying those objects only when an operation is performed to modify them. For a detailed C++ example of this strategy, see "Item 29" within "More Effective C++: 35 New Ways to Improve Your Programs and Designs", by Scott Meyers (Addison-Wesley, 1996). Having defined the strategy and given it a name, you can treat it as a design idiom. During design you can say, "XYZ will be a reference-counted object," and experienced programmers will understand what that implies.
In contrast, code patterns are physical in nature. They focus on how a particular structure or sequence of action is accomplished using the specific mechanisms of a programming language. With C++, we've been trained to create code-level abstractions to support certain design idioms (for example, using collection classes and templates), but this practice has limitations. Classes and templates are not adequate mechanisms to implement many of the code patterns that are interesting to us. Many important code patterns either describe how a class should deal with its parts (a parts-based pattern), or describe how a number of cooperating classes are created to implement a single design idea (a multiclass pattern). Developers currently use parts-based and multiclass patterns, but they do so via hand coding. You either know the pattern by heart, or you copy a code sample that already follows the pattern, and modify it to work in the specific case.
Using Code Patterns
Most production software environments operate under time and quality pressures. As a result, code patterns that should be staples are all-too-seldom used. This is not because the patterns are unknown. It is not because developers do not recognize cases in which they would be good strategies to apply. It is because hand-crafting patterns adds just enough time and risk to a schedule to cause them to be shunned. For example, a correct implementation of a reference-counted object in C++ requires careful attention to the use of a copy-on-write mechanism that each non-"const" function plays into. This adds unwelcome detail and complexity to a hand-coding-based process.
To make use of nontrivial code patterns, we just need an easy way to characterize objects during design and then have those characteristics influence how code is produced.
Using Cleanscape Sourcemill For Pattern Instantiation
Cleanscape Sourcemill is a tool for instantiating patterns of code that can be derived from object models. First, it allows you to define code patterns that are important, based on your own local standards and classifications of object characteristics. Cleanscape Sourcemill then provides the capability to instantiate those code patterns for any set of specific objects. Given a properly thought-out set of rules for creating code for objects and their parts, based on specific characteristics, you can realize gains in standardization, quality, and time-to-implementation.
Cleanscape Sourcemill enables code patterns that use an object model as an instantiation context to be defined; see figure 1. It applies the rules called out in an executable template to the objects and their parts, and produces code files based on those rules.
Object Model ---> SourceMill ---> Code Files
Code Templates /
Figure 1: Cleanscape Sourcemill's operating model
To use Cleanscape Sourcemill, you first need a well-defined implementation strategy. You need to call out the characteristics of the objects (and their parts) that participate in that strategy, and decide how code should be created for each of them. Given that strategy, you create a set of code-creation rules, placing them in a Cleanscape Sourcemill template file. A number of template files that serve as good starting points for this come with the tool. For example, a template for handling frequent C++ class constructs that can be easily extended to include special rules is provided.
You can develop the template files iteratively using SNlP's user interface. Once you are satisfied with the code pattern that is created by the template, you can add the command form of Cleanscape Sourcemill into makefiles for use in non-interactive builds.
As an example of how Cleanscape Sourcemill is used, I'll use the common C++ programming task of properly managing dynamically allocated objects. Whenever you have a contained pointer, it is important for the code in the class containing the pointer to manage the dynamically allocated object correctly. (Although you might otherwise create a special container class or smart-pointer template, I'll use this example because it is familiar and brief.)
First, I'll define what it means to manage an object via a contained pointer. (Feel free to disagree with the definition -- it just means your template would be different than the one I present.) I define the code-pattern requirements as follows:
DOD-STD-2167A All pointers to objects, both owned and shared, are initially set to NULL.
A "set" operation keeps the pointer passed into it in both the owned and shared pointer cases.
A "clear" operation deletes the object if it is owned; otherwise, the pointer is set to NULL.
The destructor deletes the object if it is owned.
A copy constructor or assignment operation calls "makeCopy()" on an owned object and uses the returned pointer. Pointers are simply copied in shared object cases.
Listing One shows how these rules are expressed in the SNlP-template file format. Before inspecting the listing, however, note that this template is pared down for illustration, so it won't be hard to find a case it doesn't handle (the production template that this example was taken from is much more sophisticated). Secondly, there are only a few constructs used in Cleanscape Sourcemill templates. Once you know them, readability isn't an issue.
Cleanscape Sourcemill's template statements draw information from an object model and generate code based on that information. Before you can make sense of template statements, you need to see what they are referring to.
Listing Two is a simple object model I'll use as input to Cleanscape Sourcemill in this example. It has just one contained pointer of each type in it -- one to an object that is owned (PetOwner::Pet) and another to an object that is shared (PetOwner::Vet). When you apply the template file in Listing One to this object model, your rules will be used to generate the code in Listing Three (the resulting header file) and Listing Four (the resulting body file).
Reading Template Statements
Cleanscape Sourcemill template files contain a mainline of executable statements and any number of modules that contain executable statements. Executable statements come in three forms: simple statements, blocks, and iterators.
Simple statements have the form
left-hand-side colon right-hand-side
To the left of the colon is a selection expression. If the expression evaluates to True, the right side is expanded, and the resulting text is emitted into the active output file.
The module emit_class_decls in Listing One contains the segment of statements in Example 1.
The first five statements have no variables on the left, which means they are selected in all cases, so each of their right sides are expanded and emitted into the active output file (variables being substituted where indicated). A backslash on the end of a statement continues the line -- no line feed is emitted when the right side's text is emitted.
The next line has a single variable on the left, called obj.has_parent, which takes on the value True if the object has at least one parent object identified in the input model. When it is True, the statement is selected. It emits a colon and space, and continues the line.
The next statement is an iterator prefaced by .each_parent. It iterates over each parent identified for the current object in the model. The body of the iterator continues until its corresponding .end is reached. Within this parent iterator, each parent class name is emitted, followed by a comma, except with the last one. The variable xxx.is_nth is used to indicate the last member of an iterated list. The "bang" character (!) inverts the sense of the Boolean variable.
This segment of the template emits code that properly creates the preamble of a class declaration and takes into account cases where the class is a subclass of one or more parent classes.
Code For Handling Contained Pointers
Listing Two contains the template module emit_class_copy_ctor that will emit the copy constructor for each object in the model. The pattern that the copy constructor follows demonstrates the need to treat contained pointers to owned objects differently from those that are shared.
The module emit_class_copy_ctor starts by emitting the copy constructor's signature and initialization list, including calls to copy constructors higher up the inheritance paths, if there are any. After emitting the opening brace of the constructor's body, the attributes that have been defined for the object are iterated, and three possibilities are used as selection criteria for producing code statements.
The attribute may be simple, such as "Name". In this case, the template assumes that the assignment operator is defined for the attribute type, and adds a statement to copy its associated value.
Second, the attribute may be a pointer that is shared. In that case, the pointer value is copied.
In the last case, you have a pointer that is owned. A call is issued to make a copy of the object in that case, and the pointer of the newly allocated object is retained. This module is also responsible for emitting the body of the makeCopy function, which appears at its end. When this module is applied to PetOwner, you get the code in the class PetOwner shown in Listing Four.
The destructor pattern (see the module emit_class_destructor in Listing Two iterates similarly over the object's attributes.
This time the iteration is done to find only the owned pointers. These are the only members that are interesting to the destructor (which is responsible for deleting them). The destructor for PetOwner has only one line, which deletes the Pet object, its sole owned pointer. Example 2 is the code created for PetOwner's destructor.
Cleanscape Sourcemill allows control of code generation, and is flexible enough that you can define code patterns that relate to how objects fit into your local frameworks. Where a consistent strategy and pattern of producing code can be identified, quality and productivity can be increased proportional to the amount of code that follows that strategy.
The ratio of lines of code to lines of input model is typically 20:1. This is a great productivity advantage. Admittedly, Cleanscape Sourcemill does not provide a revolutionary new way to produce code. Instead, it offers the opportunity to automate what you are already doing to follow code patterns by hand.
About The Author
Fred Wild is principal of Advantage Software Technologies.