Code Complete - Ch.2

Chapter 2: Metaphors for a Richer Understanding of Software Development

Code Complete - Ch.2

We're all familiar with unhelpful or simplistic metaphors. If you work in corporate America, you'll no doubt be familiar with a long list of trite and uninspiring phrases that haunt our professional language. We've all been encouraged to "reach for low-hanging fruit" too many times. We've all "geared up" to "hit the ground running" on the next effort—perhaps even with "all hands on deck." The ubiquity and repetitiveness of language like this strips it of all meaning and inspiration. Personally I find this sort of language odious. Metaphors can be extremely useful when chosen carefully and with respect to the actual context of meaning, but they can just as easily prevent us from actually understanding a topic by means or oversimplification or misdirection.

A metaphor helps us make sense of complex ideas by re-framing them in a familiar and understandable conceptual model. Through conceptual models we can present esoteric knowledge in a format understood by all, as a means of enabling communication and cooperation. The most useful models are able to encompass the properties and relationships of the underlying ideas while also suggesting potential areas of inquiry; they should be relatable to other metaphors, so that our inquiry is extensible if necessary. We must also be careful not to rely too rigidly on a metaphorical understanding of a topic. Too often throughout history a misguided model has impeded progress rather than aided it. For centuries, humanity understood its place in the universe based on an earth-centered astronomical model. The shift to a heliocentric model during the enlightenment opened up countless new paths of inquiry until it, too, was superseded by other astronomical models.

The best way to employ metaphor is to use it as a heuristic: a method of asking the right questions and finding solutions that are "good enough" without worrying too much about details and execution. In programming, we often talk about algorithms: deterministic processes that take specific inputs and, by means of complex and sometimes-inscrutable operations, produce specific outputs. An algorithm (explicit, clearly defined, unambiguous) is the opposite of a heuristic (suggestive, usefully vague, open to interpretation). A software program is composed of hundreds or thousands of algorithms; until recently no computer ever took suggestive guidance to produce a precise result. The advent of consumer-grade artificial intelligence muddies the waters a bit, but that is perhaps a discussion for a different post. In this series, we are concerned with actually designing and building software the old fashioned way, so we'll stick with the algorithm/heuristic dichotomy.

If clearly-defined algorithms are the end goal, why then devote so much attention to choosing the right fuzzy metaphors for our work? Well, as any experienced designer will tell you, conceptualizing the problem is often more difficult than actually solving it. With experience, solving specific problems becomes less and less of a concern; we often know exactly what to do in a specific situation because we've dealt with that little piece of the puzzle a hundred times already. The bigger challenge is to creatively and efficiently employ our experience while building something new and unique. Metaphors help us find the right problems to obsess over and allow a lot of needless detail work to fall by the wayside.

A sketch of a quill pen and a piece of paper.

"Writing"

Everyone knows that the act of coding involves writing text on a screen with a keyboard. We refer to different programming "languages", their "syntax" and "semantics," and even what sort of "libraries" exist for those languages. Computer code is often evaluated based on its legibility as much as whether it performs well. The metaphor of writing code much as we write an essay or literature is a strong one, and easily comprehended by even the most techno-phobic layperson.

While it is indeed very important to have good natural language writing skills in order to write useful computer code, the metaphor is not very helpful in describing the task of programming. For one, the act of writing is often perceived as being rather linear. We are taught in grade school that effective essays have introductions, body paragraphs, and conclusions. Editing aside, people usually start at the beginning of a letter and write until the end. This is not the case for programming, where interconnected pieces of computer logic are developed simultaneously. In compiled programming languages, the computer does not even read code from top to bottom, they way humans read down a sheet of paper. Another misleading aspect of the writing metaphor is that we ascribe a lot of associations with originality and personal invention to our writing. Plagiarism is frowned upon in most circles. Having studied art history in undergrad, the academic in me is actually wary of this entire project, based as it is on a close reading of a secondary source—the "Code Complete" textbook by Steve McConnell—but in writing software the free adoption of others' work is extremely common. What I don't like most about the writing metaphor is that it belies the collaborative realities of software design.

A sketch of a sheaf of wheat.

"Growing"

Steve McConnell addresses the metaphor of creating software much as you might cultivate growing things, thought I have to admit this was an unfamiliar comparison for me. I had not previously thought of programming as akin to farming, though perhaps that was more common in the late nineties when the book was originally written. There is some use to this metaphor, though, as the idea of planting many small algorithmic seeds and growing them gradually (and concurrently) into a bountiful harvest does more accurately describe the distributed nature of programming effort than the idea of a lone genius writing her magnum opus. Similar to writing, growing is also seen as a linear process (perhaps more so) and also somewhat fatalistic. We can sow our seeds with care, but the weather, climate, and bees also have something to say about whether our farmland will be productive in the end. The farming metaphor implies a certain lack of control over the process and places a lot of importance on getting everything set up right at the very beginning and then hoping for the best.

What's most useful about this metaphor is how it very correctly conveys the idea of accretion in software development. It's very common for programmers to start with a minuscule, almost non-functional example of what they are trying to build. This is known as the minimum viable product (or MVP). They will then build on that over time, adding complexity in layers over the original core concept. This idea also tracks well to the practice of test-driven development (or TDD), whereby programmers write the simplest possible code that will pass a clearly defined test, and then build in complexity to both the source code and the test code concurrently. McConnell gives the example of oyster farming to demonstrate accretion. In his example the oyster accretes calcium carbonate over a tiny grain of sand until a brilliant pearl is formed, but I think this metaphor could be better. I prefer the idea of oyster farming for aquaculture (not jewelry), where a suitable and sturdy substrate is laid into the water so that oysters can accrete on top of it (and each other) until a delicious harvest is reaped.

A sketch of a hammer and nails.

"Building"

If you read my previous post for Chapter 1, you knew where this would end up. For a book devoted to the practice of software construction, of course "building" would be seen as the best metaphor, and there are good reasons for that. I studied architecture and currently work for an architecture firm, so perhaps I am also a little biased, but there are so many useful concepts conveyed by this conceptual model for programming. Like our farming metaphor and perhaps more so, a great deal of active planning and preparation is implied. Nobody would start to construct a house without first coming up with a design, documenting that design through drawings and specifications, planning a construction schedule, and budgeting materials, time, and labor for the building process. Last week I touched briefly on the importance of planning and design for computational processes. My baptism in computational design came through putting together ad-hoc Grasshopper scripts, often without much forethought. This usually works out fine because the stakes for one single script are often quite low and errors can be fixed quickly without much consequence. Another great teaching point about McConnell's "building" metaphor is that it becomes quite easy to see the direct (or perhaps even exponential) relationship between project scale and planning complexity:

Building a four-foot tower requires a steady hand, a level surface, and 10 undamaged beer cans. Building a tower 100 times that size doesn’t merely require 100 times as many beer cans. It requires a different kind of planning and construction altogether.

At larger scales, more effort in planning is required to handle that complexity; errors further down the line are proportionately more disruptive and costly. With scale also comes the need to carefully choose which parts of the project to actually design as opposed to simply specify. In our aforementioned house project, we might specify a modular kitchen cabinet system as opposed to actually designing custom cabinetry and paying a woodworker to manufacture it. As any architect or contractor will tell you, the difference in cost and effort is enormous; however for some clients the customization is worth it. Other building components you would rarely design from scratch except in very special circumstances—windows, for example. Some things like air conditioners or washing machines simply must be specified, and it would be ridiculous to try otherwise.

These architect's and contractor's choices have direct parallels in programming. For complex software, the planning and choosing of components as well as the coordination and integration of everything is just as important as the time spent writing code from scratch. Time and effort spent toward design, coordination, sequencing, and phasing (words used all the time in architecture) can pay big dividends when it comes to construction. To use another trite and overused metaphor, it's rarely worth it for me to reinvent the wheel when I can simply order the right one from McMaster.

Given that this is a blog series about learning, however (and since we already have seen that metaphors have limits in their usefulness), we will be reinventing some wheels in the coming weeks and months. Heuristics are wonderful ways of simplifying our thoughts and figuring out what's actually important. They're flexible enough to bend and blend with other metaphors as needed, though we should also know when to put a limit on our heuristic flights of fancy and realize that you can take a metaphor only so far. Two paragraphs ago I actually found myself wondering what sort of direct parallel I could make between an air conditioner and some sort of C# utility class in a Grasshopper plugin. Just as a well-stocked toolbox affords a skilled contractor different approaches to nearly any problem, our personal collections of metaphorical models can suggest better paths forward for those willing to be flexible and try new ways of thinking.


If you're new to this series, you can read more about my motivations for this project in the introductory post:

Code Complete - Introductions
Why I’m doing this, what I hope to get out of it, and how you might benefit from following along.