Skip to main content

Naming

Naming has probably the highest ROI of all the things.

Write code is an exercise in usability and user experience design. Your users are fellow developers, including the present and future versions of yourself. Code is written for your users to read and consume (by writing code that interfaces with it), and only incidentally for computers to execute.

It is easy to understand how coding is all about user experience design when you are coding a library with a public API — a poorly designed, awkward to use API will get you complains that it’s “hard to use”. But the idea applies to all code, not just libraries and public APIs, for no bit of code lives in isolation; in some sense all we do is call functions or transform data so that we can call functions with the data they expect, and each function’s signature is a result of conscious or unconscious design.

Any sort of consistency reduces cognitive overhead of reading code, since it allows for predictability, which means the reader doesn’t waste brain cycles by keeping themselves on their toes.

The rule of thumb is:

Choose a name that describes the value, is consistent, meaningful, clearly communicating intent, in every context it is referenced.

Do not abbreviate — surely your editor has intelligent token completion, so what exactly are you saving? Given a choice of having a clear name, or having a shorter one that may confuse someone, there’s rarely a reason to choose the latter. There’s an exception to this rule — when you access a value multiple times per line, and format your code in order to bring out the structure, a super-short name can be very helpful.

// this goes rightward for a dozen field names
val inserter = table.map(r => (r.userId, r.mealId, r.name, ...))
inserter +=                   (e.userId, e.mealId, e.name, ...)

The type of a value is the single best source for the name. In most cases, a leading lowercase version of the type name is all you need, so an instance of Recipe becomes const recipe.  Similarly, the names of collections should be simple pluralizations of the type, so const recipes: List<Recipe>.

Adopt and strictly follow a convention for common helper types, like options and futures/promises/asyncs. At my workplace, we use

const maybeRecipe: Option<Recipe>;
const recipeF:     Future<Recipe>;

If your function uses mutable data structures internally to do some accumulation[1], don’t struggle with the name, and just call the accumulator result. Chances are your function is generic anyway. Here’s a contrived example:

function process(things: Seq<Thing>): Seq<ProcessedThing> {
    const result: Array<Thing> = [];
    things.forEach(thing => {
        ...
    });
    return result;
}

In cases where the name is used a small number of times, on the same line as the binding, naming may actually reduce readability, so it may make sense to not name at all if your language permits this.

const names = persons.map(_ => _.name) // TypeScript
val names = persons.map(_.name)        // Scala

Be as concise as possible without losing meaning. The field on the Recipe class containing the name of the recipe should be called name, and not recipeName. You may think “I’d never do that”, but it’s surprising how often this comes up in less contrived cases, especially as a result of refactoring (see simplify complexity). A less contrived example:

class RecipeService {
    public updateRecipe(recipe: Recipe): Q.Promise<void> {
        ...
    }
}

The “Recipe” part of updateRecipe is doubly redundant. First, it’s a public function in the RecipeService class — what else are you going to be updating? (Private functions are a different story, you could have updateRecipeLocal and updateRecipeRemote, for example, if you have local and remote stores, and the smooth handling of both isn’t abstracted out into a superclass. Secondly, the parameter recipe: Recipe already tells you what we are updating.

Your naming should be contextual. This is particularly true for longer functions, which, incidentally, are something you should avoid. If a function deals with multiple instances of Recipe, it’s most likely confusing to call any of them recipe. In particular, the further the use site is from the definition of the value, the more careful you should be with a precise name. The last thing you want is to send the reader back up fifteen lines to search for the definition of the variable.

function update(recipeId: RecipeId, title: string, content: string): Q.Promise<void> {
    return find(recipeId)
    .then(originalRecipe => {
        const updatedRecipe = originalRecipe.clone({title, content});
        return persist(updatedRecipe);
    })
}

Above everything, though, use your brain. Here’s an example where contextual naming would fail you. Say you have a file, RecipeDialog.ts, and within it you have a function to open the recipe dialog. Given the file name, the reasonable contextual name for the function would be

export function open() {
    ...
}

It’s an exported function, so naturally it gets imported in SomeOtherFile.ts, like so

import { open as openRecipeDialog } from "RecipeDialog.ts";
import { open as openMealDialog } from "MealDialog.ts";

And then, in some other file, you might end up with one of

import { open as openRecipe } from "RecipeDialog.ts";
import { open as recipeDialogOpen } from "RecipeDialog.ts";

So the good intention of contextual naming produced an undesired side-effect of use site inconsistency in aliasing, which increases cognitive overhead of reading the code. Incidentally, this sort of thing does not happen with class member functions, e.g.  recipeService.update(...) always carries its context around, before the . character.

Design your naming schemes around the user experience you are aiming to provide to the future you and other team members. Well-established naming conventions make for a good start, but if you see problems, update the conventions — they exist to serve you, not to bind you. Your use case may be different, or maybe the author of the convention was just another normal human being, and didn’t think through all the possibilities.

[1] This is sometimes a good choice in terms of both readability and performance. Most respectable immutable libraries are implemented this way internally. “When a tree falls in an empty forest, does it make a sound?”

#todo more content needed here

* writing code is a UX exercise

* know who your users are

* know how they will use your code

* code to the editor (is there type inference, or do can we gain mental overhead reduction by encoding types in names?)

* code to the language (e.g. avoid aliasing that can lead to inconsistency by having redundantly named public exports)

* make sure semantics match the name. For example, if a function is called setOpenAccordionItem(key: string), one would be surprised if it in fact closed the item for key if it’s already open. The appropriate name for such a function would be something like toggleItem(key: string).

* reduce uncertainty for the reader — even if you don’t notice it, it tires your brain out when you’re reading code

Next: Code in the Ideal Language ⇒

Comments