【问题标题】:Why is it a bad idea to write configuration data in code?为什么在代码中编写配置数据是个坏主意?
【发布时间】:2011-05-11 19:17:27
【问题描述】:

真实案例(来自caff)来举例说明短问题:

$CONFIG{'owner'} = q{Peter Palfrader};
$CONFIG{'email'} = q{peter@palfrader.org};
$CONFIG{'keyid'} = [ qw{DE7AAF6E94C09C7F 62AF4031C82E0039} ];
$CONFIG{'keyserver'} = 'wwwkeys.de.pgp.net';
$CONFIG{'mailer-send'} = [ 'testfile' ];

然后在代码中:eval `cat $config`,访问%CONFIG


提供列出一般问题的答案,而不仅仅是针对示例。

【问题讨论】:

  • 标记为 language-agnostic 因为这在动态语言中是可能的,而且原则上在所有语言中都是可能的。专门标记 Perl 因为它很乐意为您提供足够的众所周知的绳索,编写上面的这种方案非常容易。相关:stackoverflow.com/questions/746972/…
  • 您可以了解 JSON 是如何产生的。有人注意到您可以通过这种技术将数据传递到 Javascript 环境中,运行对包含数据的对象进行评估的代码。他们立即意识到这太过分了,因此他们指定了 Javascript 的“安全”子集,以便您可以使用熟悉的语法,结果与运行等效的 Javascript 完全相同,但没有机会在其中混合代码使用您的数据,因此结果更容易推理(没有副作用等)。
  • 在我的网站上,我们采用了一个名为 Chef 的工具进行部署 - 它的配置文件,称为“recipes”,具有复杂的语法,实际上它本身就是一种语言

标签: perl language-agnostic


【解决方案1】:

避免在代码中配置的原因有很多,我在Mastering Perl的配置章节中介绍了其中的一些。

  • 任何配置更改都不应有破坏程序的风险。它当然不应该承担破坏编译阶段的风险。
  • 人们不应该为了获得不同的配置而编辑源代码。
  • 人们应该能够共享同一个应用程序,而无需使用一组通用设置,而是重新安装应用程序只是为了更改配置。
  • 应该允许人们创建多个不同的配置并批量运行它们,而无需编辑源代码。
  • 您应该能够在不同的设置下测试您的应用程序,而无需更改代码。
  • 人们不应该必须学习如何编程才能使用您的工具。
  • 您应该只将配置数据结构松散地绑定到信息源,以使以后的架构更改更容易。
  • 您确实需要一个界面,而不是应用程序级别的直接访问。

我在我的精通 Perl 课程中总结了这一点,告诉人们编程的第一条规则是创造一种你做更少的工作而人们不理你的情况。当您将配置放入代码中时,您会花费更多时间来处理安装问题和响应损坏。除非您喜欢那种东西,否则请给人们一种方法来更改设置而不会增加您的工作量。

【讨论】:

  • "人们必须学习如何编程才能使用您的工具。" =~s/应该/不应该/
  • 我不同意这里列出的几乎所有你应该人们应该
  • 许多程序员不同意,这就是为什么大多数非程序员讨厌与程序员谈论任何事情的原因。
  • 内联配置的一个弱点是允许一个可移植的单文件解决方案。使用合理的默认值并允许它们在配置文件和/或 cmdline 中被覆盖,而 pro 没有实际意义。
  • 您仍然可以将非代码配置字符串用作默认文件。不过,正如您所说,您仍然需要默认值。
【解决方案2】:
$CONFIG{'unhappy_employee'} = `rm -rf /`

【讨论】:

  • 如果可以将其投入生产,那么您的审核过程已经很破碎了! QA 怎么会没有发现呢?
  • @Axeman:确实,有些商店的流程确实很糟糕。而如果这是一个只有少数人使用的内部维护工具,那么严格审查的机会就更渺茫了,这种事情的机会就会增加。一个人只需在服务器上的配置文件中添加一行,计划的作业在指定的时间运行,然后,BAM!!
  • 如果审核过程不好,那就不好了。我只需将相同的内容添加到未经审查的代码中即可。或system( $path_to_utility_in_config )
  • @Axeman:使用简单的文本文件进行配置,这甚至都不是问题。
  • @FrustratedWithFormsDesigner:当然可以,因为您仍然可以使用 path_to_browser=/bin/rm\n browser_flags=-rf\n default_url=/ 之类的配置属性来混淆它,然后输入 system( $konqueror_path, $browser_flags, $uninit_url || $default_url ) 或更棘手的东西。审核过程中的漏洞就是审核过程中的漏洞。
【解决方案3】:

这种方法的一个主要问题是您的配置不是很便携。如果在 Java 中构建了功能相同的工具,则必须重做加载配置。如果 Perl 和 Java 变体都使用简单的key=value 布局,例如:

owner = "Peter Palfrader"
email = "peter@peter@palfrader.org"
...

他们可以共享配置。

此外,在配置文件上调用eval 似乎会打开该系统以进行攻击。如果恶意的人想要造成一些破坏,他们可以在这个配置文件中添加什么?您是否意识到配置文件中的任意代码都会被执行?

另一个问题是它非常违反直觉(至少对我而言)。我希望某个配置加载器可以读取配置文件,而不是作为可运行的代码执行。这并不严重,但可能会使不习惯的新开发人员感到困惑。

最后,虽然 p{...} 等构造的实现极不可能发生变化,但如果它们确实发生变化,这可能无法继续发挥作用。

【讨论】:

  • 事实上,这在许多语言中都是有效的语法。在这种情况下,配置将通过“源”文件加载。
  • @Gaius:它可能是有效的语法,但这并不意味着它会被eval加载。 Java 中的 .properties 文件的格式为 key=value,但它不会像代码一样执行。如果我在 Perl 中执行此操作,我会尝试类似的方法,逐行解析文件并将数据存储在某种结构中,而不是像代码一样简单地执行它。
【解决方案4】:

将配置数据放在编译代码中是个坏主意,因为用户不能轻易更改它。对于脚本,只需确保它与其余部分完全分开并很好地记录它。

【讨论】:

  • 在没有缩短生命周期的情况下更改配置也是一个坏主意。
  • 如果您要在多台机器上部署脚本,那么您不希望在安装时在每台主机上都对其进行编辑。最好将关注点分开,将逻辑放在脚本中,将配置放在配置文件中。
【解决方案5】:

我很惊讶没有人提到的一个原因是测试。当 config 在代码中时,您必须编写疯狂、扭曲的测试才能安全地进行测试。您最终可能会编写重复他们测试的代码的测试,这使得测试几乎毫无用处;大多只是测试自己,可能会漂移,并且难以维护。

与测试密切相关的是前面提到的部署。当某些东西很容易测试时,它就会很容易(嗯,更容易)部署。

【讨论】:

    【解决方案6】:

    这里的主要问题是在可以使用多种语言的环境中的可重用性。如果您的配置文件是语言 A,那么您想与语言 B 共享此配置,您将不得不进行一些重写。

    如果您有更复杂的配置(例如 apache 配置文件)并试图弄清楚如何处理数据结构中的潜在差异,这将更加复杂。如果您使用 JSON、YAML 等内容,该语言中的解析器将知道如何根据该语言的数据结构映射事物。

    在语言中没有它们的一个主要缺点是,您失去了将配置值设置为动态数据的潜力。

    【讨论】:

      【解决方案7】:

      我同意蒂姆·安德森的观点。这里有人将代码中的配置混淆为配置不可配置。这已针对已编译的代码进行了更正。

      读取和解释 perl 或 ruby​​ 文件,以及带有配置数据的 yml 文件或 xml 文件。我选择 yml 是因为它在视觉上比在代码中更容易,因为它按测试环境、开发、暂存和生产进行分组,在代码中会涉及更多..代码。

      顺便说一句,XML 与“容易上手”完全矛盾。我发现 XML 配置被广泛用于编译语言很有趣。

      【讨论】:

        【解决方案8】:

        原因 1. 美学。虽然没有人受到难闻的气味的伤害,但人们往往会努力摆脱它。

        原因 2. 运营成本。对于一个 5 人的团队,这可能没问题,但是一旦您将开发人员/系统管理员分开,您必须聘请了解 Perl(即 $$$)的系统管理员,或者让开发人员访问生产系统(大 $$$)。

        更糟糕的是,当您突然需要配置引擎时,您将没有时间(也是 $$$)引入配置引擎。

        【讨论】:

        • R1 不是解释。 为什么/出于什么原因人们认为这是一种气味/不美观?
        【解决方案9】:

        我在编写的许多小脚本中配置的主要问题是它们通常包含我使用的服务的登录数据(用户名和密码或身份验证令牌)。后来,当脚本变大时,我开始对其进行版本控制,并希望将其上传到 github。

        所以在每次提交之前,我需要用一些虚拟值替换我的配置。

        $CONFIG{'user'} = 'username';
        $CONFIG{'password'} = '123456';
        

        您还必须小心,这些值最终不会在某个时候滑入您的提交历史记录。这会很烦人。当你经历过一两次之后,你就再也不会尝试将配置放入代码中了。

        【讨论】:

          【解决方案10】:

          请原谅冗长的代码清单。下面是我在许多系统中使用过的方便的 Conf.pm 模块,它允许您为不同的生产、登台和开发环境指定不同的变量。然后我构建我的程序以接受命令行上的环境参数,或者将此文件存储在源代码控制树之外,这样就永远不会被覆盖。

          AUTOLOAD 为变量检索提供了自动方法。

          # Instructions:
          # use Conf;
          # my $c = Conf->new("production");
          # print $c->root_dir;
          # print $c->log_dir;
          
          package Conf;
          use strict;
          our $AUTOLOAD;
          
          my $default_environment = "production";
          
          my @valid_environments  = qw(
              development
              production
          );
          
          #######################################################################################
          # You might need to change this.
          sub set_vars {
              my ($self) = @_;
          
              $self->{"access_token"} = 'asdafsifhefh';
          
              if ( $self->env eq "development" ) {
                 $self->{"root_dir"}       = "/Users/patrickcollins/Documents/workspace/SysG_perl";
                 $self->{"server_base"}    = "http://localhost:3000";
              }
          
              elsif ($self->env eq "production" ) {
                 $self->{"root_dir"}       = "/mnt/SysG-production/current/lib";
                 $self->{"server_base"}    = "http://api.SysG.com";
                 $self->{"log_dir"}        = "/mnt/SysG-production/current/log"
              }  else {
                      die "No environment defined\n";
              }
          
              #######################################################################################
              # You shouldn't need to configure this.
          
              # More dirs. Move these into the dev/prod sections if they're different per env.
              my $r = $self->{'root_dir'};
              my $b = $self->{'server_base'};
          
              $self->{"working_dir"} ||= "$r/working";
              $self->{"bin_dir"}     ||= "$r/bin";
              $self->{"log_dir"}     ||= "$r/log";
          
              # Other URLs. Move these into the dev/prod sections if they're different per env.
          
              $self->{"new_contract_url"}   = "$b/SysG-training-center/v1/contract/new";
              $self->{"new_documents_url"}  = "$b/SysG-training-center/v1/documents/new";
          
          }
          
          #######################################################################################
          # Code, don't change below here.
          
          sub new {
              my ($class,$env) = @_;
              my $self = {};
              bless ($self,$class);
          
              if ($env) {
                      $self->env($env);
              } else {
                      $self->env($default_environment);
              }
          
              $self->set_vars;
              return $self;
          }
          
          sub AUTOLOAD {
              my ($self,$val) = @_;
              my $type = ref ($self) || die "$self is not an object";
              my $field = $AUTOLOAD;
          
              $field =~ s/.*://;
          
              #print "field: $field\n";
          
              unless (exists $self->{$field} || $field =~ /DESTROY/ )
              {
                 die "ERROR: {$field} does not exist in object/class $type\n";
              }
          
              $self->{$field} = $val if ($val);
              return $self->{$field};
          
          }
          
          sub env {
              my ($self,$in) = @_;
              if ($in) {
                      die ("Invalid environment $in") unless (grep($in,@valid_environments));
                      $self->{"_env"} = $in;
              }
              return $self->{"_env"};
          }
          
          1;
          

          【讨论】:

          • 在讨论“为什么在代码中编写配置数据是个坏主意?”你已经提供了一个这样做的例子。这是一个很好的例子,说明为什么这是一个坏主意,因为这段代码是不透明的、不必要的复杂混乱。
          猜你喜欢
          • 2019-04-07
          • 1970-01-01
          • 1970-01-01
          • 2012-01-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-01-31
          相关资源
          最近更新 更多