3
$\begingroup$

I'm becoming more and more convinced that starting with an imperative attitude is not doing a favor to the students, especially if it's all about scheduling and repeating some scanf and printf in loops. I ignore if this teaching technique has any systematic treatment or evidence of efficacy, to me it seems that it produces students unable to process data, as I'll explain soon.

I have a class of students who show the following features, for which I'm unable to find a good framework besides listing them (but perfectly and angrily knowing where they come from):

  • They are convinced that for (i=0; i<5; i++) return i is going to return 5 results. They are obviously so much confused in thinking that they're doing a minor variation of for (i=0; i<5; i++) printf("%d\n", i).

  • Given a function that needs X as input they constantly point out to me that somewhere in the first lines we need to read X from somewhere, they just ignore that we can add another argument as needed.

  • They cannot trace the execution of a for loop, they don't check the loop condition: to them it's just "I do the body N times", I don't yet know what do they think of the index used in the loop besides that it magically happens to take the values 0..N-1.

I'm on the edge of despair. Just showing them how I'd do something and explaining its operational interpretation obviously doesn't work, they even show a sort of resistance to abandon this method of doing any imaginable thing just by throwing scanf's and printf's around. Do you have any suggestions for better framing their difficulties, or any examples that might be illuminating in this situation?

I am finding incredibly difficult to produce some wrong code that might appear correct in their eyes and could possibly shock them and making them understand that things are a bit different from what they believe. Any ideas?

$\endgroup$
  • 6
    $\begingroup$ The problem isn't that they misunderstand the code; rather, it's they have an incomplete (or incorrect) model of computation. They don't see memory as an array of cells, nor do they understand how the code gets executed. Flowcharts might help. Use a visual debugger that lets you see the call stack and variables. Try avoiding the "for" loop until they've mastered the "while" loop, which makes its steps more explicit. $\endgroup$ – Barry Brown Mar 7 at 17:30
  • 1
    $\begingroup$ Sort of yes, sure. They absolutely lack a model of computation and that's arguably a major point, how can you teach to write code without the least of models of computation? I recall the very first thing at my univ was: these are our building blocks, it was a minimal von Neumann model, but any would do, except the non-existent one. The problem is that this is what I think, not what was thought by the ones who initiated my pupils, and I cannot change their history, just trying to reshape their models, dealing with their resistance and below ground motivation in the meantime. $\endgroup$ – user9137 Mar 7 at 18:21
  • 1
    $\begingroup$ What about the mandelbrot set? It's remarkably simple, but nonalgebraic, and the output makes nice T-shirts $\endgroup$ – pojo-guy Mar 8 at 1:18
  • $\begingroup$ What language are you using? $\endgroup$ – ctrl-alt-delor Mar 9 at 11:25
  • 1
    $\begingroup$ Based on your comments to my answer, you won't be able to 'shock' them because they don't have enough correct understanding in place to have their assumptions violated. They need concepts first. $\endgroup$ – Scott Rowe Mar 15 at 11:10
2
$\begingroup$

It sounds like the program of study has a few bugs if 4th or 5th year students cannot reason about a loop.

I taught myself programming in 9th grade by writing simple programs that printed shapes on the screen using characters. For example, make a solid rectangle. Now modify the program to make it a hollow rectangle. Accept input to get the width and height, etc. Now draw triangles. Now diamonds scrolling up the screen. Now make them hollow...

Rather than fight against scanf and printf, use them in a way that is direct, gives immediate feedback, and is somewhat interesting.

You can introduce functions by taking the inner loop and making it into a "draw a line" function, with a variable length, offset and hollowness. Then build another function that makes a shape by calling the first function in a loop.

If they can't handle functions, nested loops and conditions after that, you have my permission to yield to despair.

$\endgroup$
  • 1
    $\begingroup$ I also was a Teaching Assistant for a "programming 101" course for non-computer majors, back in 1987. Same approach... "What does this line of code do? Now what does the next line do?" Some mastered it quickly, some struggled, some were uninterested. Pouring water on a horse does not result in people learning to program. Apples work though. $\endgroup$ – Scott Rowe Mar 15 at 2:24
  • 1
    $\begingroup$ Mmh, thinking about it again, maybe what they struggle with the most is not the concept of loop, it's that of mutation. I mean, they have this idea that things in for are going to be repeated, but probably they have no idea how: maybe they think "I want repetition -> I write for", but without understanding. They are so much confused that even I don't understand where is the confusion :D [...] $\endgroup$ – user9137 Mar 15 at 9:22
  • 2
    $\begingroup$ -- Apparently we are too proud of ourselves these days to begin the explanation at this low level. But I never had any question in my mind what a variable was, and when people said that mutation was confusing to students, I was like, yeah, like a doorknob is confusing... So when I encountered the dreaded concept, the horrific terror if Indirection the next year, it was not much of a stretch. Oh, a variable that holds an address. We can increment it, etc. But I get flack from people on this site about teaching this way. I learned this way. It works. $\endgroup$ – Scott Rowe Mar 15 at 11:03
  • 1
    $\begingroup$ Probably this is a case where the major difficulty is unlearning a confused way of writing mindless random things without any reason more than learning a concept that would clarify things but that requires thought energy. Btw another strange thing I see in younger students (to their defense it's the first time they see anything related to cs) is that they imagine the code do what they have in their minds instead of what's written. They give personality and force of will to the code. Also here it's quite difficult for me to understand why. $\endgroup$ – user9137 Mar 15 at 23:52
  • 1
    $\begingroup$ @BarryBrown yes, I have heard about this confusion too. I recall a girl saying, "How can x ever equal x plus 1?" It took me a moment to figure out what on earth she was saying. But if you drew a box on the board and wrote in it, then it seems obvious that a and y are not magically linked. People are not learning in a way that makes it obvious what is happening, which means that teachers are not writing on the board and uttering the correct words. It is not a magic spell, it is just explanation. The idea of Functional Programming will only increase this confusion. $\endgroup$ – Scott Rowe Mar 21 at 22:56
5
$\begingroup$

My advice to all of these problems is the same. Your students do not know how to draw out memory. This is a basic technique that most people who have been programming for a while do mentally, but that nearly all students need to be explicitly taught and shown on paper.

How to Draw Memory

There is a good chance that you have seen something like this before. Maybe when you were learning to program, or maybe when watching another instructor teach a beginner class. Drawing memory is about physically (with pencil or chalk) drawing a diagram of the state of the program's memory. For beginner programmers, some things are typically abstracted away, such as the idea of a register and the specifics about how memory is loaded and stored.

I am going to create a short video tutorial, showing how I personally draw memory, including how I draw loops and functions, but here are some basics in text form:

  • Variable is declared => I write the name of the variable and draw a box with a slash through it to signal that it is Null.
  • The Variable is defined => I erase the slash and write the value in the box. If the variable is ever reassigned, the value is erased, and the new value is written in its place.
  • The Variable is a pointer => the box contains an arrow to another, larger box that holds the data.
  • Loop => I draw loops (and other smaller scope constructs) as circles or "bubbles." At the top are the condition and an iterator value if it's a for loop (the iterator value is drawn like any other variable). An arrow points from the bottom of the loop to the condition. All locally scoped values are drawn inside the circle, and the whole thing is erased once the loop finishes.
  • Function => I draw functions as large blocks. At the very top, any parameter values are placed in their own box that is linked with an arrow. All locally scoped values, loops, etc. are drawn inside the function block, and the output is linked to the bottom of the box. I typically do not erase function boxes since they are likely to be used again, but you can if you wish.
  • Print Statements => I find it helpful to have a three-board view when drawing memory. One display contains the code, which we will go through line-by-line. The next display contains my drawing, an abstract representation of what the program "knows." The third contains the text output. This helps students visualize the distinction between what is happening and what is displayed to the console.

How to Teach Drawing Memory

In lecture, you should draw memory constantly. Any time you review what a piece of code does, you should draw it out with and for your students.

Since your students already sort-of know some basics, you will likely need to go back and explicitly teach them how to draw. Talk to them about why it's important to understand what the program knows at any given time and how it flows through lines of code, and ask them to brainstorm some ways to keep track of that.

Then, show them the basics for a very simple program that they understand. Something that declares and defines two variables and uses an if statement. Have them practice the drawing structure in small groups or pairs on a slightly different problem. Switch gears back to your normal lecture topic. The next lecture, have your students tell you how to draw yet another variant on the same few lines of code. If they can do that, introduce the next drawing construct, maybe a while loop. Repeat this slow and methodical process over the course of several lectures until your students are able to draw anything they can code. Then, when you introduce a new concept, you should show them how to draw and visualize it as part of the initial learning.

The goal is to make drawing second nature so that they do it when they hit a bug or look at code that is not their own. Tell them that eventually, they will be good enough at drawing that they can do it in their heads, but that for now, they should always do it on paper or a board so that they can practice. Until they are at least intermediate programmers, you should encourage or even mandate that your students draw the state of memory every time they approach a problem.

As you teach them this important skill, you should have them practice on code that works and on code that does not work. Give them something with a bug- a one-off error, or a logical issue. Tell them to draw it out or do it with them on the board. Once they are done, ask them to tell you what the issue is and how to fix it. Have them draw it again once it's fixed.

Once they know how to draw, you can ask them to do so in all sorts of situations. Put it on a quiz or test. Require a memory drawing before helping with a bug in their projects. Make sure your TAs do the same. Have them continue to draw memory during class, and encourage them to draw out tricky parts of assignments as they work on them.

How Drawing Solves Your Students' Confusion

You gave three specific examples of holes in understanding. Drawing solves all three.

They are convinced that for (i=0; i<5; i++) return i is going to return 5 results. They are obviously so much confused in thinking that they're doing a minor variation of for (i=0; i<5; i++) printf("%d\n", i).

A drawing of a function only has one return value, and it is at the bottom. They will see clearly that once a value is returned, the function is over. Since they won't reach the return statement until the end, they will return once, the final value of i. Due to erasing past values, they will only see the most recent assignment.

Given a function that needs X as input they constantly point out to me that somewhere in the first lines we need to read X from somewhere, they just ignore that we can add another argument as needed.

A drawing of a function has input values included. In practicing drawing, you will demonstrate that those inputs can be used as defined variables.

They cannot trace the execution of a for loop, they don't check the loop condition: to them it's just "I do the body N times", I don't yet know what do they think of the index used in the loop besides that it magically happens to take the values 0..N-1.

Drawing the loop involves updating the iterator each time through. They will never forget again after drawing out a loop that updates 10+ times.

I hope this helps, I will try to upload a rough video tutorial in the next few days and will update my answer with a link.

$\endgroup$
  • $\begingroup$ I completely agree with you, drawing the memory is the best thing I know for understanding imperative (stateful) code, simply because drawing the memory amounts to seeing the states transitions that are described by the code fragment. The problem here is that I cannot really do it in short time, I was hoping for something of strong effect: it's a 5th year class that has been trained for the past 4 to use global variables for everything and to emit swarms of printf in place of computing results. In my opinion this is about the idea of brain damage considered by ED. Am I wrong? $\endgroup$ – user9137 Mar 7 at 22:47
  • $\begingroup$ Looking forward to check out the video anyway, might be helpful! Possibly even just letting them see that I am not the only one to draw on a blackboard what the code does when executed. They think I'm kind of crazy for forbidding them the use of prints, and it's quite frustrating. $\endgroup$ – user9137 Mar 7 at 22:52
  • 2
    $\begingroup$ The term I always heard for writing the values of variables as the code is hand-evaluated is Desk Checking. I guess the idea was that people didn't formerly have computers sitting on their desks. They simulated the computations with pencil and paper. If someone cannot do this, they cannot program, because they cannot understand the code. $\endgroup$ – Scott Rowe Mar 15 at 2:09
  • $\begingroup$ Yes, Scott. Can't agree more. My 5th year students told me they last did something similar 3 years ago, not often and not reinforced, I have no idea how they did so but I'd guess not in an effective way because it's not second nature for them, worse it was still difficult to show them how I'd do it and they have no will in following this method. I could accept they consider it boring were they proficient, but clearly they aren't. $\endgroup$ – user9137 Mar 15 at 9:35
4
$\begingroup$

I recognize the feeling of despair! However, I don't think that you want what you are asking for. The most shocking bit of code that I know to show students is the code I wrote up here. Beware, it's quite strange! If you want a bit of fun, don't look at my explanations. Just copy the code into an IDE, look it over, make a guess about what it will do, and run it.

I don't think that this will help you with your students, however!

What you're pointing to are what I call "cognitive traps", and they must be addressed very specifically during instruction. You're already on the right track in that you are spotting them, but I don't think you'll make a lot of headway trying to find a single, unified solution.

The problem with being an expert

There is no one key understanding that will, for a novice, unify everything into a sensible framework.

There's actually a reason for this, which I previously wrote about in my answer here. In essence, overarching frameworks are the toolsets of experts. Factoids and small systems are the toolsets of novices. The way that novices become experts is typically by learning a set of small systems, and then in later revisitings, seeing how they are actually similar ideas.

Experts often wish to teach novices to see the big system, and show how all of the smaller systems operate under the same principles. If we could only get them to think about it the way we do, they would be able to work so fluidly! Principle to small systems is a top-down method of learning.

An alternative is to move bottom up: here, look at these three, apparently separate ideas. Now, let's link them together! While I am very impressed with @GreenGriffin's answer, I believe that this is the approach that they are taking here.

I would contend (and I know that our top user, Buffy, has also said this many times) that when we learn deeply, we actually do it in a spiral: we revisit an idea many times, each time enriching it by linking it to other ideas. This fits nicely with our current understanding of learning in the brain, in which information is physically structured within the brain as a pattern of interconnected neurons, and new information is learned by linking new ideas to old ones that we have already mastered.

Getting practical

So, what's a teacher to do? First, with regards to the three very specific things you've mentioned, explicitly call attention to them when you are presenting function calls and loops. They are traps that students can fall into, and you want to head them off at the pass. Give them a chance to practice doing it correctly immediately, and demonstrate what happens when they do it wrong. The longer you teach, the more such traps you'll discover and can thus help student avoid. That's part of becoming a better teacher.

And as for the large systems, revisit older ideas and keep linking them to new ideas as you teach them. Ask students to reflect directly on how this idea and that idea are similar. Ask students to make a drawing of how a loop iterator actually operates. The more that we help students link ideas together, the more abstractly they'll be able to work.

It's not a lightning-fast quick fix, but you will see improvement if you keep making improvements!

$\endgroup$
  • $\begingroup$ Yes! Definitely agree. (Besides having yet to see the java code :) ). For what I can I am constantly trying to show them small pieces and their connections. Problem is that so many times I already have shown them that a function cannot return many values, how a for translates to a while, how the variables are updated, they just kind of don't want to believe it, and their motivation is really 0. They're used to these crazy I/O operations: oh we need to know how many iterations, let's scanf something! I was even starting wondering: is this what cobol was made of? What's this thing? $\endgroup$ – user9137 Mar 7 at 23:14
  • $\begingroup$ Unfortunately I cannot introduce them functions and loops, they've already been introduced in previous years, they shouldn't even be so much unaware of those concepts. My goal was having them build small real algorithms, let's say for linear algebra, I have already given up on this actually. $\endgroup$ – user9137 Mar 7 at 23:21
  • $\begingroup$ When I mentioned the shocking effect I was thinking something so short as a for (i=0; i<5; i*=2). Just I don't think I am producing examples of good interest and of the right degree between too trivial and too crazy. Has anyone an arsenal of things of this kind? Btw, has anyone else noticed that learning steps in CS seem to be wider than other disciplines? $\endgroup$ – user9137 Mar 7 at 23:28
1
$\begingroup$

I, too, will give a somewhat orthogonal answer to your quest. I would suggest that you find a way to incorporate unit testing into your Java based course with Unit. You can give them tests or require that they write tests. You can require an explanation of them when tests fail.

But if you incorporate testing deeply into your teaching and requirements (Test First - Test Driven Development) these sorts of misconceptions should start to disappear.

If you demonstrate code to your class live with some projection equipment you can demonstrate such things to good effect.

$\endgroup$
  • $\begingroup$ Thanks for the answer, it has its place, but it's not adequate for my specific case: my course is not Java, we have no classes (fortunately!), we need just a very very basic intro to the world of computing. The pupils had a taste of COBOL written with C syntax and are addict to printf before computing. I showed them Javascript to work more gently with arrays, but they have a really hard time understanding what do I mean for "working with arrays": there is no output print and they are lost. I think adding the complexity of testing is a bit too much for people who cannot understand a for loop. $\endgroup$ – user9137 Mar 10 at 10:21
  • $\begingroup$ Hmmm. You mentioned Java in a comment. There are similar unit testing frameworks for most languages, actually. $\endgroup$ – Buffy Mar 10 at 10:25
  • $\begingroup$ I don't think I mentioned Java, I may be in error, but it may also be that I can be confused with someone else, my name does not seem designed for identification tasks :) Anyway, no probl. The more discussions I can see the happier I am. I'm starting to give up with this class because besides the technical problems they are also very demotivated and this complicates things even more: they sort of demand that I just write code while they do nothing with their brains, probably I'll end up with this. $\endgroup$ – user9137 Mar 10 at 10:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.