Stop Teaching with Standard Output

by Jeremy D. Frens on May 1, 2015


This post is going to be a bit ranty.

I’ve been learning Elixir this month, and while reading tutorials and books, one thought kept crossing my mind: Teaching with standard out considered dangerous. Or just really stupid.

The hello-world program is partly to blame:

puts "Hello, World!"
System.out.println("Hello, World!");
IO.puts "Hello, World!"
printf("Hello, World!\n");

After your book shows you hello-world, then it’s off to some other program that spits something else out to standard output.

It’s not really practical

My least interesting argument against standard output is the practicality complaint: no one prints to the standard output. And by “no one”, I mean more than 90% of software developers. The other 10% are writing UNIX commands, and even then, they’ve probably abstracted somewhat so that they print to an output stream so that the command works with file output, too.

Student comprehension

For a beginning programmer, everything looks like an important hammer. A common mistake I saw from students was to use an output statement to return a value from a function. That’s how the value in the hello-world program was returned to them, so that’s how values are returned, right?

Plus, students would burn up much of their time just trying to figure out how to format their output correctly. (And I really don’t want to talk about how much time they wasted trying to get standard input to work right in the console window in Eclipse.)

Students wasted their time for no good reason.

It’s boring

It is. Enough said.

REPLs are much better

I never got to teach an introductory course with a REPL, but then a REPL was hard to find for C++ and Java (before Groovy was a thing1). But I have learned quite a few languages with a REPL.

With a REPL, there’s no standard output in my code to get in the way. I can type (+ 2 2) into a Scheme REPL, and get 4 as my result. I can then plug the exact same expression into a new expression: (* 3 (+ 2 2)) to get 12.

Sure, you need to have a special conversation with students when you need to write a console app, but it’s easier to add this as a special case later.

Plus, you get immediate feedback.

And for goodness sake, Haskell has a REPL, so I have to figure that any language can have one.

Testing frameworks are awesome

While playing around with JUnit in the early ’00s, I realized that my students in CS1 could handle JUnit as well or better than console output. So I retooled our lab manual to use JUnit primarily instead of CLI drivers.

I loved it. The students loved it.

I knew I was on to something the first semester during the second lab. That second lab had students experiment with Java expressions and all their wrinkles: integers, floats, scientific notation, addition, multiplication, integer division, association, precedence, &&, ||, short circuiting, comparisons, assignment operator, etc. The original lab had them printing out results of expressions to the standard output:

System.out.println(2 + 3 * 4);
System.out.println(2 + (3 * 4));
System.out.println((2 + 3) * 4);

They’d have to verify their expectations by hand with the output in a separate window. And which output went with what output statement?

Assertions read and instruct much better:

assertEquals(14, 2 + 3 * 4);
assertEquals(14, 2 + (3 * 4));
assertEquals(20, (2 + 3) * 4);

This does a better job of pushing the student into making a prediction before running their code. It’s persistent (i.e., they could use this to study for a quiz). And it’s simple: hit the “Rerun” button on the JUnit widget. A green bar came quickly, and the student could move on immediately. A red bar gave specific feedback for which assertion had failed.

One benefit I hadn’t anticipated was that the JUnit “traffic light” essentially game-ified their assignments. It was fun getting that green bar; it was never fun trying to read through the output on the console.

One key, though, for the test library: great error messages. I know of several programmers who claim that all they need for testing is a C-style assert(). It either passes or fails. This is nearsighted for regular development, but it’s ten times worse for learning. If an assertion fails, you need to know how it failed. “Failed: expected 24, got 20” is something you can learn from. “The assertion failed” is nearly useless.

Using unit tests certainly isn’t perfect, but every complaint against unit tests is ten times worse with standard output. Every time I learn a new language now, I look for the testing library first so that I can use it on my own.

Me, me, me

I want books and tutorials that allow me to experiment quickly and provide constant feedback which I can easily repeat. Teach me with a REPL and a testing framework. I can quickly iterate through examples in the REPL; I can preserve interesting results in tests. But stop teaching me with output to the standard output. You’re wasting my time.

Footnotes

  1. Is Groovy still a thing?

rant teaching