Originally appeared on my blog
You can't get near education theory in the 21st century without Benjamin Bloom's categorization of cognitive activities. Yes, there are accompanying taxonomies for affective and psychomotor activities, but for now, I'm focusing on the cognitive set. Let's all recite them together:
Amen. These are ordered by cognitive complexity, and as it so happens, map extremely well onto the process of learning the skills of computer programming. Perhaps that's obvious: a framework for describing learning describes how you learn something. Still, I'd argue that the congruence is remarkable. Some subjects engage with many of these levels at once, asking students to analyze, synthesize, and evaluate all at once. In programming, it is difficult to approach a higher echelon before mastering the ones beneath.
Let me explain with my own experience learning these skills.
There are ways in which learning to program is like learning a language—the symbols and syntax of each programming language is unique, and it takes time to memorize these sufficiently to produce code that is not riddled with errors.
Here's a bit of code in Python—my first language—that checks whether a number is even or odd:
def is_even(n): if n % 2 == 0: return True elif n % 2 != 0: return False
I am willing to bet that even if you've never read a line of Python before, you can make some sense of that. The modulus (
%) returns the remainder of integer division, so essentially I'm saying:
If, when dividing
nby 2, I get zero,
nreally is even.
Otherwise, if I get anything that isn't zero,
nis a phony, a fake. A flim-flammer.
This bit of Python is syntactically perfect. Of course, it took me days to be able to rattle off something like this without constantly checking the reference documentation. But the syntax, the symbols, were the first thing I had to learn. I had to remember what Python was even supposed to look like.
But you know what? This code is partially nonsense. It's a little like a Chomsky sentence: structurally sound, but functionally useless. To fix it, I had to comprehend something more about the task. In this case, something about the nature of functions, and the nature of if/else statements.
"If it's sunny, then I'll go to the beach. Otherwise, I'll go to the movies."
if sunny == True: go_to_beach() else: go_to_movies()
A contrived example perhaps, but that's how conditionals work. We perform tests on our information, and then we execute different commands accordingly. You can see how the same structure exists in English, and maps well onto Python's syntax. But you'll also notice that the second part of the code is an
else, not an
elif like above. Conditionals, if they have more than one option, must end with
else; it's the default behavior. And I didn't comprehend why when I wrote the syntactically-correct version above, it was yelling at me.
Once I comprehended the rule that conditionals must end in
else, I rewrote it as:
def is_even(n): if n % 2 == 0: return True else: return False
But I still had more to comprehend. This time, about functions.
def is_even(n): line above is the start of a function. Like in math, functions take information in and spit something back out. That's what the
return symbol is all about: spitting back either
False based on whether the number is even. Functions can only return one thing—after they do a return, they stop until they're "called again." So if I have
4 to my
is_even function, it would test to see if
4 is evenly divided by
2, and if so, return
elif ("else if") part of the code isn't even touched in that case.
Turns out, I don't even need the
else! Just a single
if will do, because if that test passes,
return ensures the rest of the function isn't run. So my final form looks like:
def is_even(n): if n % 2 == 0: return True return False
A tad more elegant, no? Concision isn't necessarily a goal for programmers (readability is), but it tends to result from a deep understanding of the tools at one's disposal.
UPDATE: Bob accurately points out that the following:
def is_even(n): return n % 2 == 0
...is even more elegant. I often, when writing teaching code, leave the last step of elegance to preserve clarity. Still, the above is worth understanding as an improvement.
Certainly writing a function to tell even numbers from odd is a bit contrived. Nevertheless, the patterns I learned in this exercise would become invaluable in the countless functions I went on to write.
If we zoom out from the specifics of evenness/oddness, what is this function doing? It's determining the nature of an object, sorting it into one of two categories. Of course in human life, binary states are rare; life is shades of gray. But in pure reason, it is often the case that we must determine whether something is or is not. Every time we do, this pattern emerges. With a firm comprehension of the form of this reasoning, of its pattern, I was able to apply it to other problems.
As a writer and English teacher, I took pride in my proofreading abilities. I'd find that missing Oxford comma, that malapropistic "its." Reflecting on this ability, I'm certain it derives from sufficient practice, yes, but also sufficient reading such that my comprehension of the "rules" of language made violations of the rules readily apparent.
A typo or grammatical error can be irritating. A program that doesn't run because of a logical flaw or a typo (it can be difficult to tell one from the other) is infuriating.
Here's a line I feed to all my students: programming is a uniquely frustrating discipline, because most of the time you spend working on a problem, it feels like you're failing. Once it works, you stop and move on to the next. And when you get it wrong, you know right away. No craft so ruthlessly exposes your own logical flaws.
Okay so to properly analyze one's own code or someone else's, the first two tiers of the Taxonomy must be well in-hand.
Ah, but analysis in the realm of programming also refers to the problem domain. A programmer has to understand the operating parameters and solve within them. To build a bridge, one must first read the terrain, measure the gorge, and plan carefully. It is in analysis where programming begins to resemble engineering in its thought processes.
The examples so far have been rather simplistic, in part because they can afford to be. And even in most programming curricula, the tasks set students will be one- or two-dimensional up to the level of synthesis: write a function that takes X and returns Y; implement function A to integrate with output from function B. You'll find significant amounts of scaffolding in the early chapters of a programming curriculum. In order to give beginners anything like an authentic experience, the tasks set to them have to be contextualized in ways that resemble what they'll encounter later in their journey. Real software applications have a lot of moving parts. It's a Rube Goldberg machine up in there.
So by the time students reach the synthesis phase, they can code a multidimensional project. A real application.
Or, y'know, direct an OK Go video.
Up here in the thin air of Bloom's, we have evaluation. In programming—and perhaps in writing—this is where style emerges. The programmer has a strong enough foundation in their craft to begin making choices about how to approach a given problem. Shall we use object-oriented or functional patterns? Would a stack or queue data structure make more sense? Should I attempt a recursive algorithm?
Choice may even extend into the programming language for a given problem domain. With broad experience comes a broad array of options, and programming languages are just tools.
But how could a programmer make such choices without experiencing extending to fully completed projects. Put another way, programmers must have completed a synthesis before ascending to evaluation.
Bloom's Taxonomy fits the programmer's journey well enough, as far as it goes. However, one criticism of the Taxonomy is its hyper-individualism. It's a curious model, given the social nature of, well, humanity in general, but certainly of learning. Any Harkness teacher will tell you that deep learning is a social enterprise, yet Bloom functionally elides discourse and dialectic in the Taxonomy. One might argue that the emotive component of the Taxonomy speaks to it, but emotional activities are specifically distinct from the cognitive, yet intellectual discussions are definitionally cognitive.
So I have beef with Bloom here, especially when it comes to programming, since programmers no longer work in solitude. To fully master the craft, a programmer must join a community of others, sharing experiences and code in an open marketplace of ideas.
It takes courage to publish one's code to the internet, open to the scrutiny of others. Yet millions do this every day. We'll talk more about open source tomorrow, but joining the community of programmers is an integral part of mastery. Learning how to discuss issues, contribute helpfully on projects, and hold oneself accountable for assigned work as part of a team transforms programming from craft to profession.
Lastly, the young programmer will hopefully start their own open source project that develops a community of contributors around it. Here, the programmer is now also a connector of people, responsible for the communal bonds that develop around the common goal of the project. Success here is no longer situated in the craft of programmer, but the more holistic human skills we hope to impart to all students.
In short, the full path of the programmer is not so different from other skills or disciplines: mastery makes one, through the hard work along the way, a good person.