命名的数字作为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.14
到pi=3.141596
那就容易了。
还有一些像e=2.71
, Avogadro
等