硬编码的字符串是否可以接受?
类似于 硬编码文字是否可以接受?,但我特别这里想到“魔法弦”。
在一个大型项目中,我们有一个配置选项表,如下所示:(
Name Value
---- -----
FOO_ENABLED Y
BAR_ENABLED N
...
数百个)。
常见的做法是调用通用函数来测试这样的选项:(
if (config_options.value('FOO_ENABLED') == 'Y') ...
当然,系统代码中的许多地方可能需要检查同一个选项。)
当添加新选项时,我正在考虑添加一个函数像这样隐藏“魔术字符串”:
if (config_options.foo_enabled()) ...
然而,同事们认为我太过分了,并反对这样做,更喜欢硬编码,因为:
- 这就是我们通常做的事情
- 时更容易看到发生了什么
它可以让调试代码 麻烦的是,我能明白他们的观点! 实际上,我们永远不会出于任何原因重命名选项,因此我能想到的函数的唯一优点是编译器会捕获任何拼写错误,例如 fo_enabled(),但不会捕获“FO_ENABLED”。
你怎么认为? 我是否错过了任何其他优点/缺点?
Similar to Is hard-coding literals ever acceptable?, but I'm specifically thinking of "magic strings" here.
On a large project, we have a table of configuration options like these:
Name Value
---- -----
FOO_ENABLED Y
BAR_ENABLED N
...
(Hundreds of them).
The common practice is to call a generic function to test an option like this:
if (config_options.value('FOO_ENABLED') == 'Y') ...
(Of course, this same option may need to be checked in many places in the system code.)
When adding a new option, I was considering adding a function to hide the "magic string" like this:
if (config_options.foo_enabled()) ...
However, colleagues thought I'd gone overboard and objected to doing this, preferring the hard-coding because:
- That's what we normally do
- It makes it easier to see what's going on when debugging the code
The trouble is, I can see their point! Realistically, we are never going to rename the options for any reason, so about the only advantage I can think of for my function is that the compiler would catch any typo like fo_enabled(), but not 'FO_ENABLED'.
What do you think? Have I missed any other advantages/disadvantages?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
如果我在代码中使用一次字符串,我通常不会担心将其设置为常量。
如果我在代码中使用一个字符串两次,我将考虑将其设为常量。
如果我在代码中使用一个字符串三次,我几乎肯定会将其设为常量。
If I use a string once in the code, I don't generally worry about making it a constant somewhere.
If I use a string twice in the code, I'll consider making it a constant.
If I use a string three times in the code, I'll almost certainly make it a constant.
将硬编码的 Y 检查限制在一个地方,即使这意味着为您的 Map 编写一个包装类。
可能看起来没问题,直到您有 100 个配置选项和 100 个方法(因此您可以在决定实施之前对未来应用程序的增长和需求做出判断)。 否则,最好有一类用于参数名称的静态字符串。
Restrict your hard coded Y check to one place, even if it means writing a wrapper class for your Map.
Might seem okay until you have 100 configuration options and 100 methods (so here you can make a judgement about future application growth and needs before deciding on your implementation). Otherwise it is better to have a class of static strings for parameter names.
我意识到这个问题很老了,但它出现在我的边缘。
AFAIC,无论是在问题中还是在答案中,都没有准确地识别出这里的问题。 暂时忘记“字符串编码”。
数据库有一个参考表,其中包含
config_options
。PK 是一个字符串。PK 有两种类型:
用户(和开发人员)看到和使用的有意义的标识符。 这些PK应该是稳定的,可以信赖的。
用户永远不应该看到的无意义的
Id
列,开发人员必须注意这些列并进行编码。 这些都是不能依赖的。使用有意义的 PK 的绝对值来编写代码是很平常的、正常的
IF CustomerCode = "IBM" ...
或IF CountryCode = "AUS"
等等。
您的参考表使用有意义的 PK。 在代码中引用这些文字字符串是不可避免的。 隐藏该值会使维护变得更加困难; 代码不再是字面意思; 你的同事是对的。 另外,还有额外的冗余功能来咀嚼循环。 如果文字中存在拼写错误,您很快就会在开发测试期间发现这一点,早在 UAT 之前。
数百个函数对应数百个文字是荒谬的。 如果您确实实现了一个函数,则规范化您的代码,并提供一个可用于数百个文字中任何一个的函数。 在这种情况下,我们又回到了裸字面量,并且可以省去该函数。
重点是,隐藏文字的尝试没有任何价值。
。
它不能被解释为“硬编码”,这是完全不同的东西。 我认为这就是您的问题所在,将这些构造识别为“硬编码”。 它只是字面上引用有意义的 PK。
现在,仅从任何代码段的角度来看,如果多次使用相同的值,则可以通过捕获变量中的文字字符串,然后在代码块的其余部分中使用该变量来改进代码。 当然不是一个函数。 但这是一个效率和良好实践的问题。 即使这样也不会改变效果
IF CountryCode = @cc_aus
I realise the question is old, but it came up on my margin.
AFAIC, the issue here has not been identified accurately, either in the question, or the answers. Forget about 'harcoding strings" or not, for a moment.
The database has a Reference table, containing
config_options
. The PK is a string.There are two types of PKs:
Meaningful Identifiers, that the users (and developers) see and use. These PKs are supposed to be stable, they can be relied upon.
Meaningless
Id
columns which the users should never see, that the developers have to be aware of, and code around. These cannot be relied upon.It is ordinary, normal, to write code using the absolute value of a meaningful PK
IF CustomerCode = "IBM" ...
orIF CountryCode = "AUS"
etc..
Your reference table uses meaningful PKs. Referencing those literal strings in code is unavoidable. Hiding the value will make maintenance more difficult; the code is no longer literal; your colleagues are right. Plus there is the additional redundant function that chews cycles. If there is a typo in the literal, you will soon find that out during Dev testing, long before UAT.
hundreds of functions for hundreds of literals is absurd. If you do implement a function, then Normalise your code, and provide a single function that can be used for any of the hundreds of literals. In which case, we are back to a naked literal, and the function can be dispensed with.
the point is, the attempt to hide the literal has no value.
.
It cannot be construed as "hardcoding", that is something quite different. I think that is where your issue is, identifying these constructs as "hardcoded". It is just referencing a Meaningfull PK literally.
Now from the perspective of any code segment only, if you use the same value a few times, you can improve the code by capturing the literal string in a variable, and then using the variable in the rest of the code block. Certainly not a function. But that is an efficiency and good practice issue. Even that does not change the effect
IF CountryCode = @cc_aus
我真的应该使用常量而不是硬编码的文字。
你可以说它们不会改变,但你可能永远不知道。 最好让它成为一种习惯。 使用符号常量。
I really should use constants and no hard coded literals.
You can say they won't be changed, but you may never know. And it is best to make it a habit. To use symbolic constants.
根据我的经验,此类问题掩盖了更深层次的问题:未能进行实际的 OOP 并遵循 DRY 原则。
简而言之,通过在
if
语句中内部每个操作的适当定义来捕获启动时的决策,然后丢弃两个config_options
和运行时测试。详细信息如下。
示例用法是:
这提出了一个明显的问题,“省略号中发生了什么?”,特别是考虑到以下陈述:
让我们假设每个 config_option 值确实对应于单个问题域(或实现策略)概念。
而不是这样做(在整个代码的各个位置重复):
我建议封装“可配置操作”的概念。
让我们举个例子(显然就像 FOO_ENABLED 一样假设......;-)您的代码必须以英制单位或公制单位运行。 如果 METRIC_ENABLED 为“true”,则将用户输入的数据从公制转换为英制以进行内部计算,并在显示结果之前转换回来。
定义一个接口:
它在一个位置标识与 METRIC_ENABLED 概念相关的所有行为。
然后编写执行这些行为的所有方式的具体实现:
并且
在启动时,不要加载一堆 config_options 值,而是初始化一组可配置操作,如下所示:(
其中上面的表达式
metricOption()
是您需要进行的任何一次性检查的替代品,包括查看 METRIC_ENABLED 的值;-)然后,无论代码会说什么:
重写 。
由于与该概念相关的所有实现细节现在都已干净地打包,因此与
METRIC_ENABLED
相关的所有未来维护都可以在一个地方完成 此外,运行时间的权衡是一个胜利; 与从 Map 获取 String 值并执行 String#equals 的开销相比,调用方法的“开销”是微不足道的。In my experience, this kind of issue is masking a deeper problem: failure to do actual OOP and to follow the DRY principle.
In a nutshell, capture the decision at startup time by an appropriate definition for each action inside the
if
statements, and then throw away both theconfig_options
and the run-time tests.Details below.
The sample usage was:
which raises the obvious question, "What's going on in the ellipsis?", especially given the following statement:
Let's assume that each of these
config_option
values really does correspond to a single problem domain (or implementation strategy) concept.Instead of doing this (repeatedly, in various places throughout the code):
I suggest encapsulating the concept of a "configurable action".
Let's take as an example (obviously just as hypthetical as
FOO_ENABLED
... ;-) that your code has to work in either English units or metric units. IfMETRIC_ENABLED
is "true", convert user-entered data from metric to English for internal computation, and convert back prior to displaying results.Define an interface:
which identifies in one place all the behavior associated with the concept of
METRIC_ENABLED
.Then write concrete implementations of all the ways those behaviors are to be carried out:
and
At startup time, instead of loading a bunch of
config_options
values, initialize a set of configurable actions, as in:(where the expression
metricOption()
above is a stand-in for whatever one-time-only check you need to make, including looking at the value of METRIC_ENABLED ;-)Then, wherever the code would have said:
rewrite it as:
Because all of the implementation details related to that one concept are now packaged cleanly, all future maintenance related to
METRIC_ENABLED
can be done in one place. In addition, the run-time trade-off is a win; the "overhead" of invoking a method is trivial compared with the overhead of fetching a String value from a Map and performing String#equals.我相信您提到的两个原因,即字符串中可能存在拼写错误,直到运行时才能检测到,以及名称更改的可能性(尽管很小)将证明您的想法是正确的。
最重要的是,你可以获取类型化函数,现在看来你只存储布尔值,如果你需要存储 int、字符串等怎么办?我宁愿使用 get_foo() 与类型,而不是 get_string("FOO") 或get_int(“FOO”)。
I believe that the two reasons you have mentioned, Possible misspelling in string, that cannot be detected until run time and the possibility (although slim) of a name change would justify your idea.
On top of that you can get typed functions, now it seems you only store booleans, what if you need to store an int, a string etc. I would rather use get_foo() with a type, than get_string("FOO") or get_int("FOO").
我认为这里有两个不同的问题:
I think there are two different issues here:
另一件需要考虑的事情是意图。 如果您所在的项目需要本地化,则硬编码字符串可能会不明确。 请考虑以下事项:
程序员的意图是明确的。 使用常量意味着该字符串不需要本地化。 现在看这个例子:
我们不太确定。 程序员是否真的不希望该字符串被本地化,或者程序员在编写这段代码时忘记了本地化?
Another thing to consider is intent. If you are on a project that requires localization hard coded strings can be ambiguous. Consider the following:
The programmer's intent is clear. Using a constant implies that this string does not need to be localized. Now look at this example:
Here we aren't so sure. Did the programmer really not want this string to be localized or did the programmer forget about localization while he was writing this code?
如果在整个代码中使用强类型配置类,我也更喜欢它。 使用正确命名的方法,您不会失去任何可读性。 如果您需要从字符串转换为另一种数据类型(十进制/浮点/整数),则无需在多个位置重复执行转换的代码,并且可以缓存结果,因此转换仅发生一次。 您已经具备了此基础,因此我认为不需要花太多时间就能习惯新的做事方式。
I too prefer a strongly-typed configuration class if it is used through-out the code. With properly named methods you don't lose any readability. If you need to do conversions from strings to another data type (decimal/float/int), you don't need to repeat the code that does the conversion in multiple places and can cache the result so the conversion only takes place once. You've already got the basis of this in place already so I don't think it would take much to get used to the new way of doing things.