将值硬编码到我们的应用程序中是个好主意吗?还是动态更改这些类型的值以防万一需要更改总是正确的做法?
pi
会改变……
将值硬编码到我们的应用程序中是个好主意吗?还是动态更改这些类型的值以防万一需要更改总是正确的做法?
pi
会改变……
Answers:
diameter = 2 * radius
还是diameter = RADIUS_TO_DIAMETER_FACTOR * radius
?确实,在某些特殊情况下,魔术数字可能是更好的解决方案。
diameter = radius << 1
吗?我想那也可能是diameter = radius << RADIUS_TO_DIAMETER_BITS_TO_SHIFT
。
diameter = radius.toDiameter()
到目前为止,我对这次问答感到奇怪的是,实际上没有人试图清楚地定义“硬编码”,或更重要的是,没有其他选择。
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
在错误处理程序中看到信息吗?没有理由。
我可能遗漏了几种情况,但我认为其中涵盖了大多数情况。
因此,是的,有时硬编码的东西是可以接受的做法。只是不要偷懒。这应该是一个有意识的决定,而不是普通的老旧草率代码。
将标识符分配给数字有多种原因。
这为我们提供了硬编码文字的标准。它们应该是不可变的,不难键入,仅在一个地方或上下文中出现,并且具有可识别的含义。例如,将0定义为ARRAY_BEGINNING或将1定义为ARRAY_INCREMENT没有意义。
作为其他答案的补充。尽可能对字符串使用常量。当然,你不想
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 enum {init_state=0, parse_state=1, evaluation_state=2, ... }
这取决于您考虑的硬编码。如果您尝试避免任何和所有硬编码的事情,那么您最终将进入软编码领域,并创建一个只有创建者才能管理的系统(这是最终的硬编码)
许多东西都在任何合理的框架中进行了硬编码,并且它们可以正常工作。也就是说,没有技术上的原因为什么我不能更改C#应用程序的入口点(静态void Main),但是硬编码不会对任何用户造成任何问题(偶尔出现SO问题除外)
我使用的经验法则是,在不影响整个系统状态的情况下,任何可以改变的事物都应该是可混淆的。
因此,恕我直言,不对没有变化的事物进行硬编码(pi,重力常数,数学公式中的常数-考虑球体的体积)是很愚蠢的。
同样,不硬编码将对您的系统产生影响的事物或过程,这在任何情况下都需要编程,这是很愚蠢的,也就是说,如果用户添加了动态字段到表单中(如果任何添加的字段需要维护开发人员执行),这是很浪费的。进去并编写一些脚本,使该东西能够正常工作。创建一些配置工具也很愚蠢(我在企业环境中已经见过几次),因此没有任何硬编码,但是,只有IT部门的开发人员才能使用它,而且比使用它要容易得多在Visual Studio中做到这一点。
因此,最重要的是,是否应该对事物进行硬编码是两个变量的函数:
将值硬编码到我们的应用程序中是个好主意吗?
仅当在规范中指定了值时才对值进行硬编码(在规范的最终版本中),例如HTTP OK响应将始终为200
(除非RFC中有更改),因此,您将看到(在我的某些代码中) )常量,例如:
public static final int HTTP_OK = 200;
否则,我将常量存储在属性文件中。
我之所以指定规范,是因为更改规范中的常量需要进行变更管理,在此过程中,涉众将审查变更并批准/不批准。它绝不会在一夜之间发生,并且需要数月/数年才能获得批准。不要忘记,许多开发人员都使用规范(例如HTTP),因此更改它意味着破坏数百万个系统。
我最近编写了一个MySQL函数,以正确计算两个经纬度对之间的距离。你不能做毕达哥拉斯;随着纬度向两极增加,经线之间的距离越来越近,因此涉及一些毛茸茸的三角帆。关键是,我是否对是否以英里为单位表示地球半径的值进行硬编码感到非常痛苦。
我最终还是这样做了,尽管事实是,例如月球上的经纬线距离很近。而且我的功能将严重低估木星上两点之间的距离。我发现进入我正在建设的网站的可能性很小。
我确实完全同意乔纳森(Jonathan)的观点,但由于所有规则都有例外...
“规范中的幻数:代码中的幻数”
基本上说,在合理尝试获得描述性上下文之后,规范中仍保留的任何魔术数字都应在代码中反映出来。如果代码中保留有魔术数字,则应尽一切努力隔离它们,并使其与起点明确相关。
我已经执行了一些接口合同,其中必须使用从数据库映射的值填充消息。在大多数情况下,映射是相当简单的,适合乔纳森(Jonathan)的一般准则,但是我遇到了目标消息结构简直糟透了的情况。必须在结构中传递的值的80%以上是由远距离系统的规范强制执行的常数。再加上消息结构庞大,这一事实使得必须填充很多这样的常数。在大多数情况下,它们没有提供含义或原因,只是说“在此处放M”或“在这里放4.10.53.10100.889450.4452”。我也没有尝试在所有注释旁边添加注释,否则将导致结果代码不可读。
就是说,当您考虑它时...几乎就是使它变得显而易见 ...
如果您要对地球重力常数的值进行硬编码,那么没人会在意。如果您对代理服务器的IP地址进行硬编码,那么您将遇到麻烦。
通常不,但是我认为值得注意的是,当您开始复制硬编码值时,您将遇到最多的问题。如果您不复制它(例如,在类的实现中仅使用它一次),那么不使用常量可能没问题。