DEV Community

Cover image for Why Naming is #1 Skill for Writing Clean Code 🧼🧑‍💻
Martin Šošić for Wasp

Posted on

Why Naming is #1 Skill for Writing Clean Code 🧼🧑‍💻

In stories, you will often find the motif of a powerful demon that can be controlled only by knowing its true name. Once the hero finds out that name, through cunning dialogue or by investigating ancient tomes, they can turn things around and banish the demon!

I firmly believe writing code is not much different: through finding good names for functions, variables, and other constructs, we truly recognize the essence of the problem we are solving. The consequence of clarity gained is not just good names but also cleaner code and improved architecture.

The power of correct naming in programming

I would go as far as to say that 90% of writing clean code is “just” naming things correctly.
Sounds simple, but it is really not!

Let’s take a look at a couple of examples.

Example #1

// Given first and last name of a person, returns the
// demographic statistics for all matching people.
async function demo (a, b) {
  const c = await users(a, b);
  return [
    avg(c.map(a => a.info[0])),
    median(c.map(a => a.info[1]))
  ];
}
Enter fullscreen mode Exit fullscreen mode

What is wrong with this code?

  1. The name of the function demo is very vague: it could stand for “demolish”, or as in “giving a demo/presentation”, … .
  2. Names a, b, and c are completely uninformative.
  3. a is reused in lambda inside the map, shadowing the a that is a function argument, confusing the reader and making it easier to make a mistake when modifying the code in the future and reference the wrong variable.
  4. The returned object doesn’t have any info about what it contains, instead, you need to be careful about the order of its elements when using it later.
  5. The name of the field .info in the result of a call to users() function gives us no information as to what it contains, which is made further worse by its elements being accessed by their position, also hiding any information about them and making our code prone to silently work wrong if their ordering changes.

Let’s fix it:

async function fetchDemographicStatsForFirstAndLastName (
  firstName, lastName
) {
  const users = await fetchUsersByFirstAndLastName(
    firstName, lastName
  );
  return {
    averageAge: avg(users.map(u => u.stats.age)),
    medianSalary: median(users.map(u => u.stats.salary))
  };
}
Enter fullscreen mode Exit fullscreen mode

What did we do?

  1. The name of the function now exactly reflects what it does, no more no less. fetch in the name even indicates it does some IO (input/output, in this case fetching from the database), which can be good to know since IO is relatively slow/expensive compared to pure code.
  2. We made other names informative enough: not too much, not too little.
    • Notice how we used the name users for fetched users, and not something longer like usersWithSpecifiedFirstAndLastName or fetchedUsers: there is no need for a longer name, as this variable is very local, short-lived, and there is enough context around it to make it clear what it is about.
    • Inside lambda, we went with a single-letter name, u, which might seem like bad practice. But, here, it is perfect: this variable is extremely short-lived, and it is clear from context what it stands for. Also, we picked specifically the letter u for a reason, as it is the first letter of user, therefore making that connection obvious.
  3. We named values in the object that we return: averageAge and medianSalary. Now any code that will use our function won’t need to rely on the ordering of items in the result, and also will be easy and informative to read.

Finally, notice how there is no comment above the function anymore. The thing is, the comment is not needed anymore: it is all clear from the function name and arguments!

Example 2

// Find a free machine and use it, or create a new machine
// if needed. Then on that machine, set up the new worker 
// with the given Docker image and setup cmd. Finally,
// start executing a job on that worker and return its id.
async function getJobId (
  machineType, machineRegion,
  workerDockerImage, workerSetupCmd,
  jobDescription
) {
  ...
}
Enter fullscreen mode Exit fullscreen mode

In this example, we are ignoring the implementation details and will focus just on getting the name and arguments right.

What is wrong with this code?

  1. The function name is hiding a lot of details about what it is doing. It doesn’t mention at all that we have to procure the machine or set up the worker, or that function will result in the creation of a job that will continue executing somewhere in the background. Instead, it gives a feeling that we are doing something simple, due to the verb get: we are just obtaining an id of an already existing job. Imagine seeing a call to this function somewhere in the code: getJobId(...)you are not expecting it to take long or do all of the stuff that it really does, which is bad.

Ok, this sounds easy to fix, let’s give it a better name!

async function procureFreeMachineAndSetUpTheDockerWorkerThenStartExecutingTheJob (
  machineType, machineRegion,
  workerDockerImage, workerSetupCmd,
  jobDescription
) {
  ...
}
Enter fullscreen mode Exit fullscreen mode

Uff, that is one long and complicated name. But the truth is, that we can’t really make it shorter without losing valuable information about what this function does and what we can expect from it. Therefore, we are stuck, we can’t find a better name! What now?

The thing is, you can't give a good name if you don't have clean code behind it. So a bad name is not just a naming mishap, but often also an indicator of problematic code behind it, a failure in design. Code so problematic, that you don’t even know what to name it → there is no straightforward name to give to it, because it is not a straightforward code!

Bad name is hiding bad code

In our case, the problem is that this function is trying to do too much at once. A long name and many arguments are indicators of this, although these can be okay in some situations. Stronger indicators are the usage of words “and” and “then” in the name, as well as argument names that can be grouped by prefixes (machine, worker).

The solution here is to clean up the code by breaking down the function into multiple smaller functions:

async function procureFreeMachine (type, region) { ... }
async function setUpDockerWorker (machineId, dockerImage, setupCmd) { ... }
async function startExecutingJob (workerId, jobDescription) { ... }
Enter fullscreen mode Exit fullscreen mode

What is a good name?

But let’s take a step back - what is a bad name, and what is a good name? What does that mean, how do we recognize them?

Good name doesn’t misdirect, doesn’t omit, and doesn’t assume.

A good name should give you a good idea about what the variable contains or function does. A good name will tell you all there is to know or will tell you enough to know where to look next. It will not let you guess, or wonder. It will not misguide you. A good name is obvious, and expected. It is consistent. Not overly creative. It will not assume context or knowledge that the reader is not likely to have.

Also, context is king: you can’t evaluate the name without the context in which it is read. verifyOrganizationChainCredentials could be a terrible name or a great name. a could be a great name or a terrible name. It depends on the story, the surroundings, on the problem the code is solving. Names tell a story, and they need to fit together like a story.

Examples of famous bad names

  • JavaScript
    • I was the victim of this bad naming myself: my parents bought me a book about JavaScript while I wanted to learn Java.
  • HTTP Authorization header
  • Wasp-lang:
    • This one is my fault: Wasp is a full-stack JS web framework that uses a custom config language as only a small part of its codebase, but I put -lang in the name and scared a lot of people away because they thought it was a whole new general programming language!

Support us! 🙏⭐️

GH star click

To help us improve our name at Wasp-lang 😁, consider giving us a star on Github! Everything we do at Wasp is open source, and your support helps us make web development easier and motivates us to write more articles like this one.

Image description

How to come up with a good name

Don’t give a name, find it

The best advice is maybe not to give a name, but instead to find out a name. You shouldn’t be making up an original name, as if you are naming a pet or a child; you are instead looking for the essence of the thing you are naming, and the name should present itself based on it. If you don’t like the name you discovered, it means you don’t like the thing you are naming, and you should change that thing by improving the design of your code (as we did in the example #2).

You shouldn't name your variables the same way you name your pets, and vice versa

Things to look out for when figuring out a name

  1. First, make sure it is not a bad name :). Remember: don’t misdirect, don’t omit, don’t assume.
  2. Make it reflect what it represents. Find the essence of it, capture it in the name. Name is still ugly? Improve the code. You have also other things to help you here → type signature, and comments. But those come secondary.
  3. Make it play nicely with the other names around it. It should have a clear relation to them - be in the same “world”. It should be similar to similar stuff, opposite to opposite stuff. It should make a story together with other names around it. It should take into account the context it is in.
  4. Length follows the scope. In general, the shorter-lived the name is, and the smaller its scope is, the shorter the name can/should be, and vice versa. This is why it can be ok to use one-letter variables in short lambda functions. If not sure, go for the longer name.
  5. Stick to the terminology you use in the codebase. If you so far used the term server, don’t for no reason start using the term backend instead. Also, if you use server as a term, you likely shouldn't go with frontend: instead, you will likely want to use client, which is a term more closely related to the server.
  6. Stick to the conventions you use in the codebase. Examples of some of the conventions that I often use in my codebases:
    • prefix is when the variable is Bool (e.g. isAuthEnabled)
    • prefix ensure for the functions that are idempotent, that will do something (e.g allocate a resource) only if it hasn’t been set up so far (e.g. ensureServerIsRunning).

The simple technique for figuring out a name every time

If you are ever having trouble coming up with a name, do the following:

  1. Write a comment above the function/variable where you describe what it is, in human language, as if you were describing it to your colleague. It might be one sentence or multiple sentences. This is the essence of what your function/variable does, what it is.
  2. Now, you take the role of the sculptor, and you chisel at and shape that description of your function/variable until you get a name, by taking pieces of it away. You stop when you feel that one more hit of your imagined chisel at it would take too much away.
  3. Is your name still too complex/confusing? If that is so, that means that the code behind is too complex, and should be reorganized! Go refactor it.
  4. Ok, all done → you have a nice name!
  5. That comment above the function/variable? Remove everything from it that is now captured in the name + arguments + type signature. If you can remove the whole comment, great. Sometimes you can’t, because some stuff can’t be captured in the name (e.g. certain assumptions, explanations, examples, …), and that is also okay. But don’t repeat in the comment what you can say in the name instead. Comments are a necessary evil and are here to capture knowledge that you can’t capture in your names and/or types.

Don’t get overly stuck on always figuring out the perfect name at the get-go → it is okay to do multiple iterations of your code, with both your code and name improving with each iteration.

Reviewing code with naming in mind

Once you start thinking a lot about naming, you will see how it will change your code review process: focus shifts from looking at implementation details to looking at names first.

When I am doing a code review, there is one predominant thought I will be thinking about: “Is this name clear?”. From there, the whole review evolves and results in clean code.

Inspecting a name is a single point of pressure, that untangles the whole mess behind it. Search for bad names, and you will sooner or later uncover the bad code if there is some.

Further reading

If you haven’t yet read it, I would recommend reading the book Clean Code by Robert Martin. It has a great chapter on naming and also goes much further on how to write code that you and others will enjoy reading and maintaining.

Also, A popular joke about naming being hard.

Top comments (43)

Collapse
 
jmfayard profile image
Jean-Michel 🕵🏻‍♂️ Fayard • Edited

Counter-argument : I don't find fetchDemographicStatsForFirstAndLastName "clean" at all.

async function fetchDemographicStatsForFirstAndLastName (
  firstName, lastName
)
Enter fullscreen mode Exit fullscreen mode

Uncle Bob acts as if there is no downside at all of using super long names
But there is a clear downside : cognitive load.

Imagine an article that was written like that.
Full of german words like PersonalEinkommenSteueerungsKommissionsMitgliedReisekostenErgänzngsRevisionsFund.

See how clean it is ?
Not.
The writer's goal is to increase the signal to noise ratio of his writing.

I strike instead to find a balance between clarity and verbosity.
Frequent words are shorter and less frequent functions longer.

The issue with first name and lastname in your example is that you are victim of primitive obsession, using strings instead of a real type.

I would refactor that to
function fetchDemographics(user) : Demograhics

Also "clean" is judgmental and nonsensical, no real code ever is clean.
Uncle Bob doesn't produce production code so for him that doesn't matter much.
But people who do should not stress out if they don't reach arbitrary levels of cleaness that don't matter to the customer anyway.

Collapse
 
raibtoffoletto profile image
Raí B. Toffoletto

Totally agree!! If I'm reviewing and see this:

fetchDemographicStatsForFirstAndLastName

it's a no go!

Naming should be meaningful but readable. There's a balance and a matter of good taste to put it in practice.

Collapse
 
davelapchuk profile image
Dave Lapchuk • Edited

Yeah I don't like that example either, but mainly because firstName and lastName are already described in the definition in the arguments. Seems redundant, especially in the age of IDEs that'll pop up the function definition or the definitions of all potential candidates as soon as you start typing it to call it.

Still, I would opt for a longer name than a comment above it any day.

Collapse
 
matijasos profile image
Matija Sosic

Interesting! I see both sides. What would be the ideal name for you? Also, I like very much the idea of using types to tell the story, if possible.

Thread Thread
 
jmfayard profile image
Jean-Michel 🕵🏻‍♂️ Fayard • Edited

What would be the ideal name for you?

The naming is ideal if it's clear for your coworkers so pull requests is the place to ask for feedback.

As a rule of thumb, I prefer to use shorter names for frequent actions and longer / more explicit names for less frequent actions.

queue.removeElementAtFirstPosition()
queue.pop()

queue.remove(7)
queue.removeElementAtPosition(7)

I like very much the idea of using types to tell the story, if possible.

Primitive obsession -> refactoring.guru/fr/smells/primiti...

Thread Thread
 
raibtoffoletto profile image
Raí B. Toffoletto • Edited

As Jean-Michel said, depends also of your coworkers culture and practices, for me a simple getDemographic or getUserDemographic will suffice. The first one is fine because it's clear that we need to pass User as an argument... but with we have several types of demographics in the app I'd go with the second.

Different cultures and languages communicate differently. In Europe for example, Mediterranean languages rely more on the context to pass the correct intent and be understood, while germanic and slavic are more about making it clear verbosely.

Same in code, for me, even the above queue.removeElementAtPosition() could have the Element dropped because it's a queue of elements... of course we are removing an element at that position.

And totally agree. Types tell you the context. I had a discussion recently with a new member in my team about adding the Async suffix to some methods in our api just because it's Microsoft's convention for c#. It's an API, everything is async by default, synchronous DB operations are the exception, so let's use the suffix for situations when we have an async and a sync methods to distinguish between them. For the rest, the type Task<> should be clear enough to indicate it's a asynchronous operation.

Thread Thread
 
matijasos profile image
Matija Sosic

amazing write up, thanks so much for sharing Rai 🙏 I completely agree, context is the king. The most important thing is that team is in sync and consistent it the naming practices they agreed upon.

Collapse
 
martinsos profile image
Martin Šošić

Author here - I agree, that might be a better name! Note that I was sticking to JavaScript here, so didn't want to use types - but I agree that if we have precise types, it can be ok to ommit that same info from name.
I use Haskell a lot these days - and there types are the name of the game. But, Haskell has no function overloading - so if you have two functions that fetch demographics, you will suffix them with some extra info about args, to differentiate them.
Soit really depends on the language:is it strictly typed, does it have function overloading, ... .

And while I agree that shorter names result in code that is easier to read quickly, practice has still shown to me that when not sure it is better to err on the longer side. It does depend on how often name is used also, as you said: but the name from my example is probably not going to be used often, it is quite specific, so I don't think making a name shorter will bring any benefits.

Your refactoring is interesting but I don't think I would go for it: it has arg "user" which is not as specific as "firstName" and "lastName", so it is not clear how is search really being done, which attributes are used to determine the people that are part of the demographic? Maybe you misunderstood and thought that we are getting demophragic data for just one person? But that doesn't make much sense because demographic is defined as statistics for a group of people.

As for "clean" being judgemental and not being realistic: I sometimes hear this argument about Uncle Bob being overly extreme in his book and not being practical, and I found so far that those who make this argument haven't really understood the essence of it. Of course there is no clear delineation between clean and non clean code, and sure, the book sometimes goes to a bit extreme lengths - but if you take the advice from it with a grain of salt and apply it, you will see a big difference, and your code will be much improved, so that is all that matters. Again, it is all about context, it depends on people, project requirements, tevhnology - a good engineer will figure out, with experience, how to apply the "clean code" to their situation with the right measure.

Collapse
 
jmfayard profile image
Jean-Michel 🕵🏻‍♂️ Fayard • Edited

Maybe you misunderstood and thought that we are getting demophragic data for just one person
Indeed, I did :)

Again, it is all about context, it depends on people, project requirements, tevhnology - a good engineer will figure out, with experience, how to apply the "clean code" to their situation with the right measure.

That's the thing, it's all context dependant but Uncle Bob delivers the same rules for everyone.
More often than not, devs end up delivering "quality" that don't matter one bit to the client.
They implement "clean architecture" because they don't want to be dirty devs.
Doing so, they loose sight that what really matters is usually not that but the time to market.
Why not both you may ask ?
If you are lucky it may work, but there is a real danger in poursuing the wrong goal.
Worse than that it often makes life worse not better for the people that are hired after them. Since programming is all about communicating with your colleagues, if you reach that point it's your failure not theirs.

Collapse
 
martinovicdev profile image
Boris Martinovic

One of the best articles I've read lately and it's a must-read for anyone. Naming things properly is one of the most important things we do.

And this isn't just important for projects that have teams around them. Think about coming back after some time on your code and you see incomprehensible names. It's such a time-waster trying to dechiper what you thought at the time.

Collapse
 
martinsos profile image
Martin Šošić

Yes exactly, I usually do it for myself first, then for others :D! Even a couple of days later, I hardly remember what I was doing -> if it wasn't for clear names I would get lost.

Collapse
 
hassankhosseini profile image
Hassan Khajeh-Hosseini

This is great, and agreed - naming goes across everything. I remember trying to come up with a name for our first startup ... and we couldn't find a good one, so finally called it 'PlanForCloud' - that name existed till we were acquired, and quickly killed hahaha

Collapse
 
martinsos profile image
Martin Šošić

Hah oh yeah, the name of the project is one thing that is the hardest to change :D. I love "Infracost" though, it is easy to remember and on point -> captures the essence ;).

Collapse
 
sreno77 profile image
Scott Reno

In addition to Clean Code by Uncle Bob, The Pragmatic Programmer is worth a read!

Collapse
 
martinsos profile image
Martin Šošić

Absolutely! My favourite is also "The Software Craftsman".

Collapse
 
georgewl profile image
George WL

I read both and personally wasn't keen

Uncle Bob especially, dude broke his own rules in his examples, and just wasn't consistent at all in what he was saying.

Collapse
 
jordantylerburchett profile image
Jordan-Tyler Burchett

This is a great article! I've always named variables and functions based on what they do and it makes code a lot easier to understand but something I noticed about myself was that I would do this but still over comment..

Commenting is important but it doesn't have to be extensive. If you write clean code it will speak for itself.

Collapse
 
artxe2 profile image
Yeom suyun

you can't give a good name if you don't have clean code behind it.

I completely agree with your statement.
Personally, I would also like to emphasize the importance of accurate indentation and file separation in addition to these points.
In the case of bun, the number of lines in a single file exceeded 20,000.

Collapse
 
ivosluganovic profile image
Ivo Sluganovic • Edited

Absolutely love this article, and could not agree more!
I was literally discussing some of the same points with a friend yesterday :)

Essentially, if someone names their code properly, it's beautiful and typically easy to follow; as you also stated, the main reason being that writing clean and good code is a prerequisite to good naming. And such code is easier to understand.
The converse is also typically true -- if code was named recklessly, ... good luck in understanding it.

Since I've been thinking about this quite a lot, here a tidbit of my reasoning related to self-descriptive vs non-descriptive names:


MORE GENERALLY, giving an idea (e.g. a system component/server/etc) a name is crucial because it makes it immediately possible for everyone to think about it in bounded terms (as long as we have a shared understanding of it basic meaning, we will all mostly agree what you mean when you mention X, or agree on whether Y is part of X or not, etc.).
This was nicely emphasized in Orwel's 1984 and its Newspeak: if you don't have a shared name for a concept (or are forbidden from using it, like protest in 1984), then it is very hard to plan/collaborate because you need to be providing the idea's boundaries and description every time: "let's go to the main square and cause a bit of upheaval and raise attention to our cause, but not too much so that it does not become dangerous", rather than simply say "let's go and protest".


HOWEVER, as much as I love (and typically use) self-describing names in code (no need for comments/less documentation!), it is actually often beneficial to name certain types of entities closer to how we name children: use names which are disconnected from their characteristics (as those are not known at creation time :D).
This is typically true with ideas whose names are hard to change (e.g. companies!), but whose functionality evolves.

Using nondescriptive names like "fluffy", "lookingGlasss", "Armadillo", "Beast", essentially creates a layer of indirection/mapping that allows us to gracefully extend/change change their characteristics/behavior/responsibilities with time by simply updating the documentation to take the changes into account.
Otherwise, we end up in a typical situation where each name has a caveat about "historical reasons why it is indeed called ProxyServer, but that name is long deprecated,it actually also does A, B, C and D".

Collapse
 
martinsos profile image
Martin Šošić

That is an interesting take and makes perfect sense to me, thanks for sharing!
I guess this would be for bigger sub-systems, where as you said, the role of it may change to some degree with time, and it is not yet clear what it is going to be exactly.

I agree that for common concepts it is good to have short names. And it works, because we assume everybody working on project will have to learn what those stand for, and in return we get easier naming / reading / talking about them. In the current project I am working on (Wasp), we have such names: Entity, Analyzer, Generator, Operation, ... -> these do require context to be understood, but they are so widely used and central to the whole project that these short names work fine.

Collapse
 
georgewl profile image
George WL

I love your inclusion of cartoons, really adds to it

Collapse
 
tonilastre profile image
Toni

Great article! 100% agree with everything here! Naming (and structure) is what makes your code clean so it is easy to get back and continue working when you need to, same with having a tidy clean room or workstation.
Whenever I need to add comments to my code, I always stop with it and think about a better naming instead.

I also use specific rules for naming variables, e.g. dates and timestamps always end with <name>At (createdAt, lastModifiedAt), maps are always <key>By<value>, (e.g. personById, titleByCode), sets have prefix unique<x>, booleans start with is<x>, has<x>, etc.

Collapse
 
martinsos profile image
Martin Šošić

Thanks, love those prefixes/suffixes! I listed only a couple of them, but I also use stuff very similar to what you described, especially the thing for maps / grouping "By".
I think it would be very interesting to add more examples to the article but it started feeling very long at some point.

Collapse
 
matijasos profile image
Matija Sosic • Edited

it would be nice to produce some kind of naming cheatsheet / styleguide that people can refer to and fork

Collapse
 
llxd profile image
Lucas Lima do Nascimento

Such a nice article!

Creating names is a big part of the reason I like TailwindCSS so much by the way.

It takes a lot of effort coming with reasonable and good names everytime. Having this weight lifted off your shoulders at least for CSS classes is pure relief.

Good job, @martinsos!

Collapse
 
matijasos profile image
Matija Sosic

+1 on this, the best thing with Tailwind is not having to invent names - that's an amazing point Lucas :)