5 ways I write about code
Writing about code isn’t the same as programming.
When you include source code in an article, you have to make certain tradeoffs to make the code more readable for a general audience, including those who may not be very familiar with programming. How you write programs for other programmers is not the same way you would write an article about programming for a nonexpert audience.
I like to write a lot of very technical articles about open source software and programming, including a few books, and I’ve developed a few habits that I like to practice. These are a few of the ways that I write about code in my articles:
1. Walk your way up to it
You can’t just jump into the deep end; you have to walk your readers up to the big idea in smaller steps. For example, if I wrote an article about “a program to print an ASCII table” for a more general audience, I wouldn’t just show the program and write “here it is.”
Instead, I would start by explaining how a “loop” works in the C
programming language. I might explain that the for
loop has
three parts: the initial state, the end condition, and
what to do after each pass in the loop. A sample
for
loop might look like this:
for (count=1; count<=10; count=count + 1) {
puts("Hello world");
}
That is a simple loop that prints the text “Hello world” ten times.
You can see the initial state is count=1
, which sets the
count
variable to 1. The end condition is
count<=10
, so the loop will continue as long as the
count
variable has a value less than or equal to
10. At the end of each iteration, the for
loop executes
count=count + 1
, which adds 1 to the count
variable.
After that, I might explain a common shortcut: count++
is the same as count=count + 1
, and can be shorter to
write. I would also show how to write two loops, with one loop embedded
in the other. From there, I would add the full code to generate a simple
ASCII table, then other code to make the table look nice:
#include <stdio.h>
int main()
{
int row, col;
/* header */
fputs("-- ", stdout);
for (col = 0; col < 16; col++) {
printf("%x ", col);
}
putchar('\n'); /* end of line */
/* body */
for (row = ' '; row <= 127; row += 16) {
printf("%x ", row);
for (col = 0; col < 16; col++) {
putchar(row + col);
putchar(' ');
}
putchar('\n'); /* end of line */
}
return 0;
}
Finally, if you include a sample program in an article or book chapter, you should also show how to compile and run the program. This helps the reader to follow along, including how to build the program on their own:
$ gcc -Wall -o ascii ascii.c
$ ./ascii
-- 0 1 2 3 4 5 6 7 8 9 a b c d e f
20 ! " # $ % & ' ( ) * + , - . /
30 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
40 @ A B C D E F G H I J K L M N O
50 P Q R S T U V W X Y Z [ \ ] ^ _
60 ` a b c d e f g h i j k l m n o
70 p q r s t u v w x y z { | } ~
2. Use meaningful variable names
In the professional world, developers often use common shorthand
notation in their programs for things that other programmers would
understand. For example, to write a loop to print a simple list of
values, a programmer might use a single letter variable like
i
or n
as the loop variable. That’s very
common in professional practice.
But when writing about code, it’s important that your reader can understand everything in the code. The program becomes an extension of the article, which means it must tell its own story. Avoid using single letter variable names; instead, use meaningful variable names.
For example, when writing the ASCII table program, I might have used
i
and j
as my loop variables. For an
experienced programmer, the loops are pretty obvious, so the single
letter variable names would still be clear. But for a reader who might
just be getting started in programming, the single letter variable names
could obscure the meaning of the program.
That’s why I used row
and col
in the
program; the row
variable tracks the rows in the table, and
col
tracks the columns in the table. These variable names
provide more meaning, and require only a little bit of explanation to
help the reader follow along.
3. Write simple code, not flashy code
When I write sample programs in articles, I try to write the most simple code that I can. That doesn’t mean I don’t know how to write programs, only that I want my readers to be able to understand the sample programs on their own.
One way that I keep programs simple is to avoid some cool
features of the programming language. For example, in the C programming
language, any statement can also be a value. When you reserve memory for
an array with the malloc
function, malloc
returns a pointer to the array, which the program would store
in a variable:
int *array;
array = malloc(10 * sizeof(int));
But if the computer doesn’t have enough memory to set aside for that
array, the malloc
function returns a special value called
NULL
to indicate a failure. Most programmers would allocate
the memory and test for its success by writing a single line of
code:
if ((array = malloc(10 * sizeof(int))) == NULL) {
/* fail */
}
But this can be difficult to read for someone who doesn’t know the programming language very well. Instead, I write the allocation statement and the test as separate lines, which makes the program easier to read:
array = malloc(10 * sizeof(int));
if (array == NULL) {
/* fail */
}
4. Define variables up front
The “C99” standard for the C programming language added the ability
to mix variable declarations within source code. This is a handy feature
when writing programs, because programmers can define a variable where
they need it. For example, a program to print a list of numbers might
include a definition of a count
variable within the
for
statement, which is valid in C99:
#include <stdio.h>
int main()
{
for (int count = 1; count <= 10; count = count + 1) {
printf("%d ", count);
}
putchar('\n');
return 0;
}
This makes sense to an experienced programmer, and might be readable to a nonexpert audience. But in my sample programs, I prefer to stick to an earlier C programming standard that requires programs to define variables at the top of the program. I find most general audiences can better understand code when written this way, because it tells a story: these are the variables we’ll use and then this is the sample code.
#include <stdio.h>
int main()
{
int count;
for (count = 1; count <= 10; count = count + 1) {
printf("%d ", count);
}
putchar('\n');
return 0;
}
5. It’s not always optimized
Writing programs that are easy to read do not always use the most efficient routines. But that’s okay for the articles that I write; I’m not teaching a computer science course, I’m writing articles that everyone should be able to understand.
One example is reading and writing data. An efficient method to read
data from a file is to read large chunks of the file at a time, such as
with the fread
function. This data can be stored in an
array, which the program can read in smaller parts, such as letter by
letter. For example, a sample program to read a large text file might
look like this:
#include <stdio.h>
int main()
{
char buffer[10];
FILE *input;
size_t nread;
int n;
input = fopen("file.txt", "r");
if (input == NULL) {
puts("cannot open file");
return 1;
}
do {
nread = fread(buffer, sizeof(char), 10, input);
if (nread > 0) {
for (n = 0; n < nread; n++) {
putchar(buffer[n]);
}
}
} while (nread > 0);
fclose(input);
return 0;
}
This program doesn’t actually do anything with the data, only prints
the results back to the user with the putchar
function. In
professional practice, a program might use this method to read large
chunks of a file, such as to make a copy of the file, or to examine the
file for patterns of data. Reading large blocks of data at a time is a
very fast way to process data in a program, because the operating system
doesn’t have to keep going back to the disk for more data.
While efficient, the fread
function is more difficult
for nonprogrammers to understand. In theory, you could explain how it
works by reading a chunk of data of a specific size (in this case, 10
bits of data, each the size of a single character .. or 10 characters)
but that’s a lot of extra text just to explain how to read a
file.
If the point of the sample program is to read data from a
file and examine that data one character at a time, using
fread
to read big blocks of data into memory is too much.
Instead, I use a slower, simpler approach that reads a single character
at a time using the fgetc
function:
#include <stdio.h>
int main()
{
char ch;
FILE *input;
input = fopen("file.txt", "r");
if (input == NULL) {
puts("cannot open file");
return 1;
}
do {
ch = fgetc(input);
if (ch != EOF) {
putchar(ch);
}
} while (ch != EOF);
fclose(input);
return 0;
}
The basic function of the program is the same (it reads data from a
file and examines it one letter at a time) but the do
loop
is easier to follow.
This program has another example of writing a program in a way that’s
easier for nonprogrammers to understand, even though it is not how a
professional programmer would write it. This writes certain statements
as multiple lines, where a “real” program would likely condense that
code into fewer lines. Another way to write this program might be to
condense the fopen
and if
test into one line,
and rewrite the do
loop as a more compact
while
loop:
#include <stdio.h>
int main()
{
char ch;
FILE *input;
if ((input = fopen("file.txt", "r")) == NULL) {
puts("cannot open file");
return 1;
}
while ((ch = fgetc(input)) != EOF) {
putchar(ch);
}
fclose(input);
return 0;
}
While this program is shorter to write, it can be more difficult for a general audience to interpret.