A few of my earliest students I mentored at Bloc.io struggled with the "Developer Mindset". In particular, they would have a hard-time scanning and understanding code, sometimes even chunks explicitly covered in the course. The inner logic of a program, they would mostly miss, and as frustration built up, the less combative amongst them would start falling behind.

I'm convinced that anyone can learn just about anything given the sufficient amount of time and effort, and most importantly the proper environment, so it got me thinking about what exactly was preventing those students from succeeding in learning how to code, and how I could best help them overcome that overwhelming feeling. What I've come to realize over time is that all my struggling students have a common weak spot, one that is actually unrelated to programming.

First Sight & Second Thoughts

There is an ability which happens to be very, very useful for programming but is certainly not limited to it indeed: metacognition.

Metacognition is often defined as the act of "thinking about thinking". The concept is useful to make a distinction between cognition, the act of knowing or learning, and what is about cognition, for instance the act of assessing what would be the best strategy to learn something. Cognitive skills are necessary to perform a task, while metacognition is necessary to understand how the task was or should be performed. It's pretty clear already that in order to be efficient with cognitive tasks, some degree of metacognition is required. Sure, one may copy-paste a program from StackOverflow, and maybe even get a general feeling of how it functions, but in order to be able to customize the program or use it as boilerplate code, one needs to understand something more than the program itself.

The above may sound obvious, but I had to accept that metacognition in practice is far from being spontaneous behaviour. For instance, truly understanding a program is a challenging task for many newcomers, and when you look at what "truly" means in that context, it's expected it would be. It means that nothing feels magical for each statement of the program is conceptually under control and makes sense as a purposeful technicality part of a bigger picture, the very program — the latter eventually making sense as a whole as well. Being able to build that level of awareness is a skill on its own! One metacognition can tremendously help with. As a mentor though, I had to empathize more than I was used to, in order to understand why exactly it was so hard for some students to seize on metacognitive skills. For one thing, many students first had to take cognizance of metacognition itself!

It's one of the reasons why I started speaking explicitly about metacognition with my students. But instead of describing as "thoughts about thoughts", I prefer to speak of a process, that of "building self-awareness". It's actually a twofold process, for deep comprehension is orthogonal. Let me explain what I mean by that with an idiom by Terry Pratchett:

First Sight and Second Thoughts, that’s what a witch had to rely on: First Sight to see what’s really there, and Second Thoughts to watch the First Thoughts to check that they were thinking right. — Terry Pratchett, The Wee Free Men

First Sight is all about untainted perception. When presented with a bunch of code lines, it would mean reading those lines as they are and not as you wish they were, avoiding skipping or mentally editing characters out of inattention or because it's "easier" (the latter would be Second Sight, "wishful perception" if you want).

Second Thoughts acts as an insurance policy or a fact checker. By limiting yourself to the implied First Thoughts, you would understand what the code lines you carefully read do, but would remain unable to assess whether they are efficiently solving a problem, or solving a problem at all for that matter.

First Sight is when you can see what’s really there, not what your heid tells you ought to be there. Ye saw Jenny, ye saw the horseman, ye saw them as real thingies. Second sight is dull sight, it’s seeing only what you expect to see. […] Thoughts are the everyday thoughts. Everyone has those. Second Thoughts are the thoughts you think about the way you think. People who enjoy thinking have those. — Terry Pratchett, The Wee Free Men

Many if not most individuals live a credulous life, fed by Second Sight and First Thoughts: they see what they want to see, they can only ever think what fits those biases. I know I do… from time to time! Problems arise when someone makes a habit of that, consciously or not, or when someone relies on that "strategy" to cope with difficulties, like… learning a challenging skill, say, programming.

When I said that deep comprehension is an orthogonal dual process, I meant that you first have to reach out to reality (leveraging First Sight & First Thoughts) and then, must cross-check that perception to dismiss any self-imposed misconception you might not be aware of (using Second Thoughts vs Second Sight). It's quite demanding for some people to do that, but for it's a skill, it can be trained, and the more you practice it, the easier it gets and the more sturdy it becomes as a learning tool.

A strategy for code analysis: Double Top-Down

Amongst the different "tricks" I share with students, there is one I especially like: it basically recommends scanning new code twice before doing anything bold. In order, one would:

  1. scan pattern-wise
  2. scan line-wise

"Line-wise" is what most new developers feel natural doing: "start with line #1, read it, try to understand what it does, move on to next line, repeat". The problem is, diving into code like this without any sort of context makes it actually very hard understanding it. Many students who pretty much skip the big picture and go straight for analyzing (or so they think!) the statements they can see on their screen, quickly freeze up with confusion. That's what the first step, "pattern-wise", is designed to help them overcome.

Let me share a real-world example. Pick that piece of spec which is testing validations for a Vote model in a Ruby on Rails app:

describe Vote do
   describe "validations" do
     describe "value validation" do
       it "only allows -1 or 1 as values" do
         up_vote = Vote.new(value: 1)
         expect(up_vote.valid?).to eq(true)

         down_vote = Vote.new(value: -1)
         expect(down_vote.valid?).to eq(true)

         invalid_vote = Vote.new(value: 2)
         expect(invalid_vote.valid?).to eq(false)

From time to time, I have a student struggling with that spec. I shall ask him to "explain the code to me", and it usually goes something like this:

Student: Ok, so it reads "up_vote equal vote dot new with an argument, value is 1", ok, and then there is "expect" and… and… and I don't really know what this is. What's "expect"?

Good question indeed! But it's the wrong question to ask oneself :cat:

At this point, my goal is anything but answering that question. I'd rather want the student to become aware of why this question is irrelevant at the moment and what would be more interesting to inspect. In order to do so, I'm going to be fully explicit about how I would answer the same question, "explain the code". I usually say something like this:

Mentor: That's a good question, but I'm not going to give you the answer right away, because knowing what "expect" is, well it's actually not going to help you that much at this point. What about "to", or "eq"? What about the rest of the code? "expect" is very specific, maybe it's important, maybe not, the thing is, you can't tell for now. So I'm going to describe my thought-process as if I was answering the same question I asked you, "explain the code", and I'm pretty sure you will see how my strategy is going to help you answer your own question, eventually. Ok?

Student: Ok!

Notice how:

  • I promptly gave a hint as to why the initial question is irrelevant;
  • I introduced my forthcoming demonstration as being a "strategy", which will eventually empower the student.

Mentor: First, instead of diving into the code itself, like you did, I'd rather breath and step back. I literally step back from my screen! and I look at this piece of code I'm discovering. My initial goal is to get a feeling for it, before actually reading it thoroughly. What I see—and again, I'm not reading the code right now—well, I see independent groups of lines. Did you notice the blank lines, demarcating three groups?

Student: No I didn't.

Mentor: I don't know whether it's been organized this specific way on purpose, and whether it's important, but hey, I noticed… It's purely visual analysis as this stage. I also notice that the three groups are very similar in shape. They consist of a couple of lines, the first line is an assignment, there is this "equal" sign in it, and the second line, well, it's filled with keywords I don't know about yet, like "expect", but reading in one go, as simplified English, it's basically saying: "Expect 'whatever' to equal true or false". Ok… Sounds like some kind of boolean test. Oh, and the three groups do look the same: same kind of assignment, and then same kind of test. So the fact they've been separated by blank lines, I'm pretty sure it was intentional now, to help me get started I guess, and to make it obvious there are three tests in here.

Notice how:

  • I'm dead explicit about everything that would come to my mind while I'm discovering the code;
  • I'm using a fairly relaxed, inner tone, but am fairly persnickety about my wording though, using technical vocabulary;
  • I highlight the existence of patterns, using successive abstractions: visual pattern (the three groups), then logical pattern (assignment + test-case);
  • I make sure to introduce statements about my self-awareness ("My initial goal is…", "I'm pretty sure now that…", etc.)

I keep going:

Mentor: So, there is a pattern. Do you see the pattern now?

Student: Yes. Pretty obvious actually now that you showed it…

Mentor: Three times in a row, I'm going to create a variable, and test something about that variable. So, it probably means I could review only the first occurrence of that pattern, the first couple of lines, and pretty much understand the whole spec in the process. At this point, I already have the feeling that I could rely on the English semantic of those new keywords, like "expect", to understand what's going on here, even though I don't really know what they do. Do you agree with me?

Student: Yes I do.

Mentor: Ok, so far it has been me looking at the code, literally looking at the code, in order to get some kind of "big picture". Now, I can proceed with reviewing the very first line in more details, just like you did. Remember how you jumped for it though, and got confused?

Student: [usually nods or laughs]

Mentor: The difference is that I have some context about the code now, so I'm less stressed about analyzing it, it's not overwhelming anymore. That first line, it's assigning a value to a variable named "up_vote". The value is an instance of the "Vote" model, with a specific value of 1 for the attribute named "value". I'm in a spec file, but there's been no "testing" so far, so I can expect it will come later. Moving on to reviewing the second line, I encounter that new keyword, "expect". Cool, sounds a lot like a test being prepared. It looks like a method call, by the way. The argument is referencing the variable that was assigned in the previous line. Only, a method is called on that variable, "valid?", which I don't know about. But it looks like a question, something one could answer to with "Yes" or "No". So I'm not really surprised it's compared to "true" or "false", and I was already aware the tests are "boolean tests", it's something I noticed earlier on: I said it was "comparing 'whatever' to 'true' or 'false'". The "expect" keyword, I'm not sure exactly where it's coming from, but I can understand what role it's playing, so I'm good with that. I look into it later on, it's not that important after all.

Notice how:

  • I keep involving the student, comparing what I do and think, with what he did and thought;
  • I avoid reading the code, rather leveraging what I can recognize in terms of structure (variable assignment, method call, etc.) to describe what the code is most likely doing;
  • I make sure to use words with a metacognitive semantic, such as "realize", "aware", "notice";
  • I stress that I'm not surprised by the code ahead of me.

I eventually formalize the method I've just demonstrated, so it can be reused by the student:

Mentor: Alright! So let me quickly summarize what I just did. I followed a two-step process: first I described the code "top-view", looking for patterns; then I logically analyzed the code, line-by-line. With the first step, I tried to be as "neutral" as possible. Sure I made some assumptions about what the code was doing, what it meant, but I knew I wasn't going to be adamant about it. I merely gathered facts and "questionable insights". The second step was more sequential, and it allowed me to check whether I was right with those insights. It boils down to "first scan pattern-wise, then line-wise". Does it make sense? Do you feel like you could apply the same strategy next time you read some new code?

Student: Yeah it makes sense, "pattern-wise then line-wise"… Let me try actually!

Mentor: [discreetly delighted] :fireworks:

That, was Double Top-Down.

Behind the scenes

The fictional conversation above is actually pretty close to what could happen during one of my mentoring session. It was a singular example, about a spec; when analyzing a complex method, for instance, the "pattern" to mark off could be the "Four parts of a method". The point is, no matter what the code under review is going to be, a student will know about a strategy to handle it, and that strategy builds upon self-awareness.

As a mentor, I'm not really trying to teach people what to think, but rather how to think. Students sure spend an awful amount of time taking in new concepts, and very often they would complain about the fact they "don't even have time to think". But from my perspective, the fact that a student will learn useful web development chops along the way is actually almost incidental: they could have learned something else than programming entirely and nonetheless have trained this same ability, metacognition, which is precisely about thinking! Students sensible of that fact usually are the most successful ones by the way.

Once they are aware of metacognition, in order to help my students get the most out of it, I regularly provide them with the kind of micro-strategy I exemplified above. I reviewed scientific studies to borrow or adapt generic metacognitive tools, tweaking them into techniques suited to learning programming.

For instance, "Double Top-Down" is nothing genius, I bet most trained developers do something very similar naturally, but believe me, it's liberating for many students because it fits into their learning process and it gives them control. Formalizing it into something a newcomer could easily seize upon was actually challenging for me though. I don't want to improvise, I don't want to get inconsistent results, and I don't have much time to digress. The strategy itself, and the way I could best demonstrate it to my students, I designed using a metacognition framework called strategy evaluation matrix (SEM). It's an instructional aid for developing awareness of metacognition amongst students. One SEM1 I especially like is the following one:

Strategy How to Use When to Use Why to Use
Skim Search for headings, highlighted words, previews, summaries. Prior to reading an extended text. Provides conceptual overview, helps to focus one's attention.
Slow down Stop, read, and think about information. When information seems especially important. Enhances focus of one's attention.
Activate prior knowledge Pause and think about what you already know. Ask what you don't know. Prior to reading or an unfamiliar task. Makes new information easier to learn and remember.
Mental integration Relate main ideas, use these to construct a theme or conclusion. When learning complex information or a deeper understanding is needed. Reduces memory load. Promotes deeper level of understanding.
Diagrams Identify main ideas, connect them, list supporting details under main ideas, connect supporting details. When there is a lot of interrelated factual info. Helps identify main ideas, organize them into categories. Reduces memory load.

Each row is a generic metacognitive strategy.

I usually start with the third column, which is about conditional knowledge, ie. understanding when a strategy fits given specific constraints. Is my student going to discover new code? Or is he going to try to fully understand code he's already familiar with? Or maybe to write code on his own? In the example above, the student had to explain code he wasn't familiar with, so good matches in the SEM matrix would be "Prior to reading an extended text" and "Prior to reading or an unfamiliar task".

The second column is about procedural knowledge, ie. understanding how the strategy works. It gives actionable steps. Instead of searching for "headings, higlighted words, previews, summaries", a student learning how to program would look for "patterns, keywords", etc. but it's pretty close. "Pause and think about…" is obviously always relevant. You can see how it shaped the "pattern-wise" aspect of Double Top-Down.

The fourth and last column is about conditional knowledge once again, ie. understanding why using that strategy is relevant given specific constraints. It's the kind of insights I try to share throughout my demonstration with my students. For instance, "Provides conceptual overview" I often rephrase as "getting the big picture"; "Makes new information easier to learn and remember" I sometimes translate as "not being surprised".

In the end:

  • for the first step of Double Top-Down, "pattern-wise", I relied on the strategies named Skim and Activate prior knowledge;
  • for the second step, "line-wise", I relied on Slow down and Mental integration.

The result is a custom, efficient strategy tailored to my students' needs, which I've been experiencing very good results with. It's a good mix of First Sight and Second Thoughts, and it has helped my student become more aware of metacognition. I created a series of such strategies using the matrix above, some formal, some informal. More generally, the matrix I use as a guideline to assess whether the explanations I give are actually helpful, and whether a student is self-aware enough. I can always work on a specific aspect if need be.

Another very important takeaway from this, is that those metacognitive skills are pretty much domain-general, meaning that if a student becomes handy at leveraging them for programming, it's very likely he's going to be able to use them for tackling a wide range of disciplines. At the end of the day, programming might not be (only) about programming.

Beyond metacognition

Metacognitive skills and strategies I've been talking about help a lot, but they are barely tangent to a very important aspect of learning: monitoring of the learning experience. A student may ask herself: what is my goal? What are my expectations? Do I understand what I am doing? How good am I doing? What didn't work? What could I improve? As a mentor, I also need to be aware of where the student is, what momentum I can rely on, to best adapt my methods. That kind of questions is about regulating metacognition, controlling one's learning experience.

The happiest and usually most efficient programmers are self-regulated learners, something a I shall discuss in a follow-up post.

  1. From Promoting general metacognitive awareness, Instructional Science, March 1998, Volume 26, Issue 1, pp 113-125