命名的数字作为variables

我最近在高调的代码中看到过好几次,其中常量值被定义为variables,以值命名,然后只使用一次。 我想知道为什么这样做?

例如Linux源码(resize.c)

unsigned five = 5; unsigned seven = 7; 

例如C#.NET源代码(Quaternion.cs)

 double zero = 0; double one = 1; 

命名数字是非常糟糕的做法,总有一天需要改变,最后是unsigned five = 7

如果它有一些含义,给它一个有意义的名字。 “魔术数字” five对魔法数字5没有任何改进,更糟的是因为它实际上可能不等于5

这种东西一般来自于某些货物风格的编程风格指南,有人听说“魔法数字不好”,并不明白为什么,就禁止使用它。

有名的variables

为variables赋予适当的名称可以极大地阐明代码,例如

 constant int MAXIMUM_PRESSURE_VALUE=2; 

这提供了两个关键优势:

  • MAXIMUM_PRESSURE_VALUE可能在许多不同的地方使用,如果由于某种原因该值改变,你只需要在一个地方改变它。

  • 在哪里使用它立即显示function正在做什么,例如下面的代码显然检查压力是否危险地高:

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

命名不当的variables

然而,一切都有一个反驳的论点,你所展示的东西看起来非常像一个好主意,迄今为止,这是没有任何意义的。 将TWO定义为2不会添加任何值

 constant int TWO=2; 
  • TWO可以用在许多不同的地方,也许可以加倍,也许访问索引。 如果将来需要更改索引, 则不能将其更改为int TWO=3; 因为这会影响所有其他(完全无关)的方式,你已经使用两个,现在你会增加三倍,而不是倍增等
  • 在哪里使用它不会比使用“2”更多的信息。 比较以下两段代码:

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

    要么

     if (pressure>TWO){ //TWO means 2, 2 might be good, I still have no idea what happens here } 
  • 更糟糕的是(在这里似乎是这样) TWO可能不等于2,如果是这样的话,这是一种混淆forms,其目的是使代码不那么清晰:显然它实现了这一点。

通常的原因是禁止幻数的编码标准,但不把TWO算作幻数; 这当然是! 99%的时间你想使用一个有意义的variables名称,但在1%的时间使用TWO而不是2增益你什么(对不起,我的意思是ZERO )。

这个代码是受Java的启发,但意图是语言不可知的

简洁版本:

  • 一个持有five的常数是相当无用的。 不要在没有理由的情况下进行这些操作(有时你必须因为语法或者input规则)。
  • Quaternion.cs中的命名variables并不是绝对必要的,但是您可以使代码的可读性明显高于没有。
  • ext4 / resize.c中的命名variables根本不是常量。 他们是简洁的计数器。 他们的名字模糊了他们的function,但是这个代码实际上正确地遵循了项目的专门编码标准。

什么是Quaternion.cs?

这个很容易

在此之后:

 double zero = 0; double one = 1; 

代码是这样的:

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

没有局部variables,替代scheme是什么样的?

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

真是一团糟! 可读性肯定是在这里创造当地人的一面。 此外,我认为明确指出variables表示“我们仔细考虑过这个问题”,而不仅仅是写一个令人困惑的回归声明。

什么是resize.c?

在ext4 / resize.c的情况下,这些数字实际上并不是常量。 如果你遵循这些代码,你会发现它们是计数器,它们的值实际上是在一个while循环的多次迭代中改变的。

注意它们是如何初始化的 :

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

三等于一,是吧? 那是什么

看看实际情况是, update_backups通过引用函数ext4_list_backups来传递这些variables:

 /* * 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) 

他们是在多次调用过程中保留的计数器。 如果你看看函数体 ,你会发现它是在计数器上find下一个3,5或7次方 ,创build你在评论中看到的序列:1,3,5,7,9,25 ,27,&c。

现在,对于最奇怪的部分:variablesthree被初始化为1,因为3 0 = 1。然而,功率0是一个特例,因为它是唯一的时间3 x = 5 x = 7 x 。 尝试一下重写ext4_list_backups以使用初始化为1(3 ext4_list_backups 0 )的所有三个计数器,您将看到代码变得多繁琐。 有时,只要告诉调用者在评论中做一些有趣的事情(初始化列表为1,5,7)就更容易了。

那么, five = 5好的编码风格呢?

“五”是variablesfive在resize.c中代表的东西的一个好名字? 在我看来,这不是一种你应该在任何随机项目中效仿的风格。 简单的名字five并没有太多的关于variables的用途。 如果您正在开发一个Web应用程序,或者快速build立一个video聊天客户端的原型,并决定命名一个variablesfive ,那么您可能会为需要维护和修改代码的其他人创build令人头痛的问题。

但是, 这是一个例子,关于编程的概念并没有描绘出全貌 。 看看内核的编码风格文档 ,特别是关于命名的章节。

GLOBALvariables(仅在真正需要时才使用)需要具有描述性名称,全局函数也是如此。 如果你有一个函数来计算活动用户的数量,你应该调用“count_active_users()”或类似的,你不应该把它称为“cntusr()”。

LOCALvariables名称应该很短,并且重点。 如果你有一些随机的整数循环计数器,它应该可能被称为“我”。 调用它“loop_counter”是非生产性的,如果没有机会被误解。 同样,“tmp”可以是任何types的用于保存临时值的variables。

如果你害怕混淆你的局部variables名称,你还有另一个问题,这就是所谓的function – 生长激素 – 失衡综合征。 见第6章(function)。

其中一部分是C风格的编码传统。 其中一部分是有目的的社会工程。 很多内核代码都是敏感的东西,经过多次修改和testing。 由于Linux是一个大型的开源项目,对于贡献并不是很大的伤害 – 在大多数方面,更大的挑战是检查这些对质量的贡献。

调用variablesfive而不是nextPowerOfFive是阻止贡献者nextPowerOfFive他们不明白的代码的一种方式。 这是一个试图迫使你在尝试做出任何改变之前,逐行阅读你正在修改的代码。

内核维护者做出了正确的决定吗? 我不能说。 但这显然是一个有目的的举措。

我的组织有一定的编程指导,其中之一就是使用魔术数字…

例如:

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

这将被改为:

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

我们还有一个源文件,其格式为:-200,000 – > 200,000#

 #define MINUS_TWO_ZERO_ZERO_ZERO_ZERO_ZERO -200000 

这可以用来代替幻数,例如引用数组的特定索引时。

我想这是为了“可读性”而完成的。

数字0,1,…是整数。 在这里,'命名variables'给整数一个不同的types。 指定这些常量可能更合理(const unsigned five = 5;)

我用了几次类似的东西来写文件的值:

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

fwrite接受一个const指针,但是如果某个函数需要一个非const指针,那么最终会使用一个非constvariables。

PS:这总是让我想知道什么是零的大小。

你如何认为它只被使用过一次? 它是公开的,它可以在任何程序集中使用任意次数。

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

同样的事情适用于.Net框架decimal类。 这也暴露了这样的公共常量。

 public const decimal One = 1m; public const decimal Zero = 0m; 

当这些数字具有特殊含义时,数字通常会被赋予一个名字。

例如在四元数的情况下,身份四元数和单位长度四元数具有特殊的含义,并经常在特殊的情况下使用。 也就是说四元数(0,0,0,1)是一个四元数,所以定义它们而不是使用幻数是一种常见的做法。

例如

 // 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 

我的第一个编程工作之一是使用Basic的PDP 11。 Basic解释器将内存分配给所需的每个数字,所以每当程序提到0时,将使用一两个字节来存储数字0.当然,在那些日子里,内存比今天更受限制,所以它是重要的节约。

该工作场所的每个项目都是从

 10 U0%=0 20 U1%=1 

也就是说,那些忘记基本的人:

 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 

这些variables,按照当地惯例,从来没有任何其他的价值,所以它们是有效的常数。 他们允许在整个程序中使用0和1而不浪费任何内存。

Aaaaah,好日子!

有时候写的更具可读性:

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

代替:

 CircleArea=3.14*r*r; 

也许你会再次使用pi (你不确定,但你认为这是可能的后来或其他类如果他们是公开的)

然后如果你想改变pi=3.14pi=3.141596那就容易了。

还有一些像e=2.71Avogadro