将值硬编码到我们的应用程序中是个好主意吗?


45

将值硬编码到我们的应用程序中是个好主意吗?还是动态更改这些类型的值以防万一需要更改总是正确的做法?


2
配置参数将对您有所帮助
Gopi

53
您永远不知道何时价值pi会改变……
Gabe

12
伙计,我猜像@gabe这样的人是这是“规则”的原因。如果您在代码中的20个地方重复3.14,然后发现您实际上需要更高的准确性,那么您将陷入困境。我没有意识到这不是显而易见的。
Bill K

17
@Bill有点不礼貌。@Gabe显然是在开玩笑,但除此之外,问题是关于硬编码与配置参数,而不是在多个位置使用常量与重复魔术数字。
戴维·康拉德

1
是的,有时硬编码可能是个好主意。请参阅有关“ Softcoding”反模式的Wikipedia文章。
user16764 2011年

Answers:


64

是的,但要使其明显

做:

  • 使用常量
  • 使用描述性变量名称

别:


44
这是清洁,diameter = 2 * radius还是diameter = RADIUS_TO_DIAMETER_FACTOR * radius?确实,在某些特殊情况下,魔术数字可能是更好的解决方案。
乔纳斯·普拉卡

5
我不同意这个答案。我倾向于认为编程就像是一个小说家。您通过代码讲故事,如果人们无法理解逻辑,我认为这会使您的代码毫无价值。这就是为什么经过深思熟虑的命名约定本质上是为了提高可读性。同样,没有充分的理由使用幻数。通过使用幻数,您可以消除方程式中的“为什么”并使之更难以理解。例如:“直径= 2 *半径”这两个分别是什么?这个“直径= RADIUS_TO_DIAMETER_FACTOR *半径”更有意义。
chrisw 2011年

9
直径= 2 *半径是根据高中数学计算得出的。不命名为“ 2”的原因是,要使其具有其他任何值,都需要更改物理定律或数学定律,或同时更改这两者。(另一方面,命名Pi或Plancks常数是易于阅读的好方法)。
quick_now 2011年

8
@乔纳斯:PFFT。当然是你的意思diameter = radius << 1吗?我想那也可能是diameter = radius << RADIUS_TO_DIAMETER_BITS_TO_SHIFT
蚂蚁

4
怎么样diameter = radius.toDiameter()
卡森·迈尔斯

26

到目前为止,我对这次问答感到奇怪的是,实际上没有人试图清楚地定义“硬编码”,或更重要的是,没有其他选择。

TL;博士:是的,这一个好的习惯进行硬编码值,但没有简单的规则来 ; 这完全取决于上下文。

这个问题确实将其范围缩小到了,我指的是魔术数字,但是关于它们是否是一个好主意的答案取决于它们的实际用途!

“硬编码”值的几个示例是:

  • 配置值

    每当我看到类似的陈述时,我都会畏缩command.Timeout = 600。为什么是600?谁决定的?它之前是否超时,有人将超时作为黑客提出,而不是解决潜在的性能问题?还是实际上是对处理时间的一些已知且有文件证明的期望?

    这些不应是幻数常量,而应将它们外部化到具有有意义名称的配置文件或数据库中,因为它们的最佳值主要或完全由应用程序所运行的环境确定。

  • 数学公式

    公式通常是非常静态的,因此内部常量值的性质并不是特别重要。金字塔的体积为(1/3)b * h。我们在乎1或3的来源吗?并不是的。先前的评论者正确地指出,这diameter = radius * 2可能比diameter = radius * RADIUS_TO_DIAMETER_CONVERSION_FACTOR- 更好,但这是错误的二分法。

    对于这种情况,您应该做的是创建一个函数。我不需要知道您怎么想到公式的,但是我仍然需要知道它的用途。如果不是上述任何书面的废话,我写的volume = GetVolumeOfPyramid(base, height)那么突然,一切都变得更加清晰,这是完全没有问题有幻数里面的函数(return base * height / 3),因为很明显,他们是公式的只是一部分。

    这里的关键当然是有短期简单的功能。这不适用于具有10个参数和30行计算的函数。在这种情况下,请使用函数组成或常量。

  • 域/业务规则

    该区域始终是灰色区域,因为它取决于确切的值。 在大多数情况下,正是这些特殊的幻数成为了常量的候选者,因为这使程序更易于理解而又不会使程序逻辑复杂化。考虑测试if Age < 19对比if Age < LegalDrinkingAge; 您可能可以不用常量就可以知道发生了什么,但是使用描述性标题会更容易。

    这可能成为功能抽象的候选人,例如function isLegalDrinkingAge(age) { return age >= 19 }。唯一的是,您的业务逻辑常常比这复杂得多,并且开始写出每个函数具有20-30个参数的数十个函数可能没有意义。如果没有基于对象和/或函数的明确抽象,则诉诸常量就可以了。

    需要注意的是,如果您在税务部门工作,编写起来会变得非常,非常繁重且毫无意义AttachForm(FORM_CODE_FOR_SINGLE_TAXPAYER_FILING_JOINTLY_FOR_DEPRECIATION_ON_ARMPIT_HAIR)。您不会这样做,AttachForm("B-46")因为每个曾经或曾经在那工作过的开发人员都会知道,“ B-46”是单个纳税人申报的表格代码等等-表单代码是域本身的一部分,它们永远不会改变,因此它们并不是真正的魔术数字。

    因此,您必须在业务逻辑中谨慎使用常量。基本上,您必须了解“魔术数字”实际上是否为魔术数字,或者它是否是域中众所周知的方面。如果它是域,那么除非有很大的机会改变它,否则就不要对其进行软编码。

  • 错误代码和状态标志

    这些绝不适合硬编码,因为曾经被罐子击中的任何可怜的混蛋Previous action failed due to error code 46都会告诉您。如果您的语言支持,则应使用枚举类型。否则,您通常将拥有一个充满常量的完整文件/模块,这些常量为特定错误类型指定有效值。

    永远不要让我return 42在错误处理程序中看到信息吗?没有理由。

我可能遗漏了几种情况,但我认为其中涵盖了大多数情况。

因此,是的,有时硬编码的东西是可以接受的做法。只是不要偷懒。这应该是一个有意识的决定,而不是普通的老旧草率代码。


感谢您的良好故障!-大多数人不认为我会添加“环境配置”的所有选项-我认为应避免使用这些选项(而不是硬编码),因为大多数数据应放在配置文件或数据库中。这遵循了“保持数据和逻辑分离”的原则,这是MVC或MVVM的主体。字符串TestServerVar =“ foo”; 字符串ProdServerVal =“ bar”;
m1m1k

7

将标识符分配给数字有多种原因。

  • 如果数字可能更改,则应该有一个标识符。 找到NUMBER_OF_PLANETS比搜索9的每个实例并考虑是否应将其更改为8要容易得多。很难事先预测。)
  • 如果数字很难以任何方式键入。 对于像pi这样的常量,最好给出一个最大精度的定义,而不是在多个位置(可能不准确)重新输入它。
  • 如果号码出现在不同的地方。 您不必在相邻的函数中使用45的两种用法,而想知道它们是否具有相同的含义。
  • 如果含义无法立即识别。 可以肯定地说,每个人都知道3.14159265 ...是什么。假设每个人都会认识到重力常数,甚至是pi / 2,这是不安全的。(“每个人”的确取决于软件的性质。可以期望系统程序员知道Unix许可位等的八进制表示形式。看看它是否为1.1或更高版本对于任何应该对此进行研究的人都是不言而喻的。)
  • 如果上下文无法识别。 每个人都知道一个小时中有60分钟,但是如果没有立即的迹象表明数量是时间值还是费率值,乘以60还是不清楚。

这为我们提供了硬编码文字的标准。它们应该是不可变的,不难键入,仅在一个地方或上下文中出现,并且具有可识别的含义。例如,将0定义为ARRAY_BEGINNING或将1定义为ARRAY_INCREMENT没有意义。


5

作为其他答案的补充。尽可能对字符串使用常量。当然,你不想

const string server_var="server_var";

但是你应该有

const string MySelectQuery="select * from mytable;";

(假设您实际上有一个查询,总是希望从特定表中获取所有结果)

除此之外,对于除0(通常)以外的任何数字使用常量。如果您需要255的权限位掩码,请不要使用

const int 8th_bit=255; //or some other obscure naming scheme that equates to 255.

改为使用

const int AllowGlobalRead=255;

当然,连同常量一起,知道何时使用枚举数。上述情况很可能合而为一。


typedef枚举{state_0 = 0,state_1 = 1,state_2 = 2,...} ...别笑了,我已经看到了。用湿鱼拍打那个人的头部!
quick_now 2011年

@当然,您当然想要更多类似的东西typedef enum {init_state=0, parse_state=1, evaluation_state=2, ... }
Earlz 2011年

6
THIS_NAMING_CONVENTION_IS_RECOMMENDED_FOR_CONSTANTS
StuperUser 2011年

4
对于字符串,您不仅需要常量。您希望将任何用户可见的字符串放入某种资源文件中(具体取决于您的平台),以便可以轻松地更改为另一种语言。
David Thornley

您可能还希望通过某种加密或混淆将与业务逻辑相关的字符串(例如SQL查询)粘贴到资源文件中。这将防止“好奇的”用户对逻辑(或数据库架构)进行逆向工程。
TMN

4

这取决于您考虑的硬编码。如果您尝试避免任何和所有硬编码的事情,那么您最终将进入软编码领域,并创建一个只有创建者才能管理的系统(这是最终的硬编码)

许多东西都在任何合理的框架中进行了硬编码,并且它们可以正常工作。也就是说,没有技术上的原因为什么我不能更改C#应用程序的入口点(静态void Main),但是硬编码不会对任何用户造成任何问题(偶尔出现SO问题除外)

我使用的经验法则是,在不影响整个系统状态的情况下,任何可以改变的事物都应该是可混淆的。

因此,恕我直言,不对没有变化的事物进行硬编码(pi,重力常数,数学公式中的常数-考虑球体的体积)是很愚蠢的。

同样,不硬编码将对您的系统产生影响的事物或过程,这在任何情况下都需要编程,这是很愚蠢的,也就是说,如果用户添加了动态字段到表单中(如果任何添加的字段需要维护开发人员执行),这是很浪费的。进去并编写一些脚本,使该东西能够正常工作。创建一些配置工具也很愚蠢(我在企业环境中已经见过几次),因此没有任何硬编码,但是,只有IT部门的开发人员才能使用它,而且比使用它要容易得多在Visual Studio中做到这一点。

因此,最重要的是,是否应该对事物进行硬编码是两个变量的函数:

  • 价值会改变吗
  • 值的变化将如何影响系统

4

将值硬编码到我们的应用程序中是个好主意吗?

当在规范中指定了值时才对值进行硬编码(在规范的最终版本中),例如HTTP OK响应将始终为200(除非RFC中有更改),因此,您将看到(在我的某些代码中) )常量,例如:

public static final int HTTP_OK = 200;

否则,我将常量存储在属性文件中。

我之所以指定规范,是因为更改规范中的常量需要进行变更管理,在此过程中,涉众将审查变更并批准/不批准。它绝不会在一夜之间发生,并且需要数月/数年才能获得批准。不要忘记,许多开发人员都使用规范(例如HTTP),因此更改它意味着破坏数百万个系统。


3
  • 如果该值可以更改,并且确实可能​​更改,则只要涉及的工作不超过预期的回报,就尽可能对其进行软编码
  • 有些值不能进行软编码;在极少数情况下,请遵循乔纳森(Jonathan)的准则

2

我注意到,只要您可以从代码中提取数据,它都会改善剩余的内容。您开始注意到新的重构并改进了代码的整个部分。

努力提取常量只是一个好主意,不要认为它是一些愚蠢的规则,而应将其视为更好地编码的机会。

最大的好处是,您可能会发现类似的常数是代码组中唯一的区别-将它们抽象到数组中有助于我将某些文件缩小了90%,并修复了许多复制和粘贴错误。

我还没有看到不提取数据的单一优势。


2

我最近编写了一个MySQL函数,以正确计算两个经纬度对之间的距离。你不能做毕达哥拉斯;随着纬度向两极增加,经线之间的距离越来越近,因此涉及一些毛茸茸的三角帆。关键是,我是否对是否以英里为单位表示地球半径的值进行硬编码感到非常痛苦。

我最终还是这样做了,尽管事实是,例如月球上的经纬线距离很近。而且我的功能将严重低估木星上两点之间的距离。我发现进入我正在建设的网站的可能性很小。


是的,可能吧,但是google.com/moon
-Residuum

1

好吧,这取决于您的语言是否已编译。如果未编译,则没什么大不了的,您只需编辑源代码,即使对于非程序员而言,源代码也会有些微妙。

如果使用编译语言进行编程,显然不是一个好主意,因为如果变量更改,则必须重新编译,如果要调整此变量,这将浪费大量时间。

您不需要制作一些滑块或界面来动态更改他的变量,但是您至少可以做的是一个文本文件。

例如,在我的ogre项目中,我总是使用ConfigFile类来加载已写入配置文件中的变量。


1

(至少在我看来)两次常量都可以的情况:

  1. 与其他无关的常数;您可以随时更改这些常量,而无需更改其他任何内容。示例:网格列的默认宽度。

  2. 绝对不变的,精确的,显而易见的常数,例如“每周的天数”。用常量days = weeks * 7替换几乎没有任何价值。7DAYS_PER_WEEK


0

我确实完全同意乔纳森(Jonathan)的观点,但由于所有规则都有例外...

“规范中的幻数:代码中的幻数”

基本上说,在合理尝试获得描述性上下文之后,规范中仍保留的任何魔术数字都应在代码中反映出来。如果代码中保留有魔术数字,则应尽一切努力隔离它们,并使其与起点明确相关。

我已经执行了一些接口合同,其中必须使用从数据库映射的值填充消息。在大多数情况下,映射是相当简单的,适合乔纳森(Jonathan)的一般准则,但是我遇到了目标消息结构简直糟透了的情况。必须在结构中传递的值的80%以上是由远距离系统的规范强制执行的常数。再加上消息结构庞大,这一事实使得必须填充很多这样的常数。在大多数情况下,它们没有提供含义或原因,只是说“在此处放M”或“在这里放4.10.53.10100.889450.4452”。我也没有尝试在所有注释旁边添加注释,否则将导致结果代码不可读。

就是说,当您考虑它时...几乎就是使它变得显而易见 ...


0

如果您要对地球重力常数的值进行硬编码,那么没人会在意。如果您对代理服务器的IP地址进行硬编码,那么您将遇到麻烦。


1
您可能需要更高的地球重力常数精度,因此对其进行多次硬编码可能会导致问题。
user281377 2011年

1
彼得·无人?来自赫尔曼的隐士?
戴维·康拉德

在大多数纬度和海拔高度,地球上的重力加速度几乎为9.81 m / s ^ 2(当然,如果您正在寻找地下石油或在北极上空射击ICBM,了解重力的变化对于小数点后的位置),而其他行星上的重力加速度是不同的数字,但是据我所知,重力常数在宇宙周围是恒定的。如果g是可变的,那么很多物理学将不得不改变。
Tangurena

0

通常不,但是我认为值得注意的是,当您开始复制硬编码值时,您将遇到最多的问题。如果您不复制它(例如,在类的实现中仅使用它一次),那么不使用常量可能没问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.