I love jazz. Especially when a musician improvises over the changes – the “changes” being chord progressions in a given section of a song. One of my favorites is Coltrane’s Giant Steps. I’m in awe when a soloist can float above complex chord changes without getting lost. The key, I’m sure, is having the progression memorized.
Here’s an analogy: I’ve found it helpful to memorize several progressions in my programming. This helps ensure that my code is safe, efficient, and loosely coupled, without conscious effort – like a musician soloing over the changes. The following code progressions might be helpful for a programmer new to the art, and seeking to develop an idiomatic approach. Each sequence begins with safest, most restrictive option, progressing to greater complexity and capability as necessary.
This is the progression every object-oriented programmer begins with. Keeping the surface area of a class as small as possible reduces complexity, test case coverage, and cognitive load for consumers of the class (i.e., it does your peers a favor).
- Module Static
- Class Static Inline
- Class Static Out-of-line
- Const Non-Virtual Inline
- Const Non-Virtual Out-of-line
- Modifiable Non-Virtual Inline
- Modifiable Non-Virtual Out-of-line
- Const Virtual
- Modifiable Virtual
Each degree in this progression increases cost or complexity, or reduces generality. This one is a little meatier, because it combines several concerns, all related to member declaration. But we can break it down into “atomic” sub-progressions, each of which factors into the overall progression:
- Module → Class
- Static → Member
- Const → Modifiable
- Non-Virtual → Virtual
- Inline → Out-of-line
Private Static might be useful with pure header implementations, or when it’s hard to identify a source code module for a member to live in. But generally, Module Static is preferable to Class Static because it introduces zero coupling, not being mentioned in the public interface. One pattern for implementing Module Static is to forward declare an opaque helper class and wrap it with a smart pointer.
Const methods have a broader domain than Modifiable methods, given the compiler’s ability to bind a const reference to a temporary object, and so should be preferred.
Inline methods can be handled more efficiently by the compiler, and should be preferred over out-of-line methods, all things being equal. For example, simple get/set accessors which add no coupling should always be implemented inline. In this case, the narrower context also aids readability.
Non-Virtual methods require the additional pushing/en-registering of the ‘this’ pointer when dispatching the call, and should only be used when required.
Finally, Virtual methods require a level of indirection (pointer table lookup) when dispatching the call, and should be used only as necessary.
- Stack Variable
- Function Parameter
- Class Member (Aggregation)
The scope of a variable’s existence and visibility should be kept as narrow as possible. This doesn’t just aid readability. For every degree of scope broadening, there is increased cost in managing memory or communicating the data between parts of the program. Whenever possible, a developer should strive for a “stack-based discipline.” This might entail rewriting an algorithm to be recursive, or simply relocating a variable to the topmost stack frame in which it must be visible. The heap should be considered a means of last resort. It certainly has its place, such as a factory method returning a collection, but it’s relatively slow and adds complexity and risk.
- Aggregation (or Private Inheritance)
- Single Inheritance
- Single Concrete & Multiple Interface
- Multiple Concrete
Private Inheritance might be useful if a developer wants to expose a small subset of an inherited interface publicly, in which case a few “using” declarations can come in handy. Otherwise, Aggregation is typically preferred, as it reduces complexity in constructor implementations.
Some languages enforce single concrete inheritance, permitting implementation of multiple abstract interfaces. This design eliminates the possibility of the “dreaded diamond hierarchy”, with multiple instances of the same base. Other languages permit multiple concrete inheritance, and must address multiple bases (typically through virtual inheritance).
Do you have a progression you’d like to add? Let’s hear it!