5
$\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$
9
  • 8
    $\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$ Mar 7, 2019 at 17:30
  • 2
    $\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, 2019 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, 2019 at 1:18
  • $\begingroup$ What language are you using? $\endgroup$ Mar 9, 2019 at 11:25
  • 3
    $\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, 2019 at 11:10

6 Answers 6

5
$\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$
10
  • 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, 2019 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, 2019 at 9:22
  • 1
    $\begingroup$ The splendor and mystery of Mutation. For some reason I never experienced e dreaded confusion about what exactly it is or how to use it in a program, but I have read that it is a major stumbling block. To be fair, a variable is unlike any other thing in the entire universe, you can just change its value any time you want. Noting else in our world works that way. When I was in 8th grade and a teacher was explaining programming, he drew a rectangle on the board, wrote 'X' next to it and said, "this is a variable." Then he wrote 7 in it. Then he added one to X, erased the 7 and wrote 8. -- $\endgroup$
    – Scott Rowe
    Mar 15, 2019 at 10:57
  • 4
    $\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, 2019 at 11:03
  • 2
    $\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, 2019 at 22:56
6
$\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$
6
  • $\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, 2019 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, 2019 at 22:52
  • 3
    $\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, 2019 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, 2019 at 9:35
  • $\begingroup$ Late comment: I try to emphasize drawing. Over the years (15+) I've realized that this is not unproblematic. I'll highlight: "the value is erased" on re-assignment; while it's the most accurate model, it has the downside of not leaving a record or documentation of what's happened to date (for students studying later or backtracking or "go over that gain?"). So we need to judge the options of (1) erasing state, (2) crossing out one and writing the new one next to it, (3) keeping a table with one row per iteration and state of all variables at each step... $\endgroup$ Dec 14, 2020 at 4:41
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$
3
  • $\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, 2019 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, 2019 at 23:21
  • 1
    $\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, 2019 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$
3
  • $\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, 2019 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, 2019 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, 2019 at 10:41
0
$\begingroup$

Is programming being taught from an abstract perspective? Some students when taught from an abstract perspective seem to treat statements as magic incantations that cause the computer to do something without seeing that there is an underlying structure to the language that is imposed by the nature of the hardware. If students have some understanding of how computers actually work and how programming languages match up with capabilities of the hardware, then they are more likely to try and see underlying meaning in the statements comprising the program. This will mean their underlying mental model will be more correct and their intuition about what will happen will be better.

I don't think this is a problem with imperative langauges, but an abstract/practical issue. Some students like an abstract perspective, others can't deal with it at all, so no "one size fits all" strategy will be effective.

I like @buffy's suggestion to use testing, as to test effectively you need to be able to predict what the code should do to make the test cases, which encourages students to understand the code. Getting students to do bug fixing is also useful, as again that involves looking at code and trying to understand why it is doing what it is doing, in order to work out how to fix it. The coursework I set for my second year university C++ class involves writing code, testing, bug fixing and refactoring, and it seems to work quite well.

$\endgroup$
0
$\begingroup$

I get the feeling that we are teaching in the wrong order. We should teach functional first.

In your first bullet point, it looks like you students are trying to invent a list comprehension/mapper. It may be easier to teach them how to do it this way that to teach how to implement it procedurally.

The 2nd point is just missing that there is more that one input.

The 3rd I would introduce foreach loops before general purpose loops.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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