On naming things
— principles, ReactJs, JavaScript, TypeScript — 5 min read
There are only two hard things in Computer Science: cache invalidation, naming things, and off-by-one errors.
— Phil Karlton
Out of those two and a half things, I genuinely believe that naming is the hardest. Why? Because it's not only hard to do, it's also highly subjective. A good name for you might not be a good name for anyone else. It is also mostly pointless to argue about it, as you can quickly drift off into bikeshedding. Good names are also hard (impossible?) to statically analyze, so the bigger the team gets, the tougher it will be to keep naming consistent.
Why is it important?
Any fool can write code that a computer can understand. Good programmers write code that humans can understand.
— Martin Fowler, 2008
Names are a vital part of your programs documentation. You assign a name to constants, functions or parameters so that everyone who reads the code knows what those things are meant to be.
The computer actually couldn't care less. In fact, in frontend programming, lots of code is minified before it is shipped to the browser. Bundle size and stuff.
Computers don't need to understand the code, they just need to interpret it. We humans however need to fix the bugs, or add new features. So understanding what is actually going on is vital in our daily life.
So what can we do?
I think every team has to come up with a naming scheme that they feel comfortable with, and then enforce it as good as possible, if striving for a coherent codebase is desirable (which I think it is). A homogenous codebase will be much easier to read, and it is well known that we read code many more times than we write it. So I want to always optimize first and foremost for readability and maintainability.
I can only give you the rules that I personally like a lot. They might not work for you. They also might 🤷♂️. Some of them are very subjective, while others seem more "objectively correct".
No abbreviations
I dislike (almost all) abbreviations, even the common ones. Try using PR with a non-developer, they will most likely think it means public relations. Twitter made abbreviations more popular because you need to constrain yourselves to a certain length, and I find this very hard to read. Sometimes, people have typos in their abbreviations, which makes it impossible to know what they're talking about.
I have a postulation:
For every abbreviation used, there is at least one person on the team that doesn't know it.
— TkDodo, 2020
I recently learned what OTOH means. You don't know what people don't know 🙃. So if you want to optimize for readability, try to avoid abbreviations whenever possible.
It's fine if the scope is small:
1posts.map((p) => p.id)
Personally, I don't even do that, because there is no harm in writing post instead of p. But I wouldn't insist on a rename here.
I also try to avoid abbreviations as much as possible because there will be situations when you have to use them anyways.
Most companies have business relevant abbreviations that they insist on being used.
You might also need to use external libraries which force a certain name on you.
Even React has props
, which is technically speaking an abbreviation.
eslint-plugin-unicorn has a rule that tries to tackle this, but I've always needed an extensive whitelist. I think it's just easier to go with conventions and "common sense" here.
Inline
Inlining functions, as in moving them to the place where they are used, has a couple of advantages. First, you don't need to come up with a name at all, which is great, as naming is hard :)
Second, in most of the situations where I would like to inline functions, giving them a name is more chore than joy and doesn't really improve readability. If you cannot come up with a name, prefer inlining.
Lastly, if you are using TypeScript, types will nicely be inferred for you, which takes away a bunch of complexity. Let's take the above map example:
1type Post = { id: string; title: string }2
3const getPostId = (post: Post) => post.id4
5// extracted6posts.map(getPostId)7
8// inlined9posts.map((post) => post.id)
I would recommend inlining functions if:
- they are not used often
- they are rather small
- you cannot come up with a meaningful name
This comes up a lot with event handlers in React. And no, inline functions are not bad for performance.
1<button onClick={() => login({ username, password })}>Login</button>
Name things for what they represent
1export const TWENTY = 20
Beginners are often told to not use magic numbers. Keep everything DRY (ha, an abbreviation 😅). If you use a magic number in code, make sure to extract it to a constant. A single source of truth, so that you only have one place to change if you need to change it.
The problem is that the above constant is named after what it is, not what it represents. We cannot just use it for various functions, because the twenties might have different meanings:
1const calculateTaxes = (amount) => amount * percentage(TWENTY)2const sessionTimeout = minutes(TWENTY)
So dry, I've used the same constant, isn't that great!
Here, the magic number would actually be fine. This is the same as inlining. Only extract it to a constant if you need the representation more than once, not the value. Don't create abstractions over things just because they look similar - they actually need to be the same thing.
I think this would be a much better name:
1const SALES_TAX = 202const calculateTaxes = (amount) => amount * percentage(SALES_TAX)
Now we know what this one twenty means. 🥳
Let's take a different example. Assume we have the following condition that we want to name:
1const ??? = status === 'fetching' && !data2
3...4
5??? && <LoadingSpinner />
How would you name this condition?
a) showLoading
b) isFetchingAndHasNoData
c) isInitializing
Tying the name to the current usage
This is what a) does. We show a loading spinner, so let's call the condition showLoading, renderLoader or hasLoadingSpinner.
But maybe, we will someday do something else, or something additional, if we are in this state. What if we want to show an Overlay or a Button as well? The name doesn't fit anymore! Of course, we can't predict the future, but being a bit resilient to change is a good thing.
Additionally, if we only take a look at our JSX (for example, because that condition is imported from another file), we have no way of knowing when the Spinner is actually shown. The code basically reads:
If we should show the loading spinner, we show the loading spinner.
Great, when is that please? We don't know without looking at the implementation, which makes this a bad name for me.
Tying the name to the implementation
b) is the most obvious candidate for a bad name. "And" has no place in a variable name if you ask me. Every time we change the implementation of this condition, we have to adapt the name and all the usages. This is very similar to the twenty example from before.
isInitializing
This is the clear winner for me. It describes very well what is happening, but we can do / render whatever we want if we are initializing. The name is therefore not tied to a usage, and we can also change what it means to be initializing without having to change the name.
React event handlers
The same applies when you create an event handler, and you call it handleClick:
1const handleClick = () => {2 login(3 { userName, password },4 {5 onSuccess: (url) => routes.push(url),6 onError: () => showToast('Could not login'),7 }8 )9}10
11...12
13<button onClick={handleClick}>Login</button>
I have been doing this for years, but it's just the same problem as above: handleClick doesn't tell us anything about what is going to happen, and it also couples it to a click event of some sort.
If we change the UX and want to log in when a form is submitted,
we would now have to re-name our event handler, unless we are fine with onSubmit={handleClick}
(probably not).
Naming the event handler something like loginUser or performLogin seems like a minor difference, but it's a lot more accurate, descriptive and future proof.
Takeaways
The two important takeaways are:
- Would the name change if the implementation changes (a)?
- Would the name change if the usage changes (b)?
If so, it's probably not a very good name.
What are your favourite rules on naming things? Leave a comment below. ⬇️