The Right Question for the Right Name
“What’s the right name for this variable/function/class/module?”
As programmers, this something we ask ourselves multiple times a day, and that is also a question that comes up often during code reviews.
In this video, I’ll share with the question that I use to determine a name for something in code. And often, the answer to that question gets you to a name that sounds right.
Transcript of the video:
Hey, this is Jonathan Boccara for Fluent C++. Today, we’re going to talk about naming.
Sometimes, it’s hard to find just the right name, and we’re going to see one technique to find a good name for something in code.
This technique consists in asking the right question. And the right question is:
What Does This Represent?
What does this thing I’m trying to name, that variable, that function, that class, that interface, what does it represent.
You may think, “Well, yeah, that’s what I’m after”. But it’s actually a very precise question, and it means something very specific. There are things that it doesn’t mean. Let’s get into that.
It comes down to respecting levels of abstraction. What do we mean by levels of abstraction? It shows easily in a call stack. So in a call stack, you’ve got functions that call each other, and at a given level of the call stack you’ve got the name of the function that says WHAT it does.
To get to a lower abstraction level it’s something that’s called by that function. And to get to that lower level, you answer the question “HOW is it implemented”. Conversely, to get to a higher level of abstraction, the question to ask is: “IN WHAT CONTEXT is my thing used”.
So the level of abstraction of something is what it represents, the level below it is how it’s implemented, and the level above is in what context it is used.
The right name for something is characterized by its own level of abstraction, so what it represents. Not how it’s implemented nor in what context it is used.
Let’s see what that looks like in code.
Let’s create a collection of balls of various colors:
enum class Color { Black, Blue, Green, Orange }; class Ball { public: explicit Ball(Color color) : color_(color){} Color getColor() const { return color; } private: }; int main() { }
We have class that represents a ball, and this ball has a color. It can be either black, blue, green, orange, whatever. Now let’s create a collection of balls:
std::vector<Ball> bagOfBalls = { Ball(Color::Black), Ball(Color::Orange), Ball(Color::Green), Ball(Color::Black), Ball(Color::Blue) };
Let’s now find the first ball in that collection of balls that has the color green.
auto = std::find_if(begin(bagOfBalls), end(bagOfBalls), [](Ball const& ball){ return ball.getColor() == Color::Green; });
What comes out of std::find_if
is an iterator, pointing to the first ball in the collection that has color green. Now the question is: what’s the right name for that thing that comes out of std::find_if
?
One possibility would be to call it “it
” or “iter
” or “iterator
” because it is an iterator. But if we think back to our right question to find the right name, which is “What does it represent?”, this thing does not represent an iterator. It’s implemented as an iterator, but we don’t care about that when reading the code (we care about it when writing the code, but code gets read much more often than it is written).
What we care about is what it represents. And what it represents is the first ball that has color green. So let’s give it a better name:
auto firstGreenBall = std::find_if(begin(bagOfBalls), end(bagOfBalls), [](Ball const& ball){ return ball.getColor() == Color::Green; });
That was an example of not choosing a name that has too low a level of abstraction.
Now let’s see an example of a name that could be too high in terms of levels of abstraction. In this example, we’re going to consider a book that has a new revision, and we’d like to know how bigger the new revision is compared to the old revision. Say that we’re comparing number of pages for example.
So let’s create a function that computes the ratio of the new book compared to the old book:
double getRatio(Book const& oldBook, Book const& newBook) { // ... } int main() { }
That looks OK. But later on imagine that we’ve got a new requirement: to compare the size of a fiction novel versus en encyclopaedia. We feel that we can reuse our getRatio
function. Except that the parameters don’t make sense, because we’d like to compare the novel with the encyclopaedia and neither one is old or new. They’re just two different books.
This shows that this first attempt of naming these parameters oldBook
and newBook
are tied to the context of the old version and new version of the book. To be able to reuse this with the novel and encyclopaedia, what we’re doing is actually comparing two books, and one of them is the reference. So let’s call them this way:
double getRatio(Book const& referenceBook, Book const& book) { // ... }
The first one is the reference book, and the second one is a book we’re comparing it too.
That’s a very simple example, but it illustrates that a good name doesn’t depend on the context where it is being used.
One last thing: it’s actually pretty hard to come up with the right name on the first trial. Particularly a name that says what it represents and not in what context it’s used.
When you first write function parameter, you may give them a name while begin influenced by the context where your function is being used.
When you have a new context where your function is being used, you have new info about how your function is used and what it is exactly. When you have that new context, I want you to go back and think about the names of your parameters. Make sure that they fit in all contexts.
If you liked this video, you can subscribe to the channel, and give a thumb up! Thank you, and I see you next time.
Don't want to miss out ? Follow:   Share this post!