Some commonly held ideas about what a programmer needs in order to learn another programming language:

  1. Your motivation: have a real problem you need to solve
  2. Your patience: tolerate solving it slowly
  3. The language's design and basic use: read a book
  4. The language's standard libraries: consult online docs as needed
  5. The language's actual behavior: play with the REPL
  6. Your basic understanding of the language: repeat all of the above until you've solved the problem
  7. Your progressively improved understanding of the language: repeat for several more problems

I find step 6 fuzzy. When you don't know a language very well, you can't express yourself in it very well, and your code frequently won't do what you intend. Of course, you can't debug it very well either, so you're forced to context-switch — either to the REPL (assuming there is one and its behavior is sufficiently representative) to validate your uninformed guesses about what might be wrong, or by littering printf statements everywhere. Neither makes for a fast feedback loop.

We all contend that context-switching and slow feedback loops are inefficient when we're learning how to solve problems in languages we already “know”. So why do we seem to tolerate these inefficiencies when we're learning how to solve problems in languages we “don't know”?

A personal anecdatum

At CodeMash a year ago, I attended an afternoon workshop on elements of advanced Ruby style that explicitly targeted experienced Rubyists. My experience consisted of having attended an intro-to-Ruby workshop that very morning. Unsurprisingly, I had trouble keeping up with the first couple exercises, and started to wonder whether I'd get to have fun or learn much.

I took a moment to retrospect. Where was the bottleneck? Was I slower than others at noticing when my program produced the wrong results? Probably not. Was I producing many more wrong results before arriving at the right ones? Almost certainly. So if I was going to have a productive afternoon — first by keeping up, and then maybe enjoying myself and digging deeper — I needed to manufacture an advantage.

I will turn this car around

As I worked on each new exercise, I whittled away a bit more at my bottleneck. First, I figured out how to load my class from a driver program. Then I mapped a vim key to save both files and invoke the driver. Then I replaced the class's verbose status reporting with some assertions in the driver, comparing some expecteds to actuals. Then I only displayed assertions when they failed.

I had turned the tide. I had made myself able, just barely, to complete each exercise in the time allotted. This would be a productive afternoon after all.

Deep breath, and continue

I extracted each assertion into its own method, to remind me of its meaning at a glance, and so I could easily comment it out when I was visually confused by too many assertions failing. Then I factored out the common bits of state shared by multiple assertions, so that I could more quickly write new assertions without screwing up. Then I started writing assertions first, at the beginning of the exercise, and making sure they failed correctly before proceeding.

The workshop's exercises were cleverly designed to scale to each participant's needs. At this point I was completing not only the simplest form of each exercise, but also each of the successively harder forms, and then turning to help my neighbor.

You see what I did there

Dear reader, from this narrative you have doubtless identified the catalyst for my rapid learning of a new programming language: Test-Driven Development. You may not be surprised. I was! Even as a longtime TDD adherent, I had assumed the steps to professional competence in a language were:

  1. Do whatever you do to learn enough of the language
  2. Find and master a popular TDD tool
  3. Test-drive production-quality code

On the basis of my experience, I now believe it is more efficient to replace the first step with these two:

  1. Figure out just enough of the language to express runtime assertions from outside your application code
  2. Test-drive in order to learn the language faster

Conclusion

You don't need a fancy tool to apply TDD concepts. You might want TDD concepts to accelerate your learning — even when what you're learning is a new programming language.