getDate: 2021-11-27
Functions that start with get are a personal pet peeve
of mine. For example, I find getName to be a suboptimal
function name. We’re taught early on to prefer function names that start
with a verb. Many developers resort to get because it
perfectly describes almost anything.
Disclaimer: I am aware that complaining about
something as dumb as the verb a function name starts with comes across
as nitpicky. It is not a hard rule. There’s always a trade-off. The
intention of this article isn’t to convince you to never use
get. The intention is to be thought-provoking.
I believe that its generality is the problem. It can mean just about anything. Let’s say we come across the following call site:
name = getName()In a strongly typed codebase you might be lucky and at least get a hint from the types about what this function does:
const name: string = getName();Without looking at getName’s implementation, I cannot
tell which of the following is true:
I find it important that the reader is able to make an educated guess
about what a function does. A function that fetches a name from the
database has very different performance and failure characteristics than
a function that just returns this.name.
A reader, or future editor, of the code might make very different choices depending on what they think the function does. A function that is actually just a property/accessor might be called repeatedly with a negligible impact on performance. For example:
console.log('User name is', getName());
doSomeWork(getName());The fact that getName is called twice is not going to be
a problem here, practically speaking. Each invocation would take mere
nanoseconds. If getName actually fetches from a database or
API, the code block above would be very problematic and would be better
written as:
name = getName(); // add some error handling here
console.log('User name is', name);
doSomeWork(name);Without a more specific verb in the function name, the reader isn’t able to make an educated guess about the performance and failure characteristics. This might lead developers less familiar with your code base to write code that isn’t as performant or resilient as you would want.
We could obviously make the argument that a quick peek at the function declaration would make it clear to the reader what the function does. We could also argue that it’s often clear from the context in which the function is used what it does and what performance and failure characteristics it has.
Unfortunately, this often doesn’t hold up in reality. Authors often
forget about how much bias and prior knowledge they have compared to
someone who is completely new to the code base. Someone new to the code
base has no context whatsoever. They might be completely unaware that in
99% of the cases, functions that are named getSomething
just access a property.
Without any prior knowledge, readers have to do a lot of mental gymnastics to thoroughly understand an unfamiliar piece of code. Even guided by fancy IDE features such as “peek declaration on hover”, there’s the mental cost of having to read, dissect and understand a function.
This mental gymnastics cost grows linearly with the size of the code that the reader is trying to understand. Functionality often spreads across dozens, if not hundreds or thousands of files, classes, functions. Having to mentally jump back and forth between them makes it harder to create a mental model of how things work.
In most organizations, code is written once and read at least twice. Once by you, the author, and once again by a reviewer. Usually, the numbers far exceed the number of authors/writers. There might be multiple reviewers, future authors that need to make modifications to debug something, new reviewers, etc.
It makes sense to optimize for the reader rather than the writer.
In most cases, there are much better verbs available in the English language to more accurately describe what a function does.
Below is a quick cheat sheet that I personally use. It is not exhaustive and is absolutely not always applicable. It’s just a little crutch to start figuring out an alternative name.
| Verb | Reader expectation |
|---|---|
fetch, query, retrieve |
Functions that retrieve data from a data store or API. Something that might take a while and has a relatively high chance of failure. |
post, push, update,
delete |
Functions that push data to a data store or API. Something that might take a while and has a relatively high chance of failure. |
make, construct, compose,
generate, create, combine |
Functions that compose, generate or create data/objects entirely locally. Something that is quick and has a low chance of failure. |
search, find, filter |
Functions that filter or search through data locally. |
Another great way to communicate what a function does is to ensure its call site always involves some form of prefixing. For example:
db.getName()In the example above, the reader can immediately figure out that this fetches from a database, despite the function name itself being very generic. Most languages offer some way to namespace functions.
In more flexible languages, this shouldn’t be an excuse to just put all functions as static methods in a class. For example, in Python, one could adjust imports to ensure name spacing:
from myapp import db
db.getName()Of course, we can do something similar in TypeScript/JS:
import * as db from 'myapp/db';
await db.getName();