Peter Seibel recently blogged to challenge the idea of reading code for reading’s sake. “Code is not literature and we are not readers,” he concluded. “Rather, interesting pieces of code are specimens and we are naturalists.”
That’s partially true. Interesting pieces of code are specimens. Yanked from their context in programs or frameworks, lines of code become like dead bugs on pins in boxes.
It might be nice to sit around with a snifter of brandy and discuss a dozen lines of code as though they were some exotic insect or even poetry, but few developers can afford that luxury. Developers have jobs to do and problems to solve that are embedded in thousands of lines of supporting code. Often, these lines are built by numerous developers attempting to address a set of problems that evolve unpredictably over time.
But that’s not to say that developers don’t read source code. Reading is actually the first thing that goes into solving most problems. The initial step is usually to pick apart a stack trace or grep through any log files that might be on hand. But then it’s on to reading the suspect portions of the source code, and the detective work begins.
That reading is not glamorous. Day-to-day source-code reading practices are often a matter survival: just enough to patch or hack together the code and maybe some unit tests to live to see another day.
To elevate reading source code to the level of a professional development activity, I propose a model for better understanding what all goes on in the act of reading source. The aim? To show how to leverage that into ongoing, embedded professional development. No reading groups or brandy snifters required.
Reading Isn’t a Simple Activity
Most people speak about reading as though it were a simple and boring activity, like breathing or sitting. But reading is actually a very complex activity. Even the act of reading through source code requires many different types of comprehension. Recognizing these different types of comprehension as they happen is key to transforming reading source from a survival skill into something that makes us better developers.
There are numerous taxonomies of reading comprehension. One that I’ve found useful and relatively straightforward is outlined in an article by Richard R. Day and Jeong-suk Park published in the online academic journal Reading in a Foreign Language.
Day and Park identify six types of comprehension–literal, reorganizational, inferential, predictive, evaluative, and personal response–that I will map onto reading source code. I’ll also describe each of the levels and suggest ways that developers can use the insights from each type of reading to shift from survival to professional development.
Literal Comprehension: Lines of Code
Literal comprehension is probably what people most often imagine when they talk of “reading source code”: actual eyeballs scanning actual lines of source. At the literal level, reading is all about comprehending Ruby’s syntax: a class is instantiated; the members of an array are passed to a block. This level of reading is essential for beginners learning a language, but it is also the mode of reading to correct errors like misspelled variables or methods.
But as a reading practice in service to ongoing professional development, even for non-beginners, this is the level of comprehension where we encounter unfamiliar patterns and Ruby-specific idioms. I remember the first time I encountered conditional assignment that followed this pattern:
x = if condition true else false end
That example reinforces that yes, all expressions in Ruby return a value:
x isn’t being assigned an
x is being assigned the value returned by the statement. But I’m getting ahead of myself: those are observations derived from the inferential level of comprehension, described in a bit. What matters at the literal level is that entire Ruby statements can live on the righthand side of
At the level of literal comprehension, recognizing new patterns is an enormous professional-development opportunity. I maintain for myself what writing teachers call a commonplace book of awesome source code patterns: a personal collection of examples found in the wild (like Seibel’s specimens), to serve as guidance or inspiration in future programming efforts.
A commonplace book is a useful counter to the antipattern books, with their series of don’t-let-this-happen-to-you warnings and mistakes. Literal comprehension that focuses on bad examples invites the possibility of unintentionally repeating that which we see most frequently (which is probably why Ruby developers coming from PHP, for example, tend to begin by writing Ruby that looks a whole lot like PHP). Balancing antipatterns or just plain old bad code with shining, smart examples is an important professional exercise. It’s also essential to building a repertoire of Ruby idioms.
Reorganizational Comprehension: Parts Form a Whole
Code is not literature, in part because it is less like a novel than it is a massive dictionary split over many volumes. This is especially true in Ruby, with its extreme flexibility for splitting modules and classes over multiple files, defining singleton methods, and so on. In reorganizational comprehension, a developer builds a mental model of the source code’s component parts–the actual lines of source encountered in literal comprehension–regardless of how the code is organized.
For professional development, reorganizational comprehension is the centerpiece of meaningful refactoring. Models and controllers in Rails 4, for example, were extensively restructured with the introduction of Concerns, which abstract and extract reusable pieces of models and/or controller logic.
On a humbler scale, the professional lessons from reading that involves reorganizational comprehension can aid developers in rethinking the organization of Ruby modules, classes, and methods as well as the files that contain them. Reorganizational comprehension establishes the mental model of the program. Refactoring can then aim to align that model with the actual components of the program.
Inferential Comprehension: Filling in the Gaps
Literal and reorganizational comprehension are dependent on reading specific lines of code. At the inferential level, though, developers bring existing knowledge to bear on the reading of specific lines of code.
For example, a method like
#each suggests an instance of a collection class such as
Array or maybe a custom class that inherits the
Enumerable class. Beginners new to a framework such as Rails often struggle to infer which methods or classes are provided by Rails, and which are part of the Ruby standard library.
Inferential comprehension encourages developers to read and cross-reference documentation and to do other kinds of research across books and blog posts. Inferential comprehension, especially for newer developers, presents a significant challenge. But, as in the brief reflection above about assignment and expressions’ return values, it also becomes a professional-development method for gauging where you are in your own understanding.
Predictive Comprehension: The Interpreter in Your Mind
If inference relates to filling in details around specific lines of code, predictive comprehension is more about the overall picture of what a program is likely to do (or, more grimly, how it is likely to fail). Predictive comprehension is the Ruby interpreter in your mind: it’s the runtime for reorganizational comprehension.
In an ideal world, developers would all have perfect predictive comprehension. That would mean the elimination of all bugs, because it would mean a complete understanding of the entire codebase and even its unintended, buggy side-effects.
But failing that, predictive comprehension guides professional development in a very important area: unit tests. Like reading itself, developing a test suite may not be grounds for promotion or worldwide fame. But test suites are nothing if not the outcome of predictive comprehension. Knowing what to test and where to expect failure in a program are the first steps in building useful tests. More advanced methods, like test-driven development, require a different kind of predictive comprehension: predicting the behavior of a program that hasn’t even been written yet.
Evaluative Comprehension: Selection and Revision
Evaluative comprehension is the most sophisticated type of reading. It takes the form of comparing, for example, the many different gems for processing Markdown. But it is also the reading required for formal code reviews in organizations and open-source projects.
Professional development shifts outward at this point. Having reached the level of evaluating the code of others, especially in reviews, professional development turns toward teaching and mentorship. The challenge is to become more explicit in stating criteria for evaluation. That, in turn, will help you to guide and mentor newer devs on a team or project, while also helping yourself to become more internally consistent in evaluating your own work and the work of others.
Personal Response: The Roots of Style
A thornier cousin of evaluation is personal response. In its less shining moments, this level is the breeding-ground for religious debates surrounding matters of programming style. Day and Park observe that “while no personal responses are incorrect, they cannot be unfounded.”
And that becomes the professional-development takeaway at this level: beyond style, personal response moves beyond knowledge and towards something like wisdom. You might be personally against using the ternary operator (
x = condition ? 'foo' : 'bar') because it is something of a relic leftover from C. But being able to express the reasons for your personal response, as well as the specific context/use-cases where it’s a problem, will make you more sympathetic toward the Java or C# developer who’s recently come over to Ruby and doesn’t yet know about conditional operators like
||= and other Ruby features that are usually less ham-fisted than the ternary operator.
Integrated Comprehension Levels
As you’ve probably already noticed, in day-to-day work, no single level of comprehension is engaged entirely on its own. And sometimes, a level of comprehension isn’t engaged at all. How many codebases have bugs inadvertently introduced (or left unpatched) because of poor predictive comprehension? And how much poor predictive comprehension is the result of poor reorganizational comprehension, which might itself be a product of poorly organized codebases?
But that is ultimately the challenge of any kind of professional development. Whether you attend a conference or pick up a book to learn a new Ruby method or framework, any meaningful professional-development activity requires moving beyond tacit, ingrained skills and building a thoughtful awareness of the complexities surrounding each line of source that gets written every day.
Taking some activity as boring and mundane as reading, exploring its component parts–like levels of reading comprehension, in this case–allows us each to reassemble the activity in a new way and reap professional benefits that would not otherwise be possible.