There are only two hard things in Computer Science: cache invalidation and naming things.
— Phil Karlton
...a variable and a variable's name are essentially the same thing. Consequently, the goodness or badness of a variable is largely determined by its name.
— Steve McConnell
The software we write is divided into parts: modules, classes, functions, variables. As we write each part, we must name it. This article takes concepts from linguistics, in particular from first and second language learning, and relates them to our everyday use of language in code.
Why do we use the names we do?
Following convention
If every time we spoke to one another we had to expain each word we were going
to use, effective communication would be nearly impossible. We rely on shared
knowledge to communicate effectively; when we program, we ideally try to name
structural elements to exactly match their conventional meaning.
A customer_id
should be something which identifies a customer; an i
should be an integer index into a looping structure; a title
should
a heading describing of a block of information. Convention avoids the overhead
of having to explain ourselves endlessly.
Frequently, we have to name something which is near to a conventional meaning, but not quite on the mark. Luckily, people are pretty good at stretching a word's meaning — this is called sense extension. It's how you can ask someone to "pass you the dog", and have them understand that you're referring to the picture of a dog on the table in front of you. Our brains are really good at extending word meanings to nearby ideas which are related. If there is no perfect name, coerce the word for the nearest matching concept, and people will usually know what you mean.
Making new names
The power of context is strong enough that people can understand you even if
you create a new word entirely. In language learning, this is called
inferring from context, and it's how you learned to read. Think about it,
did someone really teach you every word you know? If you use a variable name
with no obvious meaning, like xyz
, the maintainer can still determine its
meaning by looking at where it is and what it does.
There is, however, a big cost to new names. Without a conventional meaning, the maintainer has to pick up a lot more information from the context. This only works if the context has enough information, and is itself mostly known ("pregnant context"). If the context also is full of new names, the maintainer has to simultaneously infer many meanings at once, a hideously difficult task. Ultimately this is about limits to our short term memory. These limits suggest we should use new names sparingly, and only in cases where using an existing word would cause more problems than it would solve. If you need to use multiple new names, explain at least some of them with comments.
Being cooperative
It's not enough to just use words we share in common when we talk to one another. Many of those words have multiple meanings, and it might not be clear what they're referring to in the real world. We rely on the cooperative principle, another way of saying that we're assumed to talk in a way which maximises the recipient's understanding.
These assumptions are embodied in Grice's maxims,
which suggest we should use names in code which are unambiguous, brief and
which convey neither more nor less information than strictly required. This
means our names should be sensitive to the context in which we use them.
Amongst many email addresses, including the word "customer" in
customerEmail
helps to disambiguate a variable's purpose; amongst other
customer data, just email
would be better.
There's reason to believe the cooperative principle might not hold for coding, at least not as we'd expect. The challenge is that the coder is holding two conversations at once, one with the compiler/interpreter and one with the future maintainer. This can be like having dinner with two friends fluent in Russian, who occasionally lapse into it mid-conversation — it depends how polite they are as to whether they keep you in the loop. When code is especially algorithmic, difficult or plain hacky, this is why best practices have us use comments to help guide the way.
When names clash
Suppose we're talking houses, and in this context the word "big" for me means 3 rooms or more, but the word "big" for you means 5 rooms or more. This is a very basic example of lexical gridding, a situation where two parties have carved up the world in different ways, such that the words we use don't exactly line up with one another. Normally this comes up between different languages; whenever you hear that there is no English equivalent for some foreign word, this is a lexical gridding issue. This is also why word-by-word translations between languages are terrible, since there is usually no exact match.
In the early days of maintaining a system, these kinds of gridding effects are present. The coder and maintainer, despite using the same words, mean very different things by them. Since, as discussed, we constantly extend words past their traditional meanings, this kind of subtle misunderstanding is largely unavoidable.
Ultimately, the two grids must be realigned, and there are two ways that can happen: either the maintainer gradually learns and accept the meanings used by the coder, or the maintainer refactors the code until the words used reflect the meaning he or she expects. Sometimes we even experience lexical gridding when revisiting old code of our own — the extra domain knowledge we have then means we have a different, but better understanding of the problem. A refactor to reflect this better understanding brings grids into alignment, and can vastly improve code.
Is naming important?
If your code never needs to be maintained, by you or anyone else, then naming's not a big deal, since it focuses on the communication between coder and future maintainer. Still, it may help you actually complete your project; couching our problems in language is what helps us solve them. This is why explaining code to a teddy bear is a valid debugging technique.
If your code will one day be maintained — a reasonable hope for most projects — taking the time to communicate well will likely save a lot of work in the long run. I believe that the process of choosing good names is similar to the process of coming to a good understanding of the problem, a skill you can develop. Names also serve as a kind of code smell. Good names reflect the heart of a thing; they reflect reality. Bad names come from bad abstractions, from a poor understanding of reality.
Go forth and name
This post has shown you some of the intricacies involved in choosing a good name, and the role of convention, sense extension, memory and gridding in software naming. I've argued that naming well is important for long-term code maintenance. So, don't be ashamed then next time you have trouble naming something. You're not really stuck naming it, you're stuck understanding it. Taking the time to reach a good understanding and thus a good name is likely to help you find abstractions you'll be more productive with.