Named Numbers as Variables

I saw this several times recently in a high profile, where constant values ​​are defined as variables named after the value, and then used only once. I wondered why this is done?

eg. Linux source (resize.c)

unsigned five = 5; unsigned seven = 7; 

eg. C # .NET Source (Quaternion.cs)

 double zero = 0; double one = 1; 
+48
c ++ c # conventions
Feb 26 '14 at 9:56
source share
10 answers

Naming numbers is a terrible practice, one day you will need to change something, and you will get unsigned five = 7 .

If it matters, give it a meaningful name. The magic number five does not improve the magic number 5 , it is worse because it cannot actually equal 5 .

This kind of thing usually occurs because of some culinary style programming style rules when someone heard that “magic numbers are bad” and forbade their use, not fully understanding why.

+83
Feb 26 '14 at 10:10
source share

Well defined variables

Providing proper names for variables can greatly clarify the code, for example

 constant int MAXIMUM_PRESSURE_VALUE=2; 

This provides two main advantages:

  • The value MAXIMUM_PRESSURE_VALUE can be used in many different places, if for any reason a change in this value needs to be changed in only one place.

  • In case of use, it immediately shows what the function does, for example, the following code obviously checks if pressure is dangerous:

     if (pressure>MAXIMUM_PRESSURE_VALUE){ //without me telling you you can guess there'll be some safety protection in here } 

Poorly named variables

Nevertheless, everyone has a counter argument, and what you have shown looks very good if you take the plan so far that it does not make sense. Defining TWO as 2 adds no value

 constant int TWO=2; 
  • The TWO value can be used in many different places, possibly for double things, perhaps for accessing the index. If in the future you will need to change the index, which you cannot just change to int TWO=3; , because it will affect all other (completely unrelated) ways that you used TWO, now you are instead doubling it, etc.
  • Where used, you will not get more information than if you used "2". Compare the following two codes:

     if (pressure>2){ //2 might be good, I have no idea what happens here } 

    or

     if (pressure>TWO){ //TWO means 2, 2 might be good, I still have no idea what happens here } 
  • Worse still (as it seems here) TWO may not equal 2, if it is, it is a form of obfuscation, where the intention is to make the code less clear: obviously, it achieves this.

A common reason for this is the coding standard, which prohibits magic numbers but does not consider TWO magic number; of course! 99% of the time when you want to use a meaningful variable name, but that 1% of the time using TWO instead of 2 gets nothing (sorry, I mean ZERO ).

this code is inspired by Java but designed for language agnostic

+48
Feb 26 '14 at 13:30
source share

Short version:

  • Standing five , which just keeps number five, is pretty worthless. Do not get around this for no reason (sometimes you have to because of syntax or input rules).
  • Named variables in Quaternion.cs are not strictly necessary, but you can make the code much more readable with them than without it.
  • Named variables in ext4 / resize.c are not constants. They are called counters. Their names obscure their functions a bit, but this code actually does correctly conform to the specialized coding standards of the project.

What happens to Quaternion.cs?

This is pretty easy.

Right after that:

 double zero = 0; double one = 1; 

The code does the following:

 return zero.GetHashCode() ^ one.GetHashCode(); 

Without local variables, what does the alternative look like?

 return 0.0.GetHashCode() ^ 1.0.GetHashCode(); // doubles, not ints! 

What a mess! Readability is definitely on the side of creating local people here. Moreover, I think that the explicit notation of the variables indicates “We thought carefully about this” much more clearly than just writing one confusing return statement.

What happens with resize.c?

In the case of ext4 / resize.c, these numbers are not constants at all. If you follow the code, you will see that they are counters, and their values ​​actually change over several iterations of the while loop.

Notice how they are initialized :

 unsigned three = 1; unsigned five = 5; unsigned seven = 7; 

Three are one, right? What does it mean?

Look what actually happens that update_backups passes these variables by reference to the ext4_list_backups function:

 /* * Iterate through the groups which hold BACKUP superblock/GDT copies in an * ext4 filesystem. The counters should be initialized to 1, 5, and 7 before * calling this for the first time. In a sparse filesystem it will be the * sequence of powers of 3, 5, and 7: 1, 3, 5, 7, 9, 25, 27, 49, 81, ... * For a non-sparse filesystem it will be every group: 1, 2, 3, 4, ... */ static unsigned ext4_list_backups(struct super_block *sb, unsigned *three, unsigned *five, unsigned *seven) 

These are counters that are stored for several calls. If you look at the body of the function , you will see that it is juggling with counters to find the next power 3, 5 or 7, creating the sequence that you see in the comment: 1, 3, 5, 7, 9, 25, 27, and c .

Now, for the strangest part: the three variable is initialized to 1 because 3 0 = 1. Power 0 is a special case, though, since it is the only time 3 x = 5 x = 7 x . Try your hand at rewriting ext4_list_backups to work with all three counters initialized to 1 (3 0 5 0 7 0 ), and you will see how much more cumbersome the code becomes. Sometimes it’s easier to just tell the caller to do something funky (initialize the list to 1, 5, 7) in the comments.

So, five = 5 good coding style?

Is "five" a good name for what the five variable represents in resize.c? In my opinion, this is not a style that you should imitate only in any random project that you accept. The simple name five does not indicate the purpose of the variable. If you are working on a web application or quickly prototyping a video chat client or something similar and decide to name the variable five , you are probably going to create headaches and annoyances for everyone who needs to maintain and modify your code.

However, this is one example where general programming information does not reflect the full picture . Take a look at the kernel coding style document , especially in the naming chapter.

GLOBAL variables (which will only be used if you really need them) have descriptive names, as well as global functions. If you have a function that counts the number of active users, you should call it "count_active_users ()" or the like, you should not call it "cntusr ()".

...

Local variable names must be short and precise. If you have some random integer counter, it should probably be called "i." Calling loop_counter is not productive if there is no chance of it being misunderstood. Similarly, "tmp" can be just about any type that is used to store a temporary value.

If you are afraid to mix the names of local variables, you have another problem called function-growth-hormone-imbalance syndrome. See Chapter 6 (Functions).

Part of this is the C-style coding tradition. Part of this is focused social engineering. A lot of kernel code is sensitive material, and it has been reviewed and tested many times. Since Linux is a large open source project, it is not very painful for contributions - in most cases, the biggest problem is to check these contributions for quality.

Calling this five variable instead of something like nextPowerOfFive is a way to discourage contributors from interfering with code that they don't understand. This is an attempt to get you to really read the code that you are modifying in detail, in turn, before trying to make any changes.

Did the kernel developers help with the right solution? I can not tell. But this is clearly a targeted move.

+30
Feb 27 '14 at 5:25
source share

My organization has certain programming guidelines, one of which is the use of magic numbers ...

eg:

 if (input == 3) //3 what? Elephants?....3 really is the magic number here... 

This will be changed to:

 #define INPUT_1_VOLTAGE_THRESHOLD 3u if (input == INPUT_1_VOLTAGE_THRESHOLD) //Not elephants :( 

We also have a source file with -200,000 → 200,000 #defined in the format:

 #define MINUS_TWO_ZERO_ZERO_ZERO_ZERO_ZERO -200000 

which can be used instead of magic numbers, for example, when referring to a specific index of an array.

I suppose this was done for readability.

+11
Feb 26 '14 at 10:04
source share

The numbers 0, 1, ... are integers. Here, the "named variables" give an integer of another type. It might be wiser to specify these constants (const unsigned five = 5;)

+7
Feb 26 '14 at 10:00
source share

I used something similar to writing values ​​to files a couple of times:

 const int32_t zero = 0 ; fwrite( &zero, sizeof(zero), 1, myfile ); 

fwrite takes a const pointer, but if any function requires a non-const pointer, you end up using a non-const variable.

PS: It always makes me wonder what could be the size of zero.

+7
Feb 26 '14 at 18:52
source share

How did you conclude that it is used only once? It is publicly available, it can be used any number of times from any assembly.

 public static readonly Quaternion Zero = new Quaternion(); public static readonly Quaternion One = new Quaternion(1.0f, 1.0f, 1.0f, 1.0f); 

The same goes for the .Net framework decimal class. which also provides public constants like this.

 public const decimal One = 1m; public const decimal Zero = 0m; 
+6
Feb 26 '14 at 10:05
source share

Numbers are often given a name when these numbers have a special meaning.

For example, in the case of quaternions, the identical quaternion and quaternion of unit length are of particular importance and are often used in a special context. Namely, a quaternion with (0,0,0,1) is an identical quaternion, so it is common practice to define them instead of using magic numbers.

for example

 // define as static static Quaternion Identity = new Quaternion(0,0,0,1); Quaternion Q1 = Quaternion.Identity; //or if ( Q1.Length == Unit ) // not considering floating point error 
+6
Feb 26 '14 at 10:06
source share

One of my first programming tasks was on PDP 11 using Basic. The main interpreter allocated memory for each required number, so every time the program is 0, bytes or two will be used to store the number 0. Of course, in those days the memory was much more limited than today, and therefore it was important to save.

Each program in this workplace began with:

 10 U0%=0 20 U1%=1 

That is, for those who have forgotten their Basic:

 Line number 10: create an integer variable called U0 and assign it the number 0 Line number 20: create an integer variable called U1 and assign it the number 1 

By local convention, these variables never contained any other value, so they were effective constants. They allowed the use of 0 and 1 in the entire program, without losing any memory.

Ahhh, good old days!

+2
Feb 27 '14 at 1:04
source share

several times this is more readable for writing:

 double pi=3.14; //Constant or even not constant ... CircleArea=pi*r*r; 

instead:

 CircleArea=3.14*r*r; 

and maybe you will use pi again (you are not sure, but consider that it is possible later or in other classes if they are public)

and then if you want to change pi=3.14 to pi=3.141596 , it's easier.

and some others, such as e=2.71 , Avogadro , etc.

+1
Feb 26 '14 at 10:12
source share



All Articles