命名数字作为变量

Jac*_*539 48 c# c++ conventions

我最近在高调代码中已经看过几次,其中常量值被定义为变量,以值命名,然后仅使用一次.我想知道它为什么要完成?

例如Linux Source(resize.c)

unsigned five = 5;
unsigned seven = 7;
Run Code Online (Sandbox Code Playgroud)

例如C#.NET Source(Quaternion.cs)

double zero = 0;
double one = 1;
Run Code Online (Sandbox Code Playgroud)

MrZ*_*bra 83

命名数字是一种可怕的做法,总有一天需要改变,你最终会得到unsigned five = 7.

如果它有一些含义,请给它一个有意义的名字."神奇数字" five并没有超过魔法数字5,它更糟糕,因为它可能实际上并不相同5.

这种事情通常源于一些货物崇拜风格的编程风格指导,其中有人听说"魔术数字是坏的"并禁止他们使用而没有完全理解为什么.

  • 当你有*有意义的*名字时,`const`很有用.`const MaximumPrice = 7`很有用.`const seven = 7`没用. (21认同)
  • @SriramSakthivel我强烈反对它更具可读性!你更愿意看到什么:`if(x == OneThousandTwoHundredAndThirtyFour)`或`if(x == 1234)`? (5认同)
  • @SriramSakthivel它确实提出了一个问题:将常量`1`定义为'ONE`的好处是什么? (4认同)
  • 任何使用O和I作为变量名的人都应该得到他们得到的......:P (4认同)
  • 我开始想这个的原因是因为在 resize.c(linux 源代码)中,`unsigned three = 1;` 这看起来很奇怪,然后我又不知道他们在这个例子中的用途,虽然我说它们只使用一次,它们实际上是由另一个函数调用通过引用传递的,所以任何事情都可能发生,现在我更仔细地研究了代码,在这里有点道理。 (2认同)
  • 命名一个永远不会改变的常量没有错.比如'One``Zero`等.不要误解我的意思我只是指常数.无论如何,1永远是一个.但是如果稍后可能更改的实体没有为其命名.例如`Decimal.One`是在`decimal`类中定义的常量,因为它永远不会改变. (2认同)
  • @SriramSakthivel看到这个[答案](http://stackoverflow.com/a/745638/151019)为Decimal.One - 不仅仅是一个常数 (2认同)

Ric*_*gle 48

名字很好的变量

为变量赋予适当的名称可以大大澄清代码,例如

constant int MAXIMUM_PRESSURE_VALUE=2;
Run Code Online (Sandbox Code Playgroud)

这有两个关键优势:

  • 该值MAXIMUM_PRESSURE_VALUE可以在许多不同的地方使用,如果由于任何原因值变化,您只需要在一个地方更改它.

  • 在使用它时,它会立即显示函数正在执行的操作,例如,下面的代码显然会检查压力是否非常高:

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

命名不佳的变量

然而,一切都有一个反驳论点,你所展示的内容看起来非常像是一个好主意,到目前为止它没有任何意义.定义TWO为2不会添加任何值

constant int TWO=2;
Run Code Online (Sandbox Code Playgroud)
  • 该值TWO可以在许多不同的地方使用,也许可以加倍,也许是为了访问索引.如果将来你需要更改索引,你不能改变,int TWO=3;因为这会影响你使用TWO的所有其他(完全不相关)的方式,现在你将三倍而不是加倍等
  • 如果使用它,它不会给你提供比你刚使用"2"更多的信息.比较以下两段代码:

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

    要么

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

通常的原因是编码标准禁止幻数,但不算TWO作幻数; 这当然是!99%的时间你想要使用一个有意义的变量名称,但是在1%的时间里使用TWO而不是2获得任何东西(抱歉,我的意思是ZERO).

此代码的灵感来自Java,但旨在与语言无关


Ale*_*x P 30

精简版:

  • five只保持数字5的常数是无用的.不要无缘无故地去做这些(有时你必须因为语法或打字规则).
  • Quaternion.cs中的命名变量并不是绝对必要的,但是你可以证明代码的可读性比不使用它们要大得多.
  • ext4/resize.c中的命名变量根本不是常量.他们是简洁的柜台.他们的名字掩盖它们的功能一点,但这段代码实际上并不正确地遵循项目的专业编码标准.

Quaternion.cs发生了什么?

这个很容易.

就在这之后:

double zero = 0;
double one = 1;
Run Code Online (Sandbox Code Playgroud)

代码执行此操作:

return zero.GetHashCode() ^ one.GetHashCode();
Run Code Online (Sandbox Code Playgroud)

没有局部变量,替代方案是什么样的?

return 0.0.GetHashCode() ^ 1.0.GetHashCode(); // doubles, not ints!
Run Code Online (Sandbox Code Playgroud)

真是一团糟!可读性绝对是在这里创建本地人的一面.此外,我认为明确地命名变量表明"我们仔细考虑过这一点"比仅仅编写一个令人困惑的返回语句更清楚.

resize.c发生了什么?

在ext4/resize.c的情况下,这些数字实际上根本不是常量.如果您遵循代码,您将看到它们是计数器,它们的值实际上会在while循环的多次迭代中发生变化.

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

unsigned three = 1;
unsigned five = 5;
unsigned seven = 7;
Run Code Online (Sandbox Code Playgroud)

三等于一,是吗?那是什么意思?

看,实际发生的是update_backups通过引用函数传递这些变量ext4_list_backups:

/*
 * 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)
Run Code Online (Sandbox Code Playgroud)

它们是在多个呼叫过程中保留的计数器.如果你看一下函数体,你会看到它正在调整计数器以找到下一个3,5或7的幂,创建你在评论中看到的序列:1,3,5,7,9,25 ,27,&c.

现在,对于最奇怪的部分:变量three初始化为1,因为3 0 = 1.但是,功率0是一种特殊情况,因为它是唯一的时间3 x = 5 x = 7 x.您可以尝试重写ext4_list_backups与初始值1的所有三个计数器(3上班0,5 0,7 0),你会看到代码多少变得繁琐.有时,在评论中告诉调用者做一些时髦的事情(将列表初始化为1,5,7)更容易.

那么,five = 5编码风格是不是很好?

对于变量five在resize.c中表示的东西,"five"是一个好名字吗?在我看来,这不是你应该在任何随机项目中模仿的风格.简单名称five与变量的用途无关.如果您正在处理Web应用程序或快速原型化视频聊天客户端或某些事情并决定命名变量five,那么您可能会为需要维护和修改代码的任何其他人制造麻烦和烦恼.

然而,这是关于编程的一般性没有描绘出全貌的一个例子.看一下内核的编码风格文档,特别是关于命名的章节.

GLOBAL变量(仅在您确实需要它们时使用)需要具有描述性名称,全局函数也是如此.如果你有一个统计的活跃用户数的函数,你应该叫为"count_active_user()"或类似的,你应该不会把它称为"cntusr()".

...

LOCAL变量名称应该简短,并且要点.如果你有一些随机整数循环计数器,它应该被称为"i".如果没有机会被误解,那么称它为"loop_counter"是非生产性的.类似地,"tmp"可以是用于保存临时值的任何类型的变量.

如果你害怕混淆你的本地变量名,你还有另一个问题,就是所谓的功能 - 生长 - 激素 - 失衡综合症.见第6章(功能).

部分原因是C风格的编码传统.其中一部分是有目的的社会工程.很多内核代码都是敏感的东西,经过多次修改和测试.由于Linux是一个很大的开源项目,它并没有真正伤害到贡献 - 在大多数情况下,更大的挑战是检查这些贡献的质量.

调用该变量five而不是类似的东西nextPowerOfFive是一种阻止贡献者干涉他们不理解的代码的方法.在您尝试进行任何更改之前,这是一种尝试迫使您逐行详细阅读您正在修改的代码.

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

  • 我称之为*优秀*答案.那些目前拥有更多选票的人肯定没有错.他们给出了很好的建议和精细的推理.但他们只是反复直觉的东西,证实了OP的怀疑.它们是"不使用魔法数字"之上的一级反流.这个答案(适当的归功于对主要问题的一些评论,这些评论是朝着这个方向前进的)深入研究并真正解决了OP提供的例子. (5认同)

Jos*_*ron 11

我的组织有一些编程指南,其中一个是魔术数字的使用......

例如:

if (input == 3) //3 what? Elephants?....3 really is the magic number here...
Run Code Online (Sandbox Code Playgroud)

这将改为:

#define INPUT_1_VOLTAGE_THRESHOLD 3u 
if (input == INPUT_1_VOLTAGE_THRESHOLD) //Not elephants :(
Run Code Online (Sandbox Code Playgroud)

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

#define MINUS_TWO_ZERO_ZERO_ZERO_ZERO_ZERO -200000
Run Code Online (Sandbox Code Playgroud)

可用于代替幻数,例如在引用数组的特定索引时.

我想这已经为"可读性"做了.

  • 第一个例子非常适合显示为什么要"命名"一个数字.第二个虽然?我无法看到这对任何人都有帮助! (12认同)
  • 呃,什么?`TWELVE_VOLTS`是逐个项目定义的?那么`TWELVE_VOLTS`实际上可能代表13V?如果是这样,那太可怕了.什么会使你的代码更具可读性(并且更不容易出错)的是,如果你的*变量*表示的单位是明确的; 例如`if(inputVolts == 12)`. (9认同)
  • 这个例子令人困惑.没有神奇数字的两个重要原因是可读性和轻松安全地改变代码的能力."十二伏"与"十二"描述性差,因此第一个问题尚未得到满足.期望"十二伏"意味着十二伏以外的任何东西都是奇怪的,所以第二个问题也没有得到解决.像这样的"常数"和幻数一样好(事实上更糟糕,因为它增加了复杂性).它应该被称为`inputVoltage` - 它将我们带回原始问题. (4认同)
  • @Joshpbarron它的错误在于它根本没有任何用途.符号"2"替换为符号"TWO",它们具有完全相同的含义,因此100%等效.另一方面,你的第一个例子是一个确实应该*做什么来避免魔术数字的例子; 为值赋予有意义的名称 (4认同)
  • `ONE`和`1`一样神奇 (4认同)
  • "我们还有一个包含-200,000的源文件 - > 200,000 #defined .."这在多个级别上都是错误的.无论谁想到这一点,无论是谁用过它,我都会解雇. (3认同)
  • 在一个项目中有'TWELVE_VOLTS`而在另一个项目中有'THIRTEEN_VOLTS`是愚蠢的; 这些常量没有语义含义.在一个理智的系统中,你有一个名为`INPUT_VOLTAGE`的常量,并为不同的项目设置不同的.你的答案中有一些讽刺,但我担心有些人会误解它并认为你提倡这种可怕的做法. (2认同)
  • 这太可怕了.正确的方法是将变量命名为一个以电压为单位的数量,或/并定义一个电压整数类型(对于像C这样的低级语言,这最后一个可能不合理,但是例如在C#中你可以公平地说很容易让`ToString()`方法在电压表示的末尾附加一个'V`,你不会得到比这更多的自我记录).我甚至不会对`MINUS_TWO_ZERO_ZERO_ZERO_ZERO_ZERO`宏发表评论,我在阅读它时死了一点. (2认同)
  • @RichardTingle当它没有达到你所期望的效果时,它甚至可能更具魔力. (2认同)

小智 7

数字0,1,...是整数.这里,'命名变量'给整数赋予不同的类型.指定这些常量可能更合理(const unsigned five = 5;)

  • 我明白了,所以不要写`return 1f;`或`return 1L;`你要浮动一个= 1; 返回一个;`或`长一个= 1; 返回一个;` (3认同)

Die*_*hez 7

我曾经使用类似于几次的东西来为文件写入值:

const int32_t zero = 0 ;

fwrite( &zero, sizeof(zero), 1, myfile );
Run Code Online (Sandbox Code Playgroud)

fwrite接受一个const指针,但是如果某个函数需要一个非const指针,你最终会使用一个非const变量.

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


Sri*_*vel 6

你是如何得出它只使用一次的结论?它是公开的,可以在任何组件中使用任意次数.

public static readonly Quaternion Zero = new Quaternion();
public static readonly Quaternion One = new Quaternion(1.0f, 1.0f, 1.0f, 1.0f);
Run Code Online (Sandbox Code Playgroud)

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

public const decimal One = 1m;
public const decimal Zero = 0m;
Run Code Online (Sandbox Code Playgroud)

  • 我得出了这个结论,因为我正在查看代码.`private static int GetIdentityHashCode(){//此代码只调用一次.double zero = 0; double one = 1; // return zero.GetHashCode()^ zero.GetHashCode()^ zero.GetHashCode()^ one.GetHashCode(); //但是这个表达式可以简化,因为前两个哈希码会被取消.return zero.GetHashCode()^ one.GetHashCode(); }` (2认同)
  • @ concept3d你可以做到这一点,尝试一下:) (2认同)

con*_*t3d 6

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

例如,在四元数情况下,标识四元数和单位长度四元数具有特殊含义,并且经常在特殊上下文中使用.即具有(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
Run Code Online (Sandbox Code Playgroud)