The Ten Commandments of Writing Programming Tutorials
commentary growthWriting programming tutorials has made me better appreciate the pitfalls of writing. It's also made me that much more aware of badly written tutorials. Whereas before I might have just called myself stupid for not understanding something, I can now clearly see what the writer did wrong and how the information could have been presented better. Anyway, I'm tired of reading badly written tutorials and hope to start better conversations about tutorials, so, without further ado and in no particular order: the Decalogue:
Name things descriptively.
It shouldn't come as a shock that the things that are good practices in
writing production code are good practices in writing tutorial code, but too
often I see foo
and bar
used as placeholders.
Here's the thing: the whole reason people read tutorials is because they don't know how to do something themselves. If they had a pretty good idea what you were going to say, they wouldn't have bothered. This is what separates tutorial code from production code. This is why readability is so important.
Now, it also doesn't come as a shock that naming things is hard, and it's
especially hard in generalized articles that can be applied to a lot of
different contexts. My advice is to pick an example context, so rather than
calling your classes SuperClass
and SubClass
, for example, use
Animal
and Dog
. Or even better, make a generalized and specific
implementation so your readers can grok the concept as well as how it is
applied in the real world.
Annotate types always.
Example code is not the place to show off how smart your type inferencing system is. You should assume the person reading your writing isn't familiar with the libraries you're using, and that means they're going to have to look up all the types, so it's going to be a lot easier for your reader to grok your code if you go ahead and put your types inline. Most importantly, your reader isn't reading your code in an IDE; they're reading it on a webpage, so they can't just mouse over your object to see the type definition.
Now, I hear you: it's true that not all languages support type annotations, but that doesn't make them unimportant. You can still annotate types in comments. That may not be an idiomatic way to use comments in a given language, and that's okay! Tutorial code doesn't have to be strictly idiomatic. It's more important to be skimmable to a beginner than idiomatic.
Keep code in a working state.
This rule can be bent a little if done thoughtfully. If there's some specific common pitfall you want the reader to see, it's okay to briefly show what's going to cause an error (although the case could be made that that should usually be broken out into its own article).
What you should avoid, though, is something like "Start with a main function that calls these three functions, and then let's spend the next several pages implementing those three functions." If you're not yet ready to write the main function, don't confuse readers by having them write it.
Instead, whenever possible, keep code in a state that compiles and, if possible, runs. That way, the reader always knows when they're on the right track and when they've gone astray.
Avoid summarizing code.
Writing code snippets can be tempting, but, whenever possible, include entire code files instead of snippets unless what you're showing is completely trivial.
I do understand the temptation. It's easy to think, for example, that the reader will be annoyed if they have to scroll past all the imports and boilerplate to see what you're trying to show, but think how much worse the reverse is: if there's something in the imports or boilerplate that they feel like they'd like to see, it's not just a matter of a little extra scrolling--they're stuck. There's no way around.
If you do like to summarize code or show just one snippet at a time, there's a good solution: summarize code in the article, but include a link to a fully-functioning repository. That way, the user can see how all the parts fully fit together.
Highlight syntax.
Did you know implementations of markdown usually support highlighting syntax? You do now. Simply enclose your code in three backticks and include the language annotation next to the first set like so:
```typescript
function thisHas(syntax: Highlighting) { }
```
Please don't make anyone read code without sytax highlighting!
Write descriptive headings.
Descriptive headings contribute greatly to search engine optimization. You owe it to yourself, your readers, and those hard-working web crawlers to put some thought into how you want your headings to read.
It's not a bad idea to use your headings to reiterate some contextualizing information like the language or tech stack your using or the problem you're trying to solve. For example, instead of, "Recursive queries," try "Recursive T-SQL queries with Common Table Expressions (CTEs) in SQL Server." The latter is a mouthful and maybe a little redundant, but the extra search keywords will help your readers find you.
Use images where appropriate.
Rule of thumb: if you're talking about menus, go ahead and include a screencap of the menu. In particular, I am thinking of .NET development in Visual Studio, which is famously menu-driven development.
The reverse is just as important: never use images to display code. To do so is inaccessible to screenreader users and just plain obnoxious to everyone else.
Distinguish between compiler and runtime errors.
"Compile and run" feels so natural in a lot of languages. Lots of editors have
a "Build & Run" button and lots of CLI tools have some equivalent, and this is
great for day-to-day editing, but I recommend avoiding using it to demonstrate
compiler errors. For example, don't advise the user to cargo run
a codebase
where they're expected to get a compilation error. Tell them to cargo check
the code instead.
I say this because lots of beginners struggle with the difference between compile-time and runtime errors. Getting in the habit of using a separate compilation step will help them get over the hurdle faster.
Include the full message when describing errors.
A lot of writers will just say, "X will produce a runtime error" or "Y will generate a compiler error." I highly encourage you to specify the exact wording of the error generated--maybe even a partial stack trace. Doing so will help the reader understand the error without having to reproduce it themselves, and it has the added benefit of helping your readers find your article through search engines because users are likely to be searching for specific errors.
Import everything explicitly.
Every language I'm aware of has some form of
Python
from my_module import *
or
Rust
use crate::my_module::*;
and depending on your preferences and the conventions of your language, you may do something like that all the time. (I do not.)
But remember, in your day to day you have access to an IDE where you can mouse over things to tell where they came from and flip to a construct's definition with a couple of keystrokes. Your readers don't have that, unless you really expect them to have to follow along with every line in order to be able to use your tutorial.
So even though it makes for a few extra lines and a little extra scrolling, make all your imports explicit.
Epilogue
I know I'm bloviating, but I do hope you've found this useful as a checklist for your own writing, or maybe just knowing how to spot a difficult-to-follow tutorial will inspire you to keep going and find better resources when you get frustrated.
And of course I am not perfect. Be sure to open issues on my own blog when you've found one that's really bad!
I write to learn, so I welcome your constructive criticism. Report issues on GitLab.