Lately I’ve been trying to learn C, starting with online training a month ago, and continuing with daily practice. Yesterday, inspired by a personal story I told on Developer on Fire 139, I wrote a C program to draw the Mandelbrot set. The first image it generated, intentionally, didn’t look like much:

Diagonal lines.
First generated image: diagonal lines

You’ll see that the image gets progressively better as I learn more. Not even a metaphor. Or is it?

I’m still getting the hang of C. But I’m doing pretty well with the hang-acquiring. You can too. Here’s my advice.

Bring your knowledge

You’ll have a head start if you already feel:

  • Productive in your preferred text editor
  • Confident with your preferred revision control system
  • Proficient in some other programming language (including writing, running, and listening to automated tests)

Be mildly relentless

Do a tiny bit of C each day. Do more than a tiny bit, if you can. Don’t do less. If you miss a day, since you’re aiming for mild relentlessness, no big deal. Just make sure to do a tiny bit the next day. The tactic here is to bring C to your conscious attention often enough to support the strategy. The strategy: keep C rolling around in the back of your brain all the time.

A circle.
Second generated image: a circle

Get a running start

Learning a new language means learning to navigate in a new environment. How to enter new code? build? run? change? test? organize? The C language is merely one aspect of the world you’re entering. If you’re not careful, your focus will be divided by more than one of the things you don’t know well: a language, a build tool, a test library, an IDE, etc.

If your goal is to learn C, pay as little attention to the other stuff as you can get away with. When you’re getting started, don’t try to choose the best compiler, build tool, test library, or IDE, and definitely don’t try to choose the best way to be installing them. You’re not having those problems yet, so you don’t have enough context to choose and the differences don’t matter much.

Start with an environment prepared by someone else. Cyber-Dojo provides compilers, test frameworks, and a text editor, and runs in your browser. You can be learning C right now.

Take assignments

Cyber-Dojo also includes code katas. Pick one at random. Write a trivial failing test and make sure it really fails. Then read the instructions, think a little, and code a little.

You might have an idea of what to do and not know how to express it in C. With a failing test, you can guess and check. If it doesn’t compile, it’s not valid C; try again until it compiles. If the test still fails, it’s C that doesn’t match your intention; try again until it does. Voilà: you’re learning C.

If you think taking a little time to work through a C tutorial would help you move faster, do it. If you think having a C reference at hand would get you unstuck more quickly, find one.

When you’re satisfied with your solution, Cyber-Dojo has plenty more katas. If you’d rather solve a real-world problem, maybe you’ve got one in mind. Or maybe someone you know can ask you for help with something.

The Mandelbrot set!
Third generated image: the Mandelbrot set

Invest in your environment

As you practice, you’re also learning what would help you be more productive. For instance, your preferred text editor would probably help! When you’re making good progress in Cyber-Dojo, you wish it could go a little faster, and your tests are green, it’s time to continue the kata offline.

On your machine, install the same test framework and a compiler (ideally also the same). You might have to twiddle the code to get it to build on your system. Once the tests are green again, return to the kata: think a little, test a little, code a little. You’re learning C again — and now that you’re in an environment you control, it’s worth (1) noticing what gets in your way and (2) taking action to improve it.

If you want to run the tests frequently, teach your text editor to run them with a keystroke. If you want it to be easier to undo back to the last green state, git init and start committing whenever you’re green.

If you’re frustrated by how long it’s taking to figure out a particular improvement, it’s worth noticing that too. Leave it for later — if it keeps being annoying, you’ll get more chances to solve it — and get back to the cycle of learning: think a little, test a little, code a little, commit on green.

Even if it’s sometimes frustrating, incrementally removing bottlenecks to your productivity is incredibly worthwhile.

…Especially in tools that can support you

Your C compiler is willing to warn you about lots of potential mistakes, and to treat warnings as errors. You only have to ask.

Your editor might be able to show the same warnings and errors, in the context where you made them, before you try to invoke the tests. Seeing mistakes earlier helps you go faster. Try it.

Your memory-management logic is easy to get wrong. Valgrind can tell you when and where you’ve made basic mistakes. You’ll want to run it less often than your unit tests, because it’s slower, but when you’re dealing with malloc() and free() you’ll want to run it pretty often. (The longer you go between Valgrind runs, the harder it’ll be to figure out where you screwed up. git bisect might help.)

Same thing, bigger.
Fourth generated image: same thing, bigger

…Especially in your fastest feedback loop

You need to be able to rely on your tests to tell you when and where something is wrong.

If it’s hard to spot when a test is failing, fix that. I noticed myself having this problem with Unity, so I wrote a tiny wrapper to run Unity tests from Perl’s prove tool, which paints failing tests red in a way I’m used to.

If it’s hard to know what a red test is trying to tell you, fix that. Using Check, for instance, when ck_assert() needs to tell you something, it can’t say much. Maybe there’s a more precise assertion available. Otherwise, ck_assert_msg() at least lets you tell it what to tell you, so you’ll understand what to do faster the next time it’s red.

Invite your friends

Pairing accelerates learning. During my 3-day online training a month ago, I worked with a remote pair the entire time. Whenever you can find someone to pair with you, do it.

Experts accelerate learning. That’s one of the reasons I took that training course from James Grenning. More recently, I asked some NetBSD developers — who understand from experience how to see the costs and risks of development in C — to review my code and suggest what I might pay more attention to. I’ve gotten some very thoughtful, articulate comments.

Exercism.io has a nascent C track, and its exercises come with ready-made tests you can enable, one at a time, to simulate part of the thinking that goes into TDD. Since you’re not trying to learn a thinking technique, this helps you focus on learning the language.

Exercism is also a way to invite code review. When you post a solution, other Exercism users might notice, read, and comment. Even if they don’t, your code’s posted at a stable URL and you can send the link to anyone you like. Or anyone you don’t like, if you like.

Same thing, with points outside the set in light blue.
Fifth generated image: same thing, with points outside the set in light blue

Choose small steps

While trying to draw the Mandelbrot set in C, given that there were lots of things I wasn’t sure how to do, I made extra sure to organize my work into small steps:

  1. Since I’ve never used GD before, start by generating an image — any image.
  2. Since I’m not sure GD offers what I need, make sure it can color individual pixels.
  3. Since I keep opening the image to see whether it’s different, write an approval test to turn red whenever the image has changed.
  4. Since I want to try plotting an equation, test-drive mapping pixels to (x,y) coordinates.
  5. Since I think I got that right, try plotting the equation for a Unit circle.
  6. Since I instead got a weird blocky thing, figure out which coordinate-mapping test I forgot to write, make it pass, and try again.
  7. Since I got a circle, refactor my (x,y) coordinates to a+bi (complex numbers).
  8. Since I don’t know C’s complex math functions, compute the Mandelbrot set using C’s ordinary math operations on a and b separately.
  9. Since that worked, refactor to C’s complex math functions.
  10. Add some color.
Same thing, with points near the boundary by red or green according to escape velocity.
Sixth generated image: same thing, with points near the boundary red or green according to escape velocity

Conclusion

This post isn’t much about C after all, is it. It’s about how to apply what you know, and what you know about what you don’t know, to acquire what you want to know. When you’re trying to learn something, do the fastest thing that gets you a walking skeleton. From there, iterate in tiny steps. As soon as you’re satisfied, stop.

It so happens that the Mandelbrot algorithm itself relies on iteration. The more times you iterate over each point, the more accurate the picture. But there’s no number of iterations that guarantees perfect accuracy, and each iteration costs runtime. So you have to choose how many iterations is good enough, and stop. And that number depends on how fast your computer is, how far you’re zoomed in, and what you want — in other words, on context.

Not a bad metaphor after all.

Not a bad exercise, either, and I’m not satisfied with it yet. I’ve still got plenty of ideas for what I can learn.

To be continued…