什么是神奇数字?为什么有些人认为它们不好?

发布于 2024-07-05 11:16:50 字数 42 浏览 7 评论 0 原文

什么是神奇数字?

为什么许多程序员建议避免使用它们?

What is a magic number?

Why do many programmers advise that they be avoided?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(15

2024-07-12 11:16:50

幻数是文件格式或协议交换开头的字符序列。 这个数字可以作为健全性检查。

例子:
打开任何 GIF 文件,您都会在开头看到:GIF89。 “GIF89”是神奇的数字。

其他程序可以读取文件的前几个字符并正确识别 GIF。

危险在于随机二进制数据可能包含这些相同的字符。 但这是不太可能的。

至于协议交换,您可以使用它来快速识别当前传递给您的“消息”是否已损坏或无效。

魔法数字仍然有用。

A magic number is a sequence of characters at the start of a file format, or protocol exchange. This number serves as a sanity check.

Example:
Open up any GIF file, you will see at the very start: GIF89. "GIF89" being the magic number.

Other programs can read the first few characters of a file and properly identify GIFs.

The danger is that random binary data can contain these same characters. But it is very unlikely.

As for protocol exchange, you can use it to quickly identify that the current 'message' that is being passed to you is corrupted or not valid.

Magic numbers are still useful.

椒妓 2024-07-12 11:16:50

我认为这是对我的答案的回应你之前的问题。 在编程中,幻数是一个嵌入的数值常量,无需解释即可出现。 如果它出现在两个不同的位置,则可能会导致一个实例发生更改而另一个实例不变的情况。 出于这两个原因,在使用数值常量的地方之外隔离和定义数值常量非常重要。

I assume this is a response to my answer to your earlier question. In programming, a magic number is an embedded numerical constant that appears without explanation. If it appears in two distinct locations, it can lead to circumstances where one instance is changed and not another. For both these reasons, it's important to isolate and define the numerical constants outside the places where they're used.

何其悲哀 2024-07-12 11:16:50

在编程中,“幻数”是一个应该被赋予符号名称的值,但实际上却以文字形式滑入代码中,通常出现在多个位置。

它不好的原因与 SPOT(单点事实)好的原因相同:如果您想稍后更改此常量,则必须搜索代码以找到每个实例。 这也很糟糕,因为其他程序员可能不清楚这个数字代表什么,因此有“魔力”。

人们有时会进一步消除幻数,将这些常量移动到单独的文件中作为配置。 这有时很有帮助,但也会造成超出其​​价值的复杂性。

In programming, a "magic number" is a value that should be given a symbolic name, but was instead slipped into the code as a literal, usually in more than one place.

It's bad for the same reason SPOT (Single Point of Truth) is good: If you wanted to change this constant later, you would have to hunt through your code to find every instance. It is also bad because it might not be clear to other programmers what this number represents, hence the "magic".

People sometimes take magic number elimination further, by moving these constants into separate files to act as configuration. This is sometimes helpful, but can also create more complexity than it's worth.

金兰素衣 2024-07-12 11:16:50

使用幻数时没有提到的一个问题...

如果您有很多幻数,那么使用幻数的可能性相当大,您有两个不同的目的,其中恰好相同。

然后,果然,您需要更改该值......仅用于一个目的。

A problem that has not been mentioned with using magic numbers...

If you have very many of them, the odds are reasonably good that you have two different purposes that you're using magic numbers for, where the values happen to be the same.

And then, sure enough, you need to change the value... for only one purpose.

落花浅忆 2024-07-12 11:16:50

幻数也可以是具有特殊、硬编码语义的数字。 例如,我曾经见过一个系统,其中记录 ID > > 0 被正常处理,0 本身是“新记录”,-1 是“这是根”,-99 是“这是在根中创建的”。 0 和 -99 将导致 WebService 提供新的 ID。

这样做的不好之处在于,您会重复使用空格(用于记录 ID 的有符号整数的空格)来实现特殊功能。 也许您永远不想创建 ID 为 0 或负 ID 的记录,但即使不想,每个查看代码或数据库的人都可能会偶然发现这一点,并且一开始会感到困惑。 不用说,这些特殊值没有得到充分记录。

可以说,22、7、-12 和 620 也算作魔法数字。 ;-)

A magic number can also be a number with special, hardcoded semantics. For example, I once saw a system where record IDs > 0 were treated normally, 0 itself was "new record", -1 was "this is the root" and -99 was "this was created in the root". 0 and -99 would cause the WebService to supply a new ID.

What's bad about this is that you're reusing a space (that of signed integers for record IDs) for special abilities. Maybe you'll never want to create a record with ID 0, or with a negative ID, but even if not, every person who looks either at the code or at the database might stumble on this and be confused at first. It goes without saying those special values weren't well-documented.

Arguably, 22, 7, -12 and 620 count as magic numbers, too. ;-)

居里长安 2024-07-12 11:16:50

我总是以不同的方式使用术语“幻数”,作为存储在数据结构中的模糊值,可以通过快速有效性检查进行验证。 例如,gzip 文件包含 0x1f8b08 作为其前三个字节,Java 类文件以 0xcafebabe 开头,等等。

您经常会看到文件格式中嵌入了幻数,因为文件可能会相当混杂地发送,并且会丢失有关其创建方式的任何元数据。 然而,幻数有时也用于内存中的数据结构,例如 ioctl() 调用。

在处理文件或数据结构之前快速检查幻数可以让人们及早发出错误信号,而不是一路拖着可能冗长的处理来宣布输入完全是胡言乱语。

I've always used the term "magic number" differently, as an obscure value stored within a data structure which can be verified as a quick validity check. For example gzip files contain 0x1f8b08 as their first three bytes, Java class files start with 0xcafebabe, etc.

You often see magic numbers embedded in file formats, because files can be sent around rather promiscuously and lose any metadata about how they were created. However magic numbers are also sometimes used for in-memory data structures, like ioctl() calls.

A quick check of the magic number before processing the file or data structure allows one to signal errors early, rather than schlep all the way through potentially lengthy processing in order to announce that the input was complete balderdash.

尬尬 2024-07-12 11:16:50

值得注意的是,有时您确实需要在代码中使用不可配置的“硬编码”数字。 有许多著名的,其中包括用于优化平方反比的0x5F3759DF根算法。

在极少数情况下,我发现需要使用此类幻数,我将它们设置为代码中的常量,并记录它们的使用原因、它们的工作原理以及它们的来源。

It is worth noting that sometimes you do want non-configurable "hard-coded" numbers in your code. There are a number of famous ones including 0x5F3759DF which is used in the optimized inverse square root algorithm.

In the rare cases where I find the need to use such Magic Numbers, I set them as a const in my code, and document why they are used, how they work, and where they came from.

幻想少年梦 2024-07-12 11:16:50

用默认值初始化类顶部的变量怎么样? 例如:

public class SomeClass {
    private int maxRows = 15000;
    ...
    // Inside another method
    for (int i = 0; i < maxRows; i++) {
        // Do something
    }

    public void setMaxRows(int maxRows) {
        this.maxRows = maxRows;
    }

    public int getMaxRows() {
        return this.maxRows;
    }

在本例中,15000 是一个幻数(根据 CheckStyles)。 对我来说,设置默认值就可以了。 我不想这样做:

private static final int DEFAULT_MAX_ROWS = 15000;
private int maxRows = DEFAULT_MAX_ROWS;

这是否会使阅读变得更加困难? 在安装 CheckStyles 之前我从未考虑过这一点。

What about initializing a variable at the top of the class with a default value? For example:

public class SomeClass {
    private int maxRows = 15000;
    ...
    // Inside another method
    for (int i = 0; i < maxRows; i++) {
        // Do something
    }

    public void setMaxRows(int maxRows) {
        this.maxRows = maxRows;
    }

    public int getMaxRows() {
        return this.maxRows;
    }

In this case, 15000 is a magic number (according to CheckStyles). To me, setting a default value is okay. I don't want to have to do:

private static final int DEFAULT_MAX_ROWS = 15000;
private int maxRows = DEFAULT_MAX_ROWS;

Does that make it more difficult to read? I never considered this until I installed CheckStyles.

2024-07-12 11:16:50

@eed3si9n:我什至建议“1”是一个神奇的数字。 :-)

与幻数相关的一个原则是,代码处理的每个事实都应该声明一次。 如果您在代码中使用幻数(例如 @marcio 给出的密码长度示例),您很容易最终会重复该事实,并且当您对该事实的理解发生变化时,您就会遇到维护问题。

@eed3si9n: I'd even suggest that '1' is a magic number. :-)

A principle that's related to magic numbers is that every fact your code deals with should be declared exactly once. If you use magic numbers in your code (such as the password length example that @marcio gave, you can easily end up duplicating that fact, and when your understand of that fact changes you've got a maintenance problem.

土豪我们做朋友吧 2024-07-12 11:16:50

提取幻数作为常数的另一个优点是可以清楚地记录业务信息。

public class Foo {
    /** 
     * Max age in year to get child rate for airline tickets
     * 
     * The value of the constant is {@value}
     */
    public static final int MAX_AGE_FOR_CHILD_RATE = 2;

    public void computeRate() {
         if (person.getAge() < MAX_AGE_FOR_CHILD_RATE) {
               applyChildRate();
         }
    }
}

Another advantage of extracting a magic number as a constant gives the possibility to clearly document the business information.

public class Foo {
    /** 
     * Max age in year to get child rate for airline tickets
     * 
     * The value of the constant is {@value}
     */
    public static final int MAX_AGE_FOR_CHILD_RATE = 2;

    public void computeRate() {
         if (person.getAge() < MAX_AGE_FOR_CHILD_RATE) {
               applyChildRate();
         }
    }
}
回忆那么伤 2024-07-12 11:16:50

返回变量呢?

我特别发现实现存储过程时具有挑战性。

想象一下下一个存储过程(我知道语法错误,只是为了展示一个例子):

int procGetIdCompanyByName(string companyName);

如果公司的 ID 存在于特定的表中,它将返回该公司的 ID。 否则,返回-1。
不知怎的,这是一个神奇的数字。 到目前为止,我读过的一些建议表明,我真的必须做这样的设计:

int procGetIdCompanyByName(string companyName, bool existsCompany);

顺便说一句,如果公司不存在,它应该返回什么? 好的:它将把 existesCompany 设置为 false,但也会返回 -1。

另一个选择是创建两个单独的函数:

bool procCompanyExists(string companyName);
int procGetIdCompanyByName(string companyName);

因此第二个存储过程的先决条件是公司存在。

但我害怕并发,因为在这个系统中,一个公司可以由另一个用户创建。

顺便说一句,底线是:您如何看待使用那种相对已知且可以安全地判断某事物不成功或某事物不存在的“神奇数字”?

What about return variables?

I specially find it challenging when implementing stored procedures.

Imagine the next stored procedure (wrong syntax, I know, just to show an example):

int procGetIdCompanyByName(string companyName);

It return the Id of the company if it exists in a particular table. Otherwise, it returns -1.
Somehow it's a magic number. Some of the recommendations I've read so far says that I'll really have to do design somthing like that:

int procGetIdCompanyByName(string companyName, bool existsCompany);

By the way, what should it return if the company does not exists? Ok: it will set existesCompany as false, but also will return -1.

Antoher option is to make two separate functions:

bool procCompanyExists(string companyName);
int procGetIdCompanyByName(string companyName);

So a pre-condition for the second stored procedure is that company exists.

But i'm afraid of concurrency, because in this system, a company can be created by another user.

The bottom line by the way is: what do you think about using that kind of "magic numbers" that are relatively known and safe to tell that something is unsuccessful or that something does not exists?

≈。彩虹 2024-07-12 11:16:50

幻数是在代码中直接使用数字。

例如,如果您有(在 Java 中):

public class Foo {
    public void setPassword(String password) {
         // don't do this
         if (password.length() > 7) {
              throw new InvalidArgumentException("password");
         }
    }
}

这应该重构为:

public class Foo {
    public static final int MAX_PASSWORD_SIZE = 7;

    public void setPassword(String password) {
         if (password.length() > MAX_PASSWORD_SIZE) {
              throw new InvalidArgumentException("password");
         }
    }
}

它提高了代码的可读性并且更易于维护。 想象一下我在 GUI 中设置密码字段大小的情况。 如果我使用幻数,每当最大大小发生变化时,我都必须在两个代码位置进行更改。 如果我忘记了,这将导致不一致。

JDK 中充满了诸如 Integer、Character 和 Math 类中的示例。

PS:像 FindBugs 和 PMD 这样的静态分析工具可以检测代码中幻数的使用并提出重构建议。

A magic number is a direct usage of a number in the code.

For example, if you have (in Java):

public class Foo {
    public void setPassword(String password) {
         // don't do this
         if (password.length() > 7) {
              throw new InvalidArgumentException("password");
         }
    }
}

This should be refactored to:

public class Foo {
    public static final int MAX_PASSWORD_SIZE = 7;

    public void setPassword(String password) {
         if (password.length() > MAX_PASSWORD_SIZE) {
              throw new InvalidArgumentException("password");
         }
    }
}

It improves readability of the code and it's easier to maintain. Imagine the case where I set the size of the password field in the GUI. If I use a magic number, whenever the max size changes, I have to change in two code locations. If I forget one, this will lead to inconsistencies.

The JDK is full of examples like in Integer, Character and Math classes.

PS: Static analysis tools like FindBugs and PMD detects the use of magic numbers in your code and suggests the refactoring.

内心荒芜 2024-07-12 11:16:50

幻数是一个硬编码值,可能会在稍后阶段发生变化,但因此很难更新。

例如,假设您有一个页面在“您的订单”概览页面中显示最后 50 个订单。 50 是这里的神奇数字,因为它不是通过标准或约定设置的,而是您出于规范中概述的原因而编造的数字。

现在,您要做的就是在不同的地方拥有这 50 个订单 - 您的 SQL 脚本(从订单中选择前 50 个 *)、您的网站(您的最后 50 个订单)、您的订单登录信息(for (i = 0; i < 50; i++)) 以及可能的许多其他地方。

现在,当有人决定将 50 更改为 25 时会发生什么? 还是75? 还是153? 你现在必须把所有地方的50都换掉,而且你很可能会错过它。 查找/替换可能不起作用,因为 50 可能会用于其他用途,并且盲目地将 50 替换为 25 可能会产生一些其他不良副作用(即您的 Session.Timeout = 50 调用,该调用也设置为到 25,用户开始报告超时太频繁)。

另外,代码可能很难理解,即“if a < 50 then bla” - 如果您在复杂的函数中遇到这种情况,其他不熟悉代码的开发人员可能会感到困惑问自己“WTF 是 50???”

这就是为什么最好在 1 个位置放置这样不明确且任意的数字 - “const int NumOrdersToDisplay = 50”,因为这使得代码更具可读性(“if a ”) code>”,这也意味着您只需在 1 个明确定义的位置进行更改。

幻数适用的位置是通过标准定义的所有内容,即 SmtpClient.DefaultPort = 25TCPPacketSize = another (不确定这是否标准化)。此外,仅在 1 个函数中定义的所有内容都可能是可接受的,但这取决于上下文。

A Magic Number is a hard-coded value that may change at a later stage, but that can be therefore hard to update.

For example, let's say you have a Page that displays the last 50 Orders in a "Your Orders" Overview Page. 50 is the Magic Number here, because it's not set through standard or convention, it's a number that you made up for reasons outlined in the spec.

Now, what you do is you have the 50 in different places - your SQL script (SELECT TOP 50 * FROM orders), your Website (Your Last 50 Orders), your order login (for (i = 0; i < 50; i++)) and possibly many other places.

Now, what happens when someone decides to change 50 to 25? or 75? or 153? You now have to replace the 50 in all the places, and you are very likely to miss it. Find/Replace may not work, because 50 may be used for other things, and blindly replacing 50 with 25 can have some other bad side effects (i.e. your Session.Timeout = 50 call, which is also set to 25 and users start reporting too frequent timeouts).

Also, the code can be hard to understand, i.e. "if a < 50 then bla" - if you encounter that in the middle of a complicated function, other developers who are not familiar with the code may ask themselves "WTF is 50???"

That's why it's best to have such ambiguous and arbitrary numbers in exactly 1 place - "const int NumOrdersToDisplay = 50", because that makes the code more readable ("if a < NumOrdersToDisplay", it also means you only need to change it in 1 well defined place.

Places where Magic Numbers are appropriate is everything that is defined through a standard, i.e. SmtpClient.DefaultPort = 25 or TCPPacketSize = whatever (not sure if that is standardized). Also, everything only defined within 1 function might be acceptable, but that depends on Context.

北凤男飞 2024-07-12 11:16:50

看过幻数的维基百科条目?

您是否 有关魔幻数字参考的所有方式的详细信息。 这是关于魔幻数字是一种糟糕的编程实践的引述

术语“幻数”还指在源代码中直接使用数字而不进行解释的不良编程习惯。 在大多数情况下,这使得程序更难阅读、理解和维护。 尽管大多数指南都对数字 0 和 1 进行了例外处理,但最好将代码中的所有其他数字定义为命名常量。

Have you taken a look at the Wikipedia entry for magic number?

It goes into a bit of detail about all of the ways the magic number reference is made. Here's a quote about magic number as a bad programming practice

The term magic number also refers to the bad programming practice of using numbers directly in source code without explanation. In most cases this makes programs harder to read, understand, and maintain. Although most guides make an exception for the numbers zero and one, it is a good idea to define all other numbers in code as named constants.

他夏了夏天 2024-07-12 11:16:50

魔法数字VS。 符号常量:何时替换?

魔法:未知语义

符号常量 -> 提供正确的语义和正确的使用上下文

语义:事物的含义或目的。

“创建一个常量,根据含义命名,然后用它替换数字。” ——马丁·福勒

首先,神奇数字不仅仅是数字。 任何基本值都可以是“魔法”。 基本值是明显的实体,例如整数、实数、双精度数、浮点数、日期、字符串、布尔值、字符等。 问题不在于数据类型,而在于代码文本中出现的值的“神奇”方面。

我们所说的“魔法”是什么意思? 准确地说:通过“魔法”,我们打算指出代码上下文中值的语义(含义或目的); 它是未知的、不可知的、不清楚的或令人困惑的。 这就是“魔法”的概念。 当一个基本值的语义意义或存在目的可以从周围的上下文中快速、容易地知道、清晰和理解(不混淆)而无需特殊的辅助词(例如符号常量)时,它就不是魔法了。

因此,我们通过测量读码器从周围环境中了解、清楚和理解基本值的含义和目的的能力来识别幻数。 读者越不为人所知、越不清楚、越困惑,基本价值就越“神奇”。

基础

我们的魔法基本值有两种情况。 对于程序员和代码来说,只有第二个才是最重要的:

  1. 一个单独的基本值(例如数字),其含义是未知的、不可知的、不清楚的或令人困惑的。
  2. 上下文中的基本值(例如数字),但其含义仍然未知、不可知、不清楚或令人困惑。

“魔法”的一个首要依赖性是,单独的基本值(例如数字)没有众所周知的语义(如 Pi),但具有本地已知的语义(例如您的程序),该语义从上下文中并不完全清楚或可能被滥用在好的或坏的环境中。

大多数编程语言的语义不允许我们使用单独的基本值,除了(也许)作为数据(即数据表)。 当我们遇到“神奇数字”时,我们通常是在特定的上下文中这样做的。 因此,答案为

“我要用符号常量替换这个幻数吗?”

是:

“您能多快评估和理解以下内容的语义含义”
其上下文中的数字(其存在的目的)?”

有点神奇,但不完全是这样。

考虑到这个想法,我们可以很快看出像 Pi (3.14159) 这样的数字放在适当的上下文中时为什么不是一个“神奇数字” (例如 2 x 3.14159 x 半径或 2Pir),这里,数字 3.14159 是没有符号常量标识符的 Pi,

但是,我们通常用像 Pi 这样的符号常量标识符来替换 3.14159。数字的长度和复杂性 Pi 的长度和复杂性(加上对准确性的需求)通常意味着符号标识符或常量不太容易出错。将“Pi”识别为名称只是一种方便。 。

同时:回到牧场

抛开 Pi 这样的常见常数,让我们主要关注具有特殊含义的数字,但这些含义仅限于我们的软件系统的范围 数字可能是“2”(作为基本整数值)。

如果我单独使用数字 2,我的第一个问题可能是:“2”是什么意思? “2”本身的含义是未知的,在没有上下文的情况下是不可知的,使其用法不明确且令人困惑。 尽管由于语言语义的原因,我们的软件中不会出现只有“2”的情况,但我们确实希望看到“2”本身不带有特殊的语义或单独的明显目的。

让我们将单独的“2”放在以下上下文中:padding := 2,其中上下文是“GUI 容器”。 在这种情况下,2(作为像素或其他图形单位)的含义为我们提供了对其语义(含义和目的)的快速猜测。 我们可能会在这里停下来,说 2 在这种情况下是可以的,我们没有什么需要知道的。 然而,也许在我们的软件世界中这并不是故事的全部。 还有更多内容,但“padding = 2”作为上下文无法揭示它。

让我们进一步假设 2 作为我们程序中的像素填充在我们的系统中属于“default_padding”类型。 因此,编写指令padding = 2是不够的。 没有透露“默认”的概念。 只有当我写: padding = default_padding 作为上下文,然后在其他地方写: default_padding = 2 时,我才完全意识到 2 在我们的代码中更好、更完整的含义(语义和目的)。系统。

上面的例子非常好,因为“2”本身可以是任何东西。 只有当我们将理解的范围和领域限制在“我的程序”(其中 2 是“我的程序”的 GUI UX 部分中的 default_padding)时,我们才能最终真正理解“2”语境。 这里“2”是一个“神奇”数字,它在“我的程序”的 GUI UX 上下文中被分解为符号常量 default_padding,以便将其用作 default_padding 在封闭代码的更大上下文中可以快速理解。

因此,任何其含义(语义和目的)无法被充分且快速理解的基本值都是代替基本值(例如幻数)的符号常量的良好候选者。

进一步扩展

规模上的数字也可能具有语义。 例如,假设我们正在制作一款 D&D 游戏,其中有怪物的概念。 我们的怪物对象有一个名为 life_force 的功能,它是一个整数。 如果没有文字来提供意义,这些数字的含义是不可知或不清楚的。 因此,我们首先任意地说:

  • full_life_force: INTEGER = 10 -- 非常活着(并且未受伤)
  • minimum_life_force: INTEGER = 1 -- 几乎活着(非常受伤)
  • dead: INTEGER = 0 -- 死亡的
  • 不死生物:INTEGER = -1 --最小不死(几乎死了)
  • 僵尸:INTEGER = -10 -- 最大不死(非常不死)

从上面的符号常量,我们开始在脑海中了解活着、死亡和“不死”(以及可能的后果或后果)我们的 D&D 游戏中的怪物。 如果没有这些单词(符号常量),我们就只剩下 -10 .. 10 范围内的数字。 如果游戏的不同部分依赖于该数字范围对诸如 attack_elvesseek_magic_healing_potion

因此,在搜索和考虑替换“幻数”时,我们想要询问有关我们软件上下文中的数字的非常有目的的问题,甚至这些数字如何在语义上相互交互。

结论

让我们回顾一下我们应该问的问题:

如果...

  1. 基本值在您的软件世界中有特殊的含义或目的吗?
  2. 即使在适当的上下文中,特殊含义或目的是否可能是未知的、不可知的、不清楚的或令人困惑的?
  3. 正确的基本价值观在错误的背景下使用不当会产生不良后果吗?
  4. 不正确的基本价值观在正确的背景下是否会产生不良后果?
  5. 基本价值观与特定上下文中的其他基本价值观是否具有语义或目的关系?
  6. 一个基本值是否可以存在于代码中的多个位置,并且每个位置具有不同的语义,从而导致读者感到困惑?

检查代码文本中的独立清单常量基本值。 慢慢地、深思熟虑地询问关于此类值的每个实例的每个问题。 考虑一下你的答案的强度。 很多时候,答案并不是黑白分明的,而是有一些被误解的意义和目的、学习速度和理解速度。 还需要看看它如何与周围的软件机器连接。

最后,替换的答案是回答(在你的脑海中)读者建立联系的优势或劣势的衡量标准(例如“明白”)。 他们越快理解意义和目的,你的“魔力”就越少。

结论:只有当魔力大到足以导致难以检测由于混乱而产生的错误时,才用符号常量替换基本值。

Magic Number Vs. Symbolic Constant: When to replace?

Magic: Unknown semantic

Symbolic Constant -> Provides both correct semantic and correct context for use

Semantic: The meaning or purpose of a thing.

"Create a constant, name it after the meaning, and replace the number with it." -- Martin Fowler

First, magic numbers are not just numbers. Any basic value can be "magic". Basic values are manifest entities such as integers, reals, doubles, floats, dates, strings, booleans, characters, and so on. The issue is not the data type, but the "magic" aspect of the value as it appears in our code text.

What do we mean by "magic"? To be precise: By "magic", we intend to point to the semantics (meaning or purpose) of the value in the context of our code; that it is unknown, unknowable, unclear, or confusing. This is the notion of "magic". A basic value is not magic when its semantic meaning or purpose-of-being-there is quickly and easily known, clear, and understood (not confusing) from the surround context without special helper words (e.g. symbolic constant).

Therefore, we identify magic numbers by measuring the ability of a code reader to know, be clear, and understand the meaning and purpose of a basic value from its surrounding context. The less known, less clear, and more confused the reader is, the more "magic" the basic value is.

Basics

We have two scenarios for our magic basic values. Only the second is of primary importance for programmers and code:

  1. A lone basic value (e.g. number) from which its meaning is unknown, unknowable, unclear or confusing.
  2. A basic value (e.g. number) in context, but its meaning remains unknown, unknowable, unclear or confusing.

An overarching dependency of "magic" is how the lone basic value (e.g. number) has no commonly known semantic (like Pi), but has a locally known semantic (e.g. your program), which is not entirely clear from context or could be abused in good or bad context(s).

The semantics of most programming languages will not allow us to use lone basic values, except (perhaps) as data (i.e. tables of data). When we encounter "magic numbers", we generally do so in a context. Therefore, the answer to

"Do I replace this magic number with a symbolic constant?"

is:

"How quickly can you assess and understand the semantic meaning of the
number (its purpose for being there) in its context?"

Kind of Magic, but not quite

With this thought in mind, we can quickly see how a number like Pi (3.14159) is not a "magic number" when placed in proper context (e.g. 2 x 3.14159 x radius or 2Pir). Here, the number 3.14159 is mentally recognized Pi without the symbolic constant identifier.

Still, we generally replace 3.14159 with a symbolic constant identifier like Pi because of the length and complexity of the number. The aspects of length and complexity of Pi (coupled with a need for accuracy) usually means the symbolic identifier or constant is less prone to error. Recognition of "Pi" as a name is a simply a convenient bonus, but is not the primary reason for having the constant.

Meanwhile: Back at the Ranch

Laying aside common constants like Pi, let's focus primarily on numbers with special meanings, but which those meanings are constrained to the universe of our software system. Such a number might be "2" (as a basic integer value).

If I use the number 2 by itself, my first question might be: What does "2" mean? The meaning of "2" by itself is unknown and unknowable without context, leaving its use unclear and confusing. Even though having just "2" in our software will not happen because of language semantics, we do want to see that "2" by itself carries no special semantics or obvious purpose being alone.

Let's put our lone "2" in a context of: padding := 2, where the context is a "GUI Container". In this context the meaning of 2 (as pixels or other graphical unit) offers us a quick guess of its semantics (meaning and purpose). We might stop here and say that 2 is okay in this context and there is nothing else we need to know. However, perhaps in our software universe this is not the whole story. There is more to it, but "padding = 2" as a context cannot reveal it.

Let's further pretend that 2 as pixel padding in our program is of the "default_padding" variety throughout our system. Therefore, writing the instruction padding = 2 is not good enough. The notion of "default" is not revealed. Only when I write: padding = default_padding as a context and then elsewhere: default_padding = 2 do I fully realize a better and fuller meaning (semantic and purpose) of 2 in our system.

The example above is pretty good because "2" by itself could be anything. Only when we limit the range and domain of understanding to "my program" where 2 is the default_padding in the GUI UX parts of "my program", do we finally make sense of "2" in its proper context. Here "2" is a "magic" number, which is factored out to a symbolic constant default_padding within the context of the GUI UX of "my program" in order to make it use as default_padding quickly understood in the greater context of the enclosing code.

Thus, any basic value, whose meaning (semantic and purpose) cannot be sufficiently and quickly understood is a good candidate for a symbolic constant in the place of the basic value (e.g. magic number).

Going Further

Numbers on a scale might have semantics as well. For example, pretend we are making a D&D game, where we have the notion of a monster. Our monster object has a feature called life_force, which is an integer. The numbers have meanings that are not knowable or clear without words to supply meaning. Thus, we begin by arbitrarily saying:

  • full_life_force: INTEGER = 10 -- Very alive (and unhurt)
  • minimum_life_force: INTEGER = 1 -- Barely alive (very hurt)
  • dead: INTEGER = 0 -- Dead
  • undead: INTEGER = -1 -- Min undead (almost dead)
  • zombie: INTEGER = -10 -- Max undead (very undead)

From the symbolic constants above, we start to get a mental picture of the aliveness, deadness, and "undeadness" (and possible ramifications or consequences) for our monsters in our D&D game. Without these words (symbolic constants), we are left with just the numbers ranging from -10 .. 10. Just the range without the words leaves us in a place of possibly great confusion and potentially with errors in our game if different parts of the game have dependencies on what that range of numbers means to various operations like attack_elves or seek_magic_healing_potion.

Therefore, when searching for and considering replacement of "magic numbers" we want to ask very purpose-filled questions about the numbers within the context of our software and even how the numbers interact semantically with each other.

Conclusion

Let's review what questions we ought to ask:

You might have a magic number if ...

  1. Can the basic value have a special meaning or purpose in your softwares universe?
  2. Can the special meaning or purpose likely be unknown, unknowable, unclear, or confusing, even in its proper context?
  3. Can a proper basic value be improperly used with bad consequences in the wrong context?
  4. Can an improper basic value be properly used with bad consequences in the right context?
  5. Does the basic value have a semantic or purpose relationships with other basic values in specific contexts?
  6. Can a basic value exist in more than one place in our code with different semantics in each, thereby causing our reader a confusion?

Examine stand-alone manifest constant basic values in your code text. Ask each question slowly and thoughtfully about each instance of such a value. Consider the strength of your answer. Many times, the answer is not black and white, but has shades of misunderstood meaning and purpose, speed of learning, and speed of comprehension. There is also a need to see how it connects to the software machine around it.

In the end, the answer to replacement is answer the measure (in your mind) of the strength or weakness of the reader to make the connection (e.g. "get it"). The more quickly they understand meaning and purpose, the less "magic" you have.

CONCLUSION: Replace basic values with symbolic constants only when the magic is large enough to cause difficult to detect bugs arising from confusions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文