Naming in Code: A Monk Knocks at the Moonlit Door
One day, riding on a donkey, he chanted: “Birds roost in the trees by the pond; a monk knocks at the moonlit gate.” At first he thought of “push,” then of “knock”; he weighed them but could not settle. So on the donkey he made the gesture of pushing, then the gesture of knocking.
— Jianjie Lu, “Jia Dao Offends the Imperial Will”
Two lines took three years to win; once chanted, tears stream down. If no true listener prizes them, I’ll go back to lie in my old hills.
— Jia Dao, “After Writing the Poem”
Naming—or choosing words and phrasing—is no easy thing. Think of when your child is born: you scratch your scalp raw, rack your brain, leaf through dictionaries, and claw through classics of poetry and antiquity in a constipated haze—then you’ll know how painful it is. Unfortunately, the thing programmers deal with most every day is naming. If a programmer has read too few books and doesn’t know how to borrow from the classics (other people’s code), then most likely when naming he’ll be at a loss and casually produce names like Dog Egg, Dummy, and Fatty Three.
A programmer’s code reflects his ability; the standard of his names reflects his cultivation and depth. In a program, if you strip away strings, punctuation, and whitespace, what is left? Names! For example:
function formatter(params) {
const result = joi.validate(params, formatterSchema, { allowUnknown: true });
if (result.error) throw result.error;
const value = result.value;
value.tag = helper.app.getAppTag();
value.flag = value.flag || new Flag();
if (value.method === 'get' || value.method === 'head') {
value.flag.set('etag', value.flag.has('etag') !== false);
}
value.description = mustache.render(value.description, helper.doc)
registeredFormatters.push(value);
}
Let’s denote strings by s, keep punctuation, and replace names with n:
n n(n) {
n n = n.n(n, n, { n: n });
n (n.n) n n.n;
n n = n.n;
n.n = n.n.n();
n.n = n.n || n n();
n (n.n === 's' || n.n === 's') {
n.n.n('s', n.n.n('s') !== n);
}
n.n = n.n(n.n, n.n);
n.n(n);
}
As you can see, when writing code, 80% of the time we are dealing with names. Naming is that important!
If we don’t name well, and every name has the character of Dummy or Fatty Three, then code written by humans is no different from code obfuscated by an obfuscator:
Y=function(a,b,c){
a=B(a)||[];
var d=b.callback,e=b.config,g=b.timeout,l=b.ontimeout,k=b.onerror,q=void 0;
...
}
Of course, using more meaningful names for beautiful things is something even junior programmers understand. But in daily life we often see code like this:
try {
const availDates = getAvailabilityDates(video.policy, { country, platform });
if (availDates.boolVal) {
video.availabilityStarts = availDates.availabilityStarts;
video.availabilityEnds = availDates.availabilityEnds;
}
} catch (error) {
// Non-fatal. Log error and continue
log.error(...);
}
Who can tell me what the hell boolVal is? In such simple code, why does the author repeat words like avail and availability over and over? Is the author emphasizing something important three times, or trying to murder our eyes and poison our minds?
Don’t laugh—the author in question is ourselves. From time to time, we all write code like this.
bad code smells. Notice that a self-absorbed comment has also been added, one that does more harm than good, leading you further astray into some Scottish-flirtation tangent.
At this point, smart as you are, you’ll feel the bigger problem lies in getAvailabilityDates. Let’s keep looking.
function getAvailabilityDates(policy, parserInput) {
parserInput.app = parserInput.app || 'tubitv';
parserInput.date = parserInput.date || (new Date()).setHours(0, 0, 0, 0);
try {
if (policy)
return availabilityParser.parse(policy, parserInput, { minDate: MIN_DATE, maxDate: MAX_DATE });
} catch (error) {
throw new ParserError(error.message);
}
return { boolVal: true, availabilityStarts: null, availabilityEnds: null }; // default
}
You can’t say it’s bad: at least each line’s intended meaning is clear, and you can understand what it wants to do. But reviewing code like this leaves a fishbone stuck in your throat. Several schemes for making it cleaner and clearer come to mind, but to articulate them basically amounts to telling the other person: Sorry, please clarify your thinking and rewrite this code.
Let’s try simplifying the names first:
function getAvailabilityDates(policy, data) {
data.app = data.app || 'tubitv';
data.date = data.date || (new Date()).setHours(0, 0, 0, 0);
try {
if (policy)
return DateParser.parse(policy, data, { minDate: MIN_DATE, maxDate: MAX_DATE });
} catch (error) {
throw new ParserError(error.message);
}
return { boolVal: true, start: null, end: null }; // default
}
The availability in the function name is still awkward. When this awkwardness appears, it may be because our code is not abstract enough. At least here, we can turn it into a parameter. Also, boolVal is a bizarre thing; besides the naming problem itself, we don’t actually need it. The caller only cares that you give me two dates; as for whether something went wrong along the way, who cares! The revised code:
function getDates(policy, data, type='availability') {
data.app = data.app || 'tubitv';
data.date = data.date || (new Date()).setHours(0, 0, 0, 0);
const default = { start: null, end: null };
if (!policy) return default;
try {
return DateParser.parse(policy, data, type);
} catch (error) {
return default;
}
}
This makes the caller much simpler too:
const { start: start, end: end } = getDates(policy, { country, platform });
video.scheduledStart = start;
video.scheduledEnd = end;
Before and after, the code is much clearer, easier to read, and easier to modify. We can go further: if we control the error inside the policy parser, we can simplify the code even more.
We could even rename this code to policy.getDates, which gives it a different semantic landscape.
From this example we can see the importance of naming. When you give an individual a name, you give it life and existential value. The better the name, the more deliberate the thought, the greater the individual’s value and the more chances others will use it. A name like getAvailabilityDates spins a cocoon around itself and confines itself to a very small space from the start.
So what is a good name?
First, avoid cumbersome compound words. Once you start using compounds like getUserNotFinishedViewHistory, something must have gone wrong. Either you need to refactor the surrounding code (perhaps a pile of classes and functions), or you need to move these piled-up words into another context—as shown above, making them parameters is a good idea.
Second, learn to express your intent, not recite facts. For example:
if (getPermissionsByUser(user).contains(getPermissionObject('can view', board)) {
// see user's permission contains 'can view' permission for the given board
...
}
It recites a pile of facts verbosely; the intent is actually:
if (user.canView(board)) {
...
}
The process of writing a program is actually a process of communicating with “others.” These others may be your colleagues, or your future self. How well you explain a matter is a real test of ability. Programmers who write good code are generally good communicators in life, at least good written communicators; but programmers who write badly surely communicate badly.
We often see that when code is not good enough, comments appear to explain the code’s intent. This is because we have used awkward, hard-to-understand expressions to write the code; even we feel uncomfortable, so we need a passage of text to supplement it. However, the person writing comments and the person writing code share a highly consistent mindset. If I can express clearly in a comment what I want to say, then I will certainly be able to reflect my thought in code (and thus have no need to write comments). Therefore, good code often needs no comments; bad code merely translates the same content into English (or Chinese) once (as above).
Finally, a good name must have a certain degree of abstraction; it should not only focus on the present but also face the future. Most code is not meant to survive a day or a month; it often stays in the codebase for an endless time, until it is refactored or its life ends. You don’t want to change variable or function names just because requirements change slightly, do you? When naming, it helps greatly to abstract appropriately and use a few rhetorical devices.
Those names that endure and roll off the tongue mostly have this characteristic. For example: pipe, stream, flow, socket. No matter the language, as soon as someone writes a.pipe(b), we immediately understand the intent, because pipe abstracts a class of common tasks and uses something familiar from daily life as a metaphor—hence it endures. I once read a piece of IKE (IPSec VPN protocol) code. In the protocol negotiation process, the author vividly used pitcher / catcher to handle the behavior of the two ends. Reading that code was a pleasure: the dry protocol was deconstructed layer by layer through the actions and stages of a baseball game, clear and beautiful. The feeling was like reading Fortress Besieged and coming upon: “This diploma has the function of the fig leaf over Adam and Eve’s lower parts; it can cover shame and hide ugliness. A tiny piece of paper can conceal a person’s emptiness, meagerness, and stupidity,” or “Watching them talk and laugh, not allowing herself to get a word in, she felt as if the words were pressed flat and she still couldn’t squeeze in; she felt bored and disgraced, like a beggar chasing after a rickshaw, running a long way without getting a single coin, wanting to stop but unwilling.” It is full of vivid imagery.
Having said so much, how does one train a good level of naming?
The naming level in code accumulates gradually. Besides the three directions above, how you practiced and improved your writing from elementary school to college is how you practice and improve your code naming: read more books (code), write (and copy) more essays (code). It is simple and difficult, requiring deliberate practice over a sufficiently long time.
A few days ago, Caitou wrote an essay, “Programmers, Struggling in the World of Muggles,” which made programmers’ blood boil. But we shouldn’t become smug because of it. You should know that most of us are mediocre in talent; the code we write is dry and ugly, and every variable and function name we invent gives off swamp gas. In the world of wizards, you and I are not the exceptionally gifted Hermione or Harry with a protagonist’s halo; at best we are Ron, whom people hate for not living up to his potential, or the dopey Neville Longbottom. Only by clearly recognizing this will we take a deep breath and engage in deliberate practice year after year—just like the clumsy wizards who botch the Transfiguration spell again and again, and then keep training again and again.
In the end, time will reward this deliberate practice. Didn’t Ron and Neville, through unremitting effort, become the Aurors and professors they hoped to be? — Even Neville—the dolt whom others didn’t know was “the chosen one,” and who didn’t know it himself—fulfilled his “chosen one” destiny!