Language agnostic 硬编码字符串是否可以接受?

Language agnostic 硬编码字符串是否可以接受?,language-agnostic,literals,string-literals,hard-coding,Language Agnostic,Literals,String Literals,Hard Coding,类似于,但我在这里特别想到“魔术弦” 在大型项目中,我们有如下配置选项表: Name Value ---- ----- FOO_ENABLED Y BAR_ENABLED N ... (数以百计) 通常的做法是调用泛型函数来测试如下选项: if (config_options.value('FOO_ENABLED') == 'Y') ... if (config_options.foo_enabled()) ... (当然,可能需要在系统代码的许多地方选

类似于,但我在这里特别想到“魔术弦”

在大型项目中,我们有如下配置选项表:

Name         Value
----         -----
FOO_ENABLED  Y
BAR_ENABLED  N
...
(数以百计)

通常的做法是调用泛型函数来测试如下选项:

if (config_options.value('FOO_ENABLED') == 'Y') ...
if (config_options.foo_enabled()) ...
(当然,可能需要在系统代码的许多地方选中此选项。)

在添加新选项时,我考虑添加一个函数来隐藏“神奇字符串”,如下所示:

if (config_options.value('FOO_ENABLED') == 'Y') ...
if (config_options.foo_enabled()) ...
然而,同事们认为我太过火了,反对这样做,他们更喜欢硬编码,因为:

  • 这是我们通常做的事
  • 这使得调试代码时更容易看到发生了什么
问题是,我明白他们的意思!实际上,我们永远不会出于任何原因重命名这些选项,因此我能想到的函数的唯一优点是编译器将捕获任何输入错误,如fo_enabled(),而不是“fo_enabled”


你觉得怎么样?我是否错过了任何其他优点/缺点?

我相信,您提到的两个原因,字符串中可能的拼写错误,在运行时之前无法检测到,以及名称更改的可能性(尽管很小),将证明您的想法是正确的


最重要的是,你可以得到类型化的函数,现在你似乎只存储布尔值,如果你需要存储一个int,一个字符串等等,我宁愿使用get_foo()和一个类型,而不是get_string(“foo”)或get_int(“foo”)。

我真的应该使用常量,而不是硬编码的文本


你可以说他们不会改变,但你可能永远也不知道。最好把它变成一种习惯。使用符号常量。

如果我在代码中只使用一次字符串,我通常不会担心在某个地方将其设置为常量

如果我在代码中使用两次字符串,我会考虑使它成为常量。< /P>


如果我在代码中使用字符串三次,我几乎肯定会将其设置为常量。

我认为这里有两个不同的问题:

  • 在当前的项目中,使用硬编码字符串的惯例已经很好地建立起来,因此所有参与该项目的开发人员都熟悉它。出于上面列出的所有原因,它可能是次优约定,但熟悉代码的每个人都可以查看它,本能地知道代码应该做什么。更改代码,使其在某些部分使用“新”功能,将使代码更难阅读(因为人们将不得不思考和记住新约定的功能),从而更难维护。但是我想,除非您能够快速编写转换脚本,否则将整个项目更改为新的约定可能会非常昂贵
  • 在一个新项目中,符号常量是IMO使用的方式,原因如下。特别是因为任何让编译器在编译时捕获错误的东西,如果不是这样的话,在运行时就会被人捕获,这是一个非常有用的约定

如果在整个代码中都使用强类型配置类,我也更喜欢它。使用正确命名的方法,您不会失去任何可读性。如果需要将字符串转换为另一种数据类型(decimal/float/int),则不需要重复在多个位置执行转换的代码,并且可以缓存结果,这样转换只发生一次。你已经有了这方面的基础,所以我不认为需要太多时间来适应新的做事方式

if (config_options.isTrue('FOO_ENABLED')) {...
}
将硬编码的Y检查限制在一个位置,即使这意味着为地图编写包装器类

if (config_options.isFooEnabled()) {...
}
在您拥有100个配置选项和100种方法之前,您似乎还可以(因此,在这里,您可以在决定实现之前对未来的应用程序增长和需求做出判断)。否则,最好为参数名使用一类静态字符串

if (config_options.isTrue(ConfigKeys.FOO_ENABLED)) {...
}

根据我的经验,这类问题掩盖了一个更深层次的问题:无法执行实际的OOP和遵循DRY原则

简言之,在启动时通过对
if
语句中的每个操作的适当定义捕获决策,然后丢弃
config\u选项和运行时测试

详情如下

示例用法为:

if (config_options.value('FOO_ENABLED') == 'Y') ...
这就提出了一个明显的问题,“省略号中发生了什么?”特别是考虑到以下陈述:

(当然,可能需要在系统代码的许多地方选中此选项。)

让我们假设这些
config_option
值中的每一个确实对应于一个问题域(或实现策略)概念

而不是这样做(在整个代码的各个地方重复):

  • 拿一根绳子(标签)
  • 查找其对应的其他字符串(值)
  • 将该值作为布尔等效值进行测试
  • 根据该测试,决定是否执行某些操作
  • 我建议封装“可配置操作”的概念

    让我们举一个例子(显然就像
    FOO_ENABLED
    …;-)一样夸张,您的代码必须使用英制单位或公制单位。如果启用的度量值为“真”,则将用户输入的数据从度量值转换为英语进行内部计算,并在显示结果之前转换回

    定义一个接口:

    public interface MetricConverter {
        double toInches(double length);
        double toCentimeters(double length);
        double toPounds(double weight);
        double toKilograms(double weight);
    }
    
    它在一个位置识别与启用的度量概念相关的所有行为

    然后写下实现这些行为的所有方法的具体实现:

    public class NullConv implements MetricConverter {
        double toInches(double length) {return length;}
        double toCentimeters(double length) {return length;}
        double toPounds(double weight)  {return weight;}
        double toKilograms(double weight)  {return weight;}
    }
    

    在启动时,不要加载一堆
    config_选项
    值,而是初始化一组可配置操作,如所示:

    MetricConverter converter = (metricOption()) ? new MetricConv() : new NullConv();
    
    (其中,上面的表达式
    metricOption()
    表示您需要进行的任何一次性检查,包括查看
    double length = converter.toInches(getLengthFromGui());
    // do some computation to produce result
    // ...
    displayResultingLengthOnGui(converter.toCentimeters(result));
    
    const string HELLO_WORLD = "Hello world!";
    print(HELLO_WORLD);
    
    print("Hello world!");