Tips for Writing Self-Documenting Code

How to write code that other developers can actually read

Self-documenting code is just a fancy way of saying “readable code,” and readable code is what keeps you from losing your mind at work. It’s no replacement for real docs or a well placed comment, but it never hurts to write better code. Let’s talk about some habits that will make your work easy to understand.

Don’t use magic numbers

Look at this and tell me what it means:

if (students.length > 23) {

What is that 23? How am I supposed to know what that is, am I Jim Carrey? That 23 is a magic number, which is a terrible name because it sounds fun, but it is not. It means that there’s a number that looks important, but has no context. Always give a name for numbers:

const maxClassSize = 23;

if (students.length > maxClassSize) {

Now it reads: if we have more students than the maximum amount allowed, do a thing. Neato. This actually leads into my next point:

Use clear variable names

I don’t know why, but I used to get super self-concious about writing long variable names. Which was dumb, becuase rStuNms and fStuNms are utterly terrible compared to rawStudentNames and filteredStudentNames . They seem too long, but I am telling you: after 2 weeks of not looking at the code you will not remember a single acronym. A variable name is extremely important because it’s your chance to tell your reader what your code is doing:

const fStuNms = stus.map(s => s.n)

// vs

const filteredStudentNames = students.map(student => {

return student.name;

});

That’s a contrived example, but you get the idea. Another helpful tip is to use naming conventions. If your value is a boolean, start with is or has , like isEnrolled: true . If your value is storing an array, the name should be plural, eg students . Numbers should start with min or max if possible. For functions, there should be a helpful verb in front, like createSchedule or updateNickname . And speaking of functions:

Refactor with tiny, named functions

Variables aren’t the only places to add helpful explanations, function names are great too! I used to think that functions were only for DRYing up your code, but recently I had my mind blown when I learned that functions really shine when they’re used for readability. Look at this code for a second and tell me what’s going on:

const handleSubmit = (event) => {

event.preventDefault();

NoteAdapter.update(currentNote)

.then(() => {

setCurrentAlert('Saved!')

setIsAlertVisible(true);

setTimeout(() => setIsAlertVisible(false), 2000);

})

.then(() => {

if (hasTitleChanged) {

context.setRefreshTitles(true);

setHasTitleChanged(false);

}

});

};

It’s possible, but how about:

const showSaveAlertFor = (milliseconds) => () => {

setCurrentAlert('Saved!')

setIsAlertVisible(true);

setTimeout(

() => setIsAlertVisible(false),

milliseconds,

);

};

const updateTitleIfNew = () => {

if (hasTitleChanged) {

context.setRefreshTitles(true);

setHasTitleChanged(false);

}

}; const handleSubmit = (event) => {

event.preventDefault();

NoteAdapter.update(currentNote)

.then(showSaveAlertFor(2000))

.then(updateTitleIfNew);

};

Boom, so much cleaner. All we did was scoot a few lines of code into functions and it improved dramatically. showSaveAlertFor is even a function we can use elsewhere if we want to. The lines of code are scooted up and away into function definitions, but you don’t need to read them directly unless there’s a problem. At the high level, we have a human readable chain of events. Function definitions are also another great way to label variables. setTimeout takes a function and number of milliseconds, but now we’ve clearly added it ourselves to be extra helpful.

Add useful test descriptions

Probably the least talked about way of sneaking documentation into code is with tests. Suppose we have this function:

const getDailySchedule = (student, dayOfWeek) => {

Let’s pretend this is a runner function made up of a bunch of other functions, so it does a lot: It retrieves the daily schedule; if the day of the week is a weekend it returns an empty array; if the student has detention it sticks it onto the end of the schedule; and if the student isn’t enrolled in the school, it prints a link to that Mean Girls Gif.

If you tried to put that paragraph as a comment, you’re going to get some weird looks. But you know where that paragraph would look great? In tests:

describe('getDailySchedule tests', () => {

it("retrieves the student's full schedule", () => {

it('returns an empty array if given a weekend day', () => {

it('adds detention if a student got one that day', () => {

it('prints a gif link if student not enrolled yet', () => {

This is hands down the easiest way to straight up put comments into code without actually adding any comments.

Bottom line: readable over cleverable

Being a good developer doesn’t mean writing the cleverest code, it means being a good teammate. Quality software is rarely developed alone, eventually other people will need to read your code. And even if you’re working alone, you-right-now and you-in-two-weeks are almost different people when it comes to remembering code. There are lots more ways to write readable code, like adding good comments, but the most important thing you can do is just start thinking about it.

happy coding everyone,

mike