One of my motivations in writing Modern Perl: the book came from many years of watching novices learn to program. I've used the "programming is like following a recipe" metaphor countless times, because it's accessible and easy and tactile. Yet programming is also about symbolic computation and the abstraction thereof, and that's not so tactile.
Programming is also craft work. In my mind, that's the foundation of the software patterns mindset: not finding mathematical descriptions and mechanisms of computability but discovering ways to arrange those mechanisms to achieve an aesthetic result while maintaining essential function.
Novices, of course, tend not to have the sort of good taste refined over time by hard-won experiences.
As a case in point, yesterday I found myself having to write code to traverse a tree structure. (It's the HTML/ePub emitter for the table of contents for the Onyx Neon book rendering pipeline, and it's why the free versions of Modern Perl are taking longer than we promised. That, and I had jury duty.) Anyone who's written this code successfully at least twice knows that the most natural approach is the recursive approach.
For various reasons of encapsulation, I have a document object which represents each input file, and a method on these document objects which can render the table of contents:
sub emit_toc
{
my $self = shift;
my $headings = $self->extract_headings;
return $self->walk_headings( $headings, filename => $self->filename );
}
That's simple and natural, and I'm sure most of you reading this could write
walk_headings()
in your sleep, knowing that $headings
is an AoAoA, where the leaves are heading objects and the branches are nested
array references.
For whatever reason, when I first started to write this code, I intended to
perform the recursion within emit_toc()
itself. I don't know why;
it seemed like a good idea at the time. I'd written a couple of lines. When it
came time to recurse, I noticed how much extra work I'd have to do to do the
right thing in the initial case but avoid doing the wrong thing in the
recursive cases.
At that point the natural solution was obvious: I'll introduce a new method which only does the recursion.
Maybe I'm especially stubborn, but I imagined myself as a novice programmer trying to cram everything in to one method. Maybe novices see programming as a lot of heavy lifting busywork brute force to get something—anything—to work that they think putting more characters on the screen is the only visible sign of progress. Maybe those of us who teach novices talk too much about functions "doing one thing and one thing only" and focus on the visible task the functions accomplish and neglect to mention that well-factored functions can just as well perform structural and architectural duties that supplement and ease the work of those visible tasks.
Certainly walk_headings()
does one and only one thing: it
processes the current level's leaves and branches. Yet only
emit_toc()
and walk_headings()
have to know that, and
outside of the class, it might as well not exist.
I didn't stop to think about any of this when I realized I needed another method to do the recursion itself. It was a very natural thing, born from experience in solving this problem many times.
That process fascinates me, and not only because I want to understand how I program and design in order to improve but also because I want to help new programmers improve without having to make as many mistakes and messes as I did.
There's some broken mark-up in the code sample.