I sometimes wonder if naming things would have the perceived difficulty it does in our field if that quote never existed. Naming variables/functions/classes is not hard - just say what it does. Sure, naming a library may seem difficult if your plan is to be clever, but I don’t think it really matters that much. Are [PyTorch, mallet, angular, dplyr, scikit-learn, cucumber, react] good names? Why? Could someone just as easily give a list detailing why they’re poor names? Probably.
But it isn't always easy to describe both clearly and succinctly what exactly a function (/ etc.) is doing.
And a particular issue I have is one of audience: to whom is this name addressed? Should it be at least somewhat obvious to a newcomer to the codebase to understand what the function is doing? Or can I assume that the reader / maintainer is already knowledgeable of the context in which this function exists?
In my (limited) experience, this becomes exacerbated when I'm trying to name some abstract operation in a context that doesn't already have a well defined lexicon. So then I, unfortunately, must be the person to create this dictionary of terms that I will be referring to repeatedly throughout the problem space.
On top of this, I generally don't even know how I'm going to solve the problem when I first start implementing, so my first round of names might be strikingly obtuse to an outside observer. But if the implementation becomes sufficiently complex, and I don't take the time to refactor or receive input early on, these names become intuitive to me via repetition, so then I end up building on top of them and create this pyramid of gibberish that at some point made sense to me but that someone will unfortunately have to take the time to grok at some point in the future.
This doesn't happen so often that I would say it's the second hardest problem in our field, but it definitely is something that can contribute significantly to tech debt. ¯\_(ツ)_/¯
As a C programmer i have two ways I go about selecting names. For anything uniqe, I use long post fix names. I think of them as a directory path to the functionality. So instead of:
I then do the same with files, so the above would probably reside in a file called:
project_module_object.c
This makes everything easy to find, you know where things belong and you can search for partial names to find instances of interfaces.
For local variables, I have a small set of variable names that all have specific meninges like i, j, k, f, sum, found, best, length, count, that I always use in the same way. They therefore become very easy to read since I know their use. If something doesn't fit the limited set of common variables, I revert to long post fix names.
For maths, the problem just sometimes lives in a deeper level of abstraction where you need to understand the problem anyway to make any meaningful changes to the code. So I don't think you'd really gain anything by renaming the kalman gain calculation from
because if you don't know what any of these things are by reading up on the theory, the names (or I believe any others) aren't going to help you figure out how a kalman filter works.
Names won’t help you understand the fundamentals. It will help you follow the logic, given the fundamentals are understood.
For example I’m reading a PDF now where you have 6 variables involved - 2x2 matrix D which is decomposed to variables [g h], a set of points R, two points defining a parallelogram P and a vector v — where the definitions are a scattered (based on order introduction/definition to the pdf, since they’re used for other things as well).
I have to keep a LUT to follow any given formula because it’s just plain hard to keep track of anything.
And of course, the second I convert it to code, it all gets real names with arbitrary intermediaries and at least I can read it through in a single pass.
Names are there to remind you what the hell you’re talking about. Mathematics on the other hand always try to be as general as possible — your formulas don’t want to discuss anything specific; any object that fits the required properties will do, starting subject/context be damned.
I agree to some extent of course. If there really are meaningful concepts for intermediates or the whole formula, especially if you are applying it to a specific domain, you should name them.
(I also just looked up the equations and noticed the formula I gave was just completely wrong, so please don't implement a kalman filter like that.)
The naming problem can not be separated from semantics and programming style. For example, I use the principle "name a function so that ideally there's no need to document it or check its source", i.e. function names should be descriptive enough but that implies that functions should be small, do one thing and do it well.
Another principle I use is: if a variable's definition and usage can fit on the screen then give it the shortest possible name. Again, this implies that both functional-leaning style of programming and short naming are encouraged.
Corollary: there's no reason to give an int loop variable any name other than "i" unless there's a naming conflict or the loop body is too long (which it shouldn't be). Right?
But at the end of the day I'd formulate the fundamental principle of naming as:
Do whatever it takes to reduce mental tax for the reader, assuming the reader is aware of the context of the codebase, and roughly has a median level of experience in programming.
The latter part is important because I think good code should teach less experienced programmers rather than make it easier for them to read it at the expense of succinctness.
I have swung back on forth on variable naming over the years. Right now for me the focus is on part V of this article. Currently I prefer shorter variable names, even if they require a bit more context.
Let's use a concrete example. Say I'm writing a lambda function, and I name it LambdaThatDoesTheThing. This is a long name that (suppose) clearly describes the Lambda. This requires little context, tells me what the function does, and it helps me to tell it apart from others. So far so good.
Now imagine we want to add Metrics, so we call them LambdaThatDoesTheThingMetrics. But maybe we have different kind of metrics, so we actually need two objects, LambdaThatDoesTheThingExecutionMetrics, and LambdaThatDoesTheThingFailureMetrics. We also want alarms, so we add LambdaThatDoesTheThingAlarms. And we want a dashboard, so we add LambdaThatDoesTheThingDashboard.
These variables by themselves might not be a big deal, but when you compose them together they create big chunks of code. At some point the long names actually obscure the intent of the code.
What if instead we encapsulate all of this in a function createLambdaThatDoesTheThing, and then remove the long prefix from all variables? Now we can clearly see that we are manipulating Metrics, Alarms and Dashboards. Sure, we need a bit more of context, but once we have that, the code is much easier to read and follow.
This infrastructure example might be a bit synthetic but I believe it represents how some real codebases are written.
Perhaps the main difference between the notation of mathematics and programming is not the former's lack of paper, but rather the inherent complexity of the problems they aim to solve.
(A brief aside on what I mean by inherent complexity: solving some partial differential equation or writing 'I must turn in my homework' one hundred times might both take the same amount of time, but the latter is less complex. Similarly, Dijkstra's algorithm to compute shortest paths might be easier to implement than a form to add a new employee to the database, but it is nevertheless more complex.)
When writing a program, one usually has to juggle lots of balls that move in simple ways. You can make a career out of writing programs that convert data between various mostly equivalent representations (table in database, packet on the network, Python object in RAM, pixels on the screen). I do not want to claim that this is easy: the details of what data ends up where, of how to deal with the not-quite-equivalent representations, of who is able to see what, etc., matter. But solving complex problems of which code to write does not mean that the code solves complex problems.
So it makes sense to use longer names. You have only little attention to spare on individual pieces and s.add(b) is more difficult to understand than submit_form.add(submit_button). Here, adding things to some container is a well-understood concept, you simply want to know which container and which things.
Conversely, when your problems have a high inherent complexity then you end up with a small number of balls that behave in a complicated manner. (If you have many complicated balls, the problem is too difficult to solve.) So you split up your explanation: first you describe the balls, then you assume that the reader knows what they are and give a succinct explanation of their interactions. (Flipping the order sometimes makes sense too.) It is clear that ax²+bx+c = (ax+b)x+c is easier to understand than equals(add(add(mul(parameter_quadratic, mul(variable, variable)), mul(parameter_linear, variable)), parameter_constant), add(mul(add(mul(parameter_quadratic, variable), parameter_linear), variable), parameter_constant)). The latter is simply too noisy, it does not allocate the symbols in an efficient manner to convey its meaning.
However, writing ax²+bx+c assumes that the reader is familiar with the meanings of a,b,c,x and with addition, multiplication and exponentiation. You probably are (else this would have made a poor example), but even if you were not, it would still make sense to explain these concepts first, to enable the succinct explanation.
> I'm pretty sure cache invalidation is the harder of the two, because of some underlying OCD most of us have about picking two arbitrary constants that dictate sweep frequency and TTL. I'd even go as far as to speculate most of the evolution of programming has been a weird spiritual exercise around trying not to pick those two arbitrary numbers. Functional programming as a whole can even be understood as a set of taboos around this, because what good is shared OCD if it doesn't lead to religion.
12 comments
[ 3.1 ms ] story [ 34.4 ms ] threadBut it isn't always easy to describe both clearly and succinctly what exactly a function (/ etc.) is doing.
And a particular issue I have is one of audience: to whom is this name addressed? Should it be at least somewhat obvious to a newcomer to the codebase to understand what the function is doing? Or can I assume that the reader / maintainer is already knowledgeable of the context in which this function exists?
In my (limited) experience, this becomes exacerbated when I'm trying to name some abstract operation in a context that doesn't already have a well defined lexicon. So then I, unfortunately, must be the person to create this dictionary of terms that I will be referring to repeatedly throughout the problem space.
On top of this, I generally don't even know how I'm going to solve the problem when I first start implementing, so my first round of names might be strikingly obtuse to an outside observer. But if the implementation becomes sufficiently complex, and I don't take the time to refactor or receive input early on, these names become intuitive to me via repetition, so then I end up building on top of them and create this pyramid of gibberish that at some point made sense to me but that someone will unfortunately have to take the time to grok at some point in the future.
This doesn't happen so often that I would say it's the second hardest problem in our field, but it definitely is something that can contribute significantly to tech debt. ¯\_(ツ)_/¯
create_object(); delete_object();
I do:
project_module_object_create(); project_module_object_delete();
I then do the same with files, so the above would probably reside in a file called:
project_module_object.c
This makes everything easy to find, you know where things belong and you can search for partial names to find instances of interfaces.
For local variables, I have a small set of variable names that all have specific meninges like i, j, k, f, sum, found, best, length, count, that I always use in the same way. They therefore become very easy to read since I know their use. If something doesn't fit the limited set of common variables, I revert to long post fix names.
A much longer explanation of how is use C that goes in to more depth about naming can be found here: https://www.youtube.com/watch?v=443UNeGrFoM
K = dot(dot(H, dot(P, H.T)) + R, inv(S))
to
kalman_gain = dot(dot(measurement_function, dot(covariance_matrix, measurement_function.T) + measurement_noise_covariance, inv(system_uncertainty))
because if you don't know what any of these things are by reading up on the theory, the names (or I believe any others) aren't going to help you figure out how a kalman filter works.
For example I’m reading a PDF now where you have 6 variables involved - 2x2 matrix D which is decomposed to variables [g h], a set of points R, two points defining a parallelogram P and a vector v — where the definitions are a scattered (based on order introduction/definition to the pdf, since they’re used for other things as well).
I have to keep a LUT to follow any given formula because it’s just plain hard to keep track of anything.
And of course, the second I convert it to code, it all gets real names with arbitrary intermediaries and at least I can read it through in a single pass.
Names are there to remind you what the hell you’re talking about. Mathematics on the other hand always try to be as general as possible — your formulas don’t want to discuss anything specific; any object that fits the required properties will do, starting subject/context be damned.
(I also just looked up the equations and noticed the formula I gave was just completely wrong, so please don't implement a kalman filter like that.)
Another principle I use is: if a variable's definition and usage can fit on the screen then give it the shortest possible name. Again, this implies that both functional-leaning style of programming and short naming are encouraged.
Corollary: there's no reason to give an int loop variable any name other than "i" unless there's a naming conflict or the loop body is too long (which it shouldn't be). Right?
But at the end of the day I'd formulate the fundamental principle of naming as:
Do whatever it takes to reduce mental tax for the reader, assuming the reader is aware of the context of the codebase, and roughly has a median level of experience in programming.
The latter part is important because I think good code should teach less experienced programmers rather than make it easier for them to read it at the expense of succinctness.
I have swung back on forth on variable naming over the years. Right now for me the focus is on part V of this article. Currently I prefer shorter variable names, even if they require a bit more context.
Let's use a concrete example. Say I'm writing a lambda function, and I name it LambdaThatDoesTheThing. This is a long name that (suppose) clearly describes the Lambda. This requires little context, tells me what the function does, and it helps me to tell it apart from others. So far so good.
Now imagine we want to add Metrics, so we call them LambdaThatDoesTheThingMetrics. But maybe we have different kind of metrics, so we actually need two objects, LambdaThatDoesTheThingExecutionMetrics, and LambdaThatDoesTheThingFailureMetrics. We also want alarms, so we add LambdaThatDoesTheThingAlarms. And we want a dashboard, so we add LambdaThatDoesTheThingDashboard.
These variables by themselves might not be a big deal, but when you compose them together they create big chunks of code. At some point the long names actually obscure the intent of the code.
What if instead we encapsulate all of this in a function createLambdaThatDoesTheThing, and then remove the long prefix from all variables? Now we can clearly see that we are manipulating Metrics, Alarms and Dashboards. Sure, we need a bit more of context, but once we have that, the code is much easier to read and follow.
This infrastructure example might be a bit synthetic but I believe it represents how some real codebases are written.
(A brief aside on what I mean by inherent complexity: solving some partial differential equation or writing 'I must turn in my homework' one hundred times might both take the same amount of time, but the latter is less complex. Similarly, Dijkstra's algorithm to compute shortest paths might be easier to implement than a form to add a new employee to the database, but it is nevertheless more complex.)
When writing a program, one usually has to juggle lots of balls that move in simple ways. You can make a career out of writing programs that convert data between various mostly equivalent representations (table in database, packet on the network, Python object in RAM, pixels on the screen). I do not want to claim that this is easy: the details of what data ends up where, of how to deal with the not-quite-equivalent representations, of who is able to see what, etc., matter. But solving complex problems of which code to write does not mean that the code solves complex problems.
So it makes sense to use longer names. You have only little attention to spare on individual pieces and s.add(b) is more difficult to understand than submit_form.add(submit_button). Here, adding things to some container is a well-understood concept, you simply want to know which container and which things.
Conversely, when your problems have a high inherent complexity then you end up with a small number of balls that behave in a complicated manner. (If you have many complicated balls, the problem is too difficult to solve.) So you split up your explanation: first you describe the balls, then you assume that the reader knows what they are and give a succinct explanation of their interactions. (Flipping the order sometimes makes sense too.) It is clear that ax²+bx+c = (ax+b)x+c is easier to understand than equals(add(add(mul(parameter_quadratic, mul(variable, variable)), mul(parameter_linear, variable)), parameter_constant), add(mul(add(mul(parameter_quadratic, variable), parameter_linear), variable), parameter_constant)). The latter is simply too noisy, it does not allocate the symbols in an efficient manner to convey its meaning.
However, writing ax²+bx+c assumes that the reader is familiar with the meanings of a,b,c,x and with addition, multiplication and exponentiation. You probably are (else this would have made a poor example), but even if you were not, it would still make sense to explain these concepts first, to enable the succinct explanation.
Can someone explain this? What numbers?