documentation is beautiful

Make your code the best comment ever.

The undocumented array

Let’s start with a simple line of JavaScript:

const a = [];

Well… that’s an array declaration: no big deal. What will be this array used for?

Hard to say without any context, plus the variable name isn’t helping. Here is the context: I’m working on a side project using React and React Router (v6). Here is the real code, with a real variable name, in a file named routes.js:

const routes = [
  { path: "/foo", element: <Foo /> },
  { path: "/bar", element: <Bar /> },
];

export default routes;

Here you are: the array will list the routes of the application. Then you may use it:

import { useRoutes } from "react-router";
import routes from "./routes";

export default function App() {
  return useRoutes(routes);
}

Of course, I didn’t invent that pattern. I saw a similar code, and wanted to make it a template repository on GitHub. Some kind of personal React framework. Thus the array should be empty in routes.js. Indeed the template shouldn’t declare any route: I don’t know the application I will build from my framework.

const routes = [];

export default routes;

I will remember how it should be filled since that’s my own code… at least a while.

Eagleson’s law

You may have heard it:

“Any code of your own that you haven’t looked at for six or more months might as well have been written by someone else.”

Sure I would be thankful to myself 6 months later — or next week — if I add some comment:

/* route format: {path: '/foo', element: <Foo />} */
const routes = [];

export default routes;

It may looks ok at first glance, but looking closer it’s not. Really not. First, I can’t expect anyone — including me — to understand this comment. It describes something as a route format. It doesn’t explain what to do with the routes array. In fact it’s only useful if you already know what to do. Good enough for a reminder, but not what you would expect of a documentation.

And there is an other issue. Do you always read comments in code? I don’t. Hours of code reading trained my eyes and my brain to ignore them as much as they can. I’m used to see comments as pollution between 2 lines of code.

Let’s see the glass half full. Now we have a checklist to write useful documentation:

  • you should express it in a explicit, non-ambiguous form,
  • you should ensure it will be read.

“Explicit, non-ambiguous expression” doesn’t look like a definition of writing, but of coding. You can’t be sure any human will read what you wrote. But would you ask a program, you can always be sure it will “read” your code. So why not coding the documentation? A code version of this comment:

/* route format: {path: '/foo', element: <Foo />} */

When the best comment is the code itself

That’s where TypeScript can help us. In a nutshell, you can use TypeScript to describe the type of value expected for a variable:

const anyValue = "42"; // standard JS: will never complain
const anyString: string = "42"; // ok: '42' is a string
const anyNumber: number = "42"; // no: '42' is not a number

That’s for primitives. Would you need to ensure an object has specific, typed properties you can define an interface or a type alias:

interface MyInterface {
  anyString: string;
  anyNumber: number;
}

const myObject: MyInterface = {
  anyString: "42",
  anyNumber: "42", // again, wrong type !!!
};

And that’s what we need. Instead of a not-sure-to-be-useful comment, we can “type” the array. This will describe its future content:

import { ReactNode } from "react";

interface MyRoute {
  path: string;
  element: ReactNode;
}

const routes: MyRoute[] = [];

export default routes;

TypeScript can’t misunderstand this, and it will never forget to read it. You can try it with an invalid object:

import { ReactNode } from "react";

interface MyRoute {
  path: string;
  element: ReactNode;
}

const routes: MyRoute[] = [{ whenToRender: "/foo", whatToRender: <Foo /> }];

export default routes;

This will output something like that:

Type '{ whenToRender: string; whatToRender: any; }' is not assignable to type 'MyRoute'
Object literal may only specify known properties, and 'whenToRender' does not exist in type 'MyRoute'

That’s how TypeScript can help you to self-document your code ;)