Course goals, psychological states, and intro programming

Subscribe to Skilling news on the home page.

Kieran Mathieson

Course goals are often expressed as a list of statements like this: "At the end of the course, students should be able to..." That's standard fare, and it makes sense.

Arguably, these are statements about the states of students' brains at the end of the course. If people can do task X, then their brains have the knowledge needed to do the task.

What do vegetarian zombies eat?

Graaaains

The goals are a snapshot of students' brains, that is, what their brains are like at a particular point in time. How the brains get there is, of course, the essence of course design, but we need goals to guide the design process. This article is just about the goals, the outcome of learning, not the learning process.

When we list goals, how can we be confident that we have a complete list? One approach is to see what the research literature says about the types of knowledge brains needed to do tasks. Then we can make goals for each knowledge type.

We're going to use one learning context to guide the discussion: an introductory programming course in Excel VBA for business students. Why? Because that's the course design task I have right now.

Adela

Adela

What about other courses? Will this work for them?

If the course is a skills course, like programming, database design, or business writing, then much of it should apply. I'm not sure though. YMMV

Fact recall

Skilled performance relies on memories for facts, and schemas. (I'm using the term "schema" generally, to include models, patterns, principles, etc.). Fast recall of facts like what an integer is, and what the accumulator pattern is, reduce cognitive load when writing programs.

The accumulator pattern is for computing totals and such. Declare a total variable, initialize it to zero, loop across a data set, adding to the total each time through the loop. If you're an experienced programmer, you've done this a bazillion times, in one form or another.

We're just talking about fact recall here. Using schemas comes later.

Another example: the times tables. I don't need to calculate with 7x8 is. I just recall it. No appreciable cognitive load. This task has a lower cognitive load for me than for my daughter, who didn't learn times tables. (Mind you, she's better at other things than I am.)

So, that's one type of goal we might have for a course.

Course goal type: memorized facts

In this case, they'll be programming facts.

Automatic(ish) subtasks

Skilled performance is easier when common subtasks are automated. For example, for me, computing 7x8+5x6 requires one cognitive operation on working memory: 56+30. (Getting the 56 and 30 require simple recall, with no appreciable cognitive load.) 56+30 is something I can do rapidly, having overlearned such tasks.

Evaluating 56+30 isn't a recall task; I have do the addition operation in short-term memory. However, the operation is automatic, not something I have to actually think about. That is what "overlearned" means in cog psych. I don't know how I compute 56+30. My brain just does it, below conscious awareness.

Course goal type: automatic(ish) subtasks

Adela

Adela

What does "ish" mean?

Well, the goal is to get common operations to be fast, with little cognitive load. Cognitive resources then will be free for other things. The operations may not be fully automatic. That's OK, as long as cognitive load is low.

I'm thinking of something specific here: expressions. Programs are full of expressions. Here are some code lines, with expressions in italics.

y = m*x + c;

total = total + sales;

if (name === "rosie" || name === "renata") {

Students should be able to evaluate the expressions quickly. What will y and total be? Will the if block be run?

There's another issue as well: can students recognize the purpose of the expressions? For example, consider the second statement, total = total + sales;. Programmers recognize that as a typical statement in an accumulator loop.

The if statement tests for my dogs. Here's Rosie:

Rosie

Here's Renata:

Renata

Both sweeties!

However, recognizing purpose isn't about automatic(ish) subtasks. It's about schemas.

Applying schemas

Being able to quickly use schemas is an important aspect of skilled performance. For example, it's easy for me to apply the accumulator schema to the results of a Drupal EntityTypeManager query.

There are different types of schemas. For example, there are categorization schemas, like the periodic table. However, in programming, the most important type of schema is a pattern, describing the code for a particular task. Patterns are often expressed as pseudocode. Here's the accumulator pattern again:

Declare a variable for total

Initialize the variable

For each data value:

  total = total + data value

End loop

You use the pattern by instantiating it for a particular task context. Here's how you would express the first steps in different programming languages:

Dim total as Single

let total: number = 0;

$total = 0;

var total = 0;

float total = 0;

So, a skilled brain can pick schemas, and instantiate them for task contexts.

Course goal type: apply schemas

Problem solving

Problem solving is what you do when you don't have a schema you can just pluck out of memory. Problem solving relies on means-ends analysis. That is, "where do we want to go?" (the ends), and "how do we get there?" (the means).

Problem solving in the abstract

We can identify problem solving methods that apply in engineering, math, programming, writing, and other fields. They are heuristics, that is, general rules-of-thumb, rather than the specific techniques embodied in patterns. For example:

  • If you're having trouble understanding a problem, try drawing a picture.
  • If the problem is abstract, try examining a concrete example.

Bendor has a nice list:

  • Decomposition: break the problem into smaller pieces.
  • Local search: look for similar solutions, and adapt them. (Quite schema-ish.)
  • Seriality: make one small change first, then move on to the next.
  • Multiple minds: don’t work on a problem alone. Find out what others think, and use them as resources.
  • Imitation: don’t reinvent the wheel. Find out what others are doing, and copy them. (Schema-ish as well.)
  • Recombination: mix it up. Combine a number of different ideas to create a solution. (We do this is a lot in programming.)

There are other things you can do, like explaining a problem to baby Groot. As you hear yourself talk, you might think about alternative solutions. Still, the list is quite good.

I'm not sure how useful some of the general heuristics are for complete novices. Problem solving is done in a context, and the tools and methods people use are specific to that context. For example, to find problems with cars, mechanics use diagnostic machines. Programmers use debuggers, like Xdebug. Nurses use those beepity-beep machines.

My guess is that an abstract goal like "able to draw a picture" is not particulary useful for a programming course. Not that we don't draw pictures; we do. Flowcharts, ERDs, the UML family of diagrams... we draw lots of pictures. Course goals should refer to those specific types of pictures. For example, a goal for a database course might be: "able to draw an ERD for a data representation scenario." An ERD is a problem-solving tool specific to the database context.

So, when specifying course goals, we might contextualize the heuristics. Use decomposition, not in the abstract, but as applied to programming. That might mean course goals involving flowcharts, and pseudocode. Use seriality, not in the abstract, but as applied to programming. That could mean effective IDE use, testing, and maybe version control.

Problem solving in programming

In programming, I think of three problem solving processes:

  • Creation. Writing a new program.
  • Debugging. Fixing a broken program.
  • Adaption. Changing what an existing program does. Uses mental process from debugging, and creation.
Adela

Adela

OK, I see what you mean about context. These processes are just for programming.

That's right. They wouldn't work for other skills courses, though there would be similar processes.

Creation

When you're writing a program, you're trying to find a set of schemas that, when implemented and wired together, will achieve the program's goals.

It's not a simple linear process. Let's say you want to total some sales, so you pick the accumulator pattern. The accumulator pattern requires a data set. OK, write an SQL statement to fetch the data set from a database. Here's such a statement:

select sale_total from sales;

You can loop across the sale_total values, and add them up.

For each sale_total:

  total = total + sale_total

End loop

But then you say to yourself:

Self

Self

Self, I can change the SQL statement, so the database will compute the total for me.

select sum(sale_total) as grand_total from sales;

I don't need to write the loop at all.

You had already worked through part of the problem, before thinking of a way to get to a solution more quickly. Programming is like this. A step forward, then you learn something, take a step sideways... That's how you work on unstructured problems.

It's common to make a plan for a program, start writing the code, and during the writing process figure out that the plan won't work. This happens to programmers at every expertise level. Experts might spot problems more quickly, but they are not immune to the uncertainty that is standard fare in programming.

Programmers have to be metacognitive. They need to think about the processes they use to write programs, as well as how the programs will work. They should ask questions like:

  • What are the big pieces of a solution? How will the pieces fit together?
  • Am I getting closer to a solution? If I keep doing what I'm doing, will I get there?
  • Is there an easier way?

Perspicacious students might even ask:

  • What am I learning?

These meta-issues can be considered schemas, too, but they're schemas of a different type from programming patterns. They're fuzzier. It's less obvious when to ask what questions, and how to work out the answers.

But wait, there's more!

To novices, the goal is to write a program to do what the specification says. Expert programmers know that there are other objectives, too. For example, code that you write today might need to be changed tomorrow. So, you should write code that's flexible, and easy to change. A simple technique is to make code readable, with variable names like total preferred over names like monkeys_are_funny.

"Use meaningful variable names" isn't a pattern. It's a principle to follow when you instantiate patterns.

Course goal type: managing the program writing process, with metacognitive questions, heuristics, and principles

Debugging

The first type of problem solving is creating a new program. The next one is debugging. Suppose you already have a program, but it crashes, or outputs the wrong information, like:

The task often is to find a poorly implemented pattern (although not always; more below). The meta-work you do is different from that you do when creating a program. For example, in debugging, there are techniques for finding where in the program the bad code is. Once you've found a bug, you can use your schema knowledge to fix it.

There's another question, though: how do you know when there's a bug? Here's an example. Click the button below to run a program that will compute an average. Type one number at a time, and type -1 when you're done. For example, if you type 10 and press Enter, then 12 and press Enter, then -1 and press Enter, you'll see that the average is 11. Give it a try.

Works fine, right?

Click the button again. This time, type -1 and press Enter, with no other data. The program will say the average is your nan, or something.

That's a bug. The program should allow for the case where there is no data to average, and show an appropriate message. The bug isn't obvious all the time, though, only in the special case where there is no data. It's common for students to forget about special cases.

There are methods you can use to find bugs. Students need to know about them. For example, the binary split schema goes like this:

Insert a debugger break point half way through the program. If the problem shows up before that breakpoint, set another breakpoint about 25% of the way through the program. If the error doesn't show up at that breakpoint, put another one at 37.5% of the way through the program. Each step narrows down the bug's location.

When debugging a complex program, it often helps to build a mental model of the program. What schemas did the original programmer choose? Why? How do they fit together? Sometimes, a bug won't be in schema implementation, but a mistake made when fitting two schemas together. Perhaps one code chunk computes distances in kilometers, and another uses miles. The chunks work fine independently, but put them together, and an expensive space probe will crash into Mars.

Course goals type: testing a program to determine whether there are bugs, modeling (determine the schemas used to make) an existing program, finding bugs, and fixing bugs

Adaption

The third problem solving task is to change a program, perhaps to add a new feature. The meta-tasks are a combination of those used in creation and debugging. Build a mental model of the current program, then adjust the schemas to meet the new goals.

The same meta-questions arise as when writing a new program, e.g., are you on the right track?

Course goals type: can change a program's functionality, by building a mental model of a program, and adjusting schemas

Self-efficacy

Self-efficacy (belief in one's ability to do something) affects motivation. If you don't think you can do something, you won't try, unless you have no choice. On the other hand, a feeling of mastery is quite motivating.

Georgina

Georgina

This element of psychological state seems different from the rest.

The others are knowledge about programming. This one is students' knowledge about themselves.

Yes, you're right, that is what's going on. A self-efficacy belief is still knowledge, but the target of the knowledge is different.

Self-efficacy is bound up with emotional evaluations of self and others. Unfortunately, some people use inappropriate comparisons when judging their self-efficacy. For example, suppose that Adela is the star of a programming course. Here's one reaction:

Ray

Ray

Adela is so much better at programming than I am. I'm no good at this!

This makes no sense. Adela being better than Ray does not imply that Ray is not good at the task.

We want students who finish a programming course to believe that they can write basic programs. Not that they think programming is easy for them, but that they can, with effort, do a basic programming task.

Course goal type: students know they can do basic programming tasks

 

Emotional skills

 

Programming can be frustrating, and laced with anxiety. Your professor asks you to write a program of a type you've never written before. You have until Monday. But you can't know what problems you'll run into. You don't know what you don't know, and will have to learn. How many times will you backtrack, and redo your work? No idea! Argh!

Maybe your professor gives you a program with a bug. You don't know where the bug is in the code, and you can't start fixing it, until you find it. You don't even whether it's one bug, or two bugs working together. Argh!

Course goal type: know how to mitigate frustration and anxiety.

What about tasks?

Adela

Adela

I thing I'm missing something here.

I get what you're saying about knowledge as psychological states. We can set course goals by listing the different kinds of knowledge students should have.

But what about the actual content of the course? Like, the course could be about JavaScript, and making games. Or it could be data analysis with Excel VBA. Where is that listed?

 

Good thinking, Adela! You're right. We've talked about the types of goals for a course, but not what those goals actually are. So, all courses will have "remembering facts" goals, but the facts to be remembered vary from course to course. All courses will have schema goals, but the schemas will be different in different courses.

Start with tasks that you want students to be able to do by the end of a course. Work out what students need to know to do those tasks. For example, to analyze data in Excel VBA, students need to know how to loop across a range of cells. That suggests a looping schema. They'll also need to be able to filter out values that match criteria. They'll need to compute totals and counts. That's the accumulator schema.

This kind of backwards design is a common recommendation in instructional design (ID). Understanding by design (UBD), 4CID, and other methods use backwards design.

Summary

Before you design a course, you need to identify course goals. Goals can be expressed as desired psychological states. This article explores the types of goals you might list.

Some goal types:

  1. Students have memorized facts.
  2. Students have automated subtasks.
  3. Students can apply programming schemas.
  4. Students can solve programming problems.
    1. Creation: students can manage the program writing process, with metacognitive questions, heuristics, and principles.
    2. Debugging: students can test a program, model an existing program, find bugs, and fix bugs.
    3. Adaption: students can change a program's functionality, by building a mental model of a program, and adjusting schemas.
  5. Students know they can do basic programming tasks.
  6. Students know how to mitigate frustration and anxiety.

What do you think of this analysis? Comments welcome.