为什么在代码中编写配置数据是个坏主意?

时间:2011-05-11 19:17:28

标签: perl language-agnostic

真实案例(来自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


提供解决一般问题的答案,而不仅仅是示例。

10 个答案:

答案 0 :(得分:27)

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

  • 没有配置更改应该带来破坏程序的风险。它当然不应该冒着打破编译阶段的风险。
  • 人们不应该编辑源代码来获得不同的配置。
  • 人们应该能够共享同一个应用程序,而无需使用一组通用设置,而是重新安装应用程序只是为了更改配置。
  • 应该允许人们创建多个不同的配置并批量运行,而无需编辑源。
  • 您应该能够在不更改代码的情况下在不同设置下测试您的应用程序。
  • 人们不应该学习如何编程才能使用你的工具。
  • 您应该只是松散地将配置数据结构绑定到信息源,以便以后更轻松地进行体系结构更改。
  • 您真的需要一个界面,而不是在应用程序级别直接访问。

我在掌握Perl 课程中总结了这一点,告诉人们编程的第一条规则就是创造一种情况,即你减少了工作,让人不管你。将配置放入代码中时,您将花费更多时间处理安装问题并响应破坏。除非你喜欢那种东西,否则给人们一种改变设置的方法,而不会给你带来更多的工作。

答案 1 :(得分:12)

$CONFIG{'unhappy_employee'} = `rm -rf /`

答案 2 :(得分:12)

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

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

他们可以共享配置。

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

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

最后,虽然 非常不可能p{...}等结构的实现会发生变化,但如果 更改,则可能无法继续运行

答案 3 :(得分:6)

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

答案 4 :(得分:6)

我很惊讶没有人提到的一个原因是测试。当配置在代码中时,您必须编写疯狂的,扭曲的测试才能安全地进行测试。你最终可以编写复制他们测试的代码的测试,这使测试几乎无用;主要是测试自己,可能漂移,难以维护。

与测试相关的是已提到的部署。当某些东西易于测试时,部署起来会很简单(好,更容易)。

答案 5 :(得分:3)

这里的主要问题是在可能有多种语言的环境中的可重用性。如果您的配置文件是A语言,那么您希望与语言B共享此配置,则必须进行一些重写。

如果你有更复杂的配置(例如apache配置文件)并且正在试图弄清楚如何处理数据结构中的潜在差异,这就更复杂了。如果你使用类似JSON,YAML等的东西,那么语言中的解析器将会知道如何根据语言的数据结构映射事物。

没有使用语言的一个主要缺点是你失去了将配置值设置为动态数据的潜力。

答案 6 :(得分:1)

理由1.美学。虽然没有人受到难闻气味的伤害,但人们倾向于努力摆脱它。

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

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

答案 7 :(得分:1)

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

读取和解释perl或ruby文件,以及包含配置数据的yml文件或xml文件。我选择yml是因为它比在代码中更容易,因为通过测试环境,开发,登台和生产进行分组,在代码中涉及更多代码。

作为旁注,XML完全与“易于观察”相矛盾。我觉得有趣的是XML配置广泛用于编译语言。

答案 8 :(得分:0)

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

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

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

此外,您必须要小心,这些值最终不会在某些时候进入您的提交历史记录。这可能会非常烦人。当你经历这一两次时,你再也不会尝试将配置放入代码中。

答案 9 :(得分:-1)

请原谅长代码清单。下面是我在许多系统中使用的一个方便的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;