检查Perl函数参数值得吗?

时间:2010-02-24 23:55:44

标签: performance perl types moose typechecking

有很多关于MooseX::Method::Signatures的嗡嗡声,甚至在此之前,Params::Validate等模块设计用于检查方法或函数的每个参数。我正在考虑使用前者用于我未来的所有Perl代码,无论是个人还是我的工作地点。但我不确定这是否值得付出努力。

我正在考虑我之前看过(和写过)的所有Perl代码,之后不执行此类检查。我很少看到一个模块这样做:

my ($a, $b) = @_;
defined $a or croak '$a must be defined!';
!ref $a or croak '$a must be a scalar!";
...
@_ == 2 or croak "Too many arguments!";

也许是因为没有某种辅助模块它只是太多的工作,但也许是因为在实践中我们不会向函数发送多余的参数,并且我们不会将arrayref发送到期望标量的方法 - 或者如果我们这样做,我们有use warnings;,我们很快就会听到它 - duck typing方法。

Perl类型检查是否值得追求性能,或者它的优势主要体现在编译的强类型语言(如C或Java)中?

我对任何有使用这些模块并且已经看到使用它们的好处(或没有)的Perl编写经验的人的答案感兴趣;如果您的公司/项目有任何与类型检查有关的政策;以及类型检查和性能方面的任何问题。

更新:我最近读了一篇关于这个主题的有趣文章,名为Strong Testing vs. Strong Typing。忽略轻微的Python偏见,它基本上表明在某些情况下类型检查可能会令人窒息,即使你的程序通过了类型检查,也无法保证正确性 - 正确的测试是确保的唯一方法。

9 个答案:

答案 0 :(得分:14)

如果你确定一个论点正是你所需要的那么重要,那就值得了。性能仅在您已经正常运行时才有意义。无论您获得错误答案或核心转储的速度有多快都无关紧要。 :)

现在,这听起来像是一个愚蠢的事情,但考虑一些不是的情况。我真的关心@_这里的内容吗?

sub looks_like_a_number { $_[0] !~ /\D/ }
sub is_a_dog            { eval { $_[0]->DOES( 'Dog' ) } }

在这两个例子中,如果参数不是你所期望的,你仍然会得到正确的答案,因为无效的参数不会通过测试。有些人认为这很难看,我可以看到他们的观点,但我也认为另类是丑陋的。谁赢了?

然而,有时你需要保护条件,因为你的情况并非如此简单。您必须将数据传递给下一件事,可能会将它们放在某些范围或某些类型中,并且不会优雅地失败。

当我考虑保护条件时,我会想到如果输入不好以及我对失败多少关心会发生什么。我必须根据每种情况的要求来判断。我知道这很糟糕,但我倾向于喜欢它比束缚和纪律的方法更好,你必须经历所有的混乱,即使它无关紧要。

我害怕Params::Validate,因为它的代码通常比我的子程序长。 Moose的东西非常有吸引力,但是你必须意识到这是你宣布你想要的东西的方式,你仍然可以得到你可以手工制作的东西(你只需要看到它或做它)。我讨厌Perl最大的问题是缺少可选的方法签名,这是Perl 6和Moose中最具吸引力的功能之一。

答案 1 :(得分:7)

我基本上同意布莱恩。您需要担心多少方法的输入在很大程度上取决于您关注的是:a)有人会输入错误的数据,以及b)错误的数据会破坏方法的目的。我还要补充说,外部和内部方法之间存在差异。你需要对公共方法更加勤奋,因为你向你的班级消费者做出承诺;相反,你可以对内部方法不那么勤奋,因为你对访问它的代码有更大的(理论上的)控制权,如果出现问题,你只能责怪自己。

MooseX :: Method :: Signatures是一种优雅的解决方案,可以添加一种简单的声明方式来解释方法的参数。方法::签名::简单和参数::验证很好但缺乏我觉得最有吸引力的功能之一:类型系统。我已经使用MooseX :: Declare和扩展MooseX :: Method :: Signatures进行了几个项目,我发现编写额外检查的标准非常小,几乎是诱人的。

答案 2 :(得分:5)

是的,它值得 - 防御性编程是值得的事情之一。

答案 3 :(得分:4)

我看到的反驳是,检查每个函数调用上的参数是多余的,浪费了CPU时间。该论点的支持者赞成一种模型,其中所有传入数据在首次进入系统时都经过严格检查,但内部方法没有参数检查,因为它们只能通过代码调用,这些代码将传递已经通过系统检查的数据。边界,所以假设它仍然有效。

理论上,我真的很喜欢这样的声音,但我也可以看到,如果有人使用系统(或系统需要增长以允许使用),它会像一个纸牌屋一样容易掉落初始验证边界建立时无法预见。所需要的只是对内部功能的一次外部调用,并且所有投注均已关闭。

实际上,我现在正在使用Moose,Moose并没有真正为您提供在属性级别绕过验证的选项,加上MooseX :: Declare处理和验证方法参数,而不是展开@_ by手,所以这几乎没有实际意义。

答案 4 :(得分:2)

我想在这里提两点。 第一个是测试,第二个是性能问题。

1)测试

您提到测试可以做很多事情,测试是唯一的方法 确保您的代码正确无误。一般来说,我会说这是 绝对正确。但是测试本身只能解决一个问题。

如果你写一个模块你有两个问题或者说两个不同 那些使用你的模块的人。

您作为开发人员和使用您的模块的用户。测试有助于 首先,你的模块是正确的,并做正确的事情,但事实并非如此 帮助只使用您模块的用户。

对于后者,我有一个例子。我用Moose写了一个模块 和其他一些东西,我的代码总是在Segmentation故障中结束。 然后我开始调试我的代码并搜索问题。我在附近度过 4小时的时间找到错误。最后问题是我有 使用Moose和Array Trait。我使用了“地图”功能而我没有 提供子程序功能,只是一个字符串或其他东西。

当然这是我的一个绝对愚蠢的错误,但我花了很长时间 调试它。最后只是检查参数的输入 一个subref将花费开发人员10秒的时间,并将花费我 并且可能还有很多时间。

我也知道其他例子。我在界面上编写了一个REST客户端 完全与驼鹿OOP。最后你总是找回对象,你 可以更改属性但确定它没有调用REST API 你做的每一个改变。相反,你改变你的价值观,最终你 调用一个传递数据的update()方法,并更改值。

现在我有一个用户写了:

$obj->update({ foo => 'bar' })

当然我收到错误,更新()不起作用。但肯定没有 工作,因为update()方法不接受hashref。它只是 对象的实际状态与在线同步 服务。正确的代码是。

$obj->foo('bar');
$obj->update();

第一件事是有效的,因为我从来没有检查过这些论点。如果有人提出更多的论据,我不会抛出错误。该方法就像开始一样正常。

sub update {
  my ( $self ) = @_;
  ...
}

当然,我的所有测试绝对可以100%正常使用。但处理这些错误 不是错误也花费我的时间。而且它可以让用户付出很多代价 更多时间。

所以最后。是的,测试是确保代码的唯一正确方法 工作正确。但这并不意味着类型检查毫无意义。 类型检查可以帮助所有非开发人员(在您的模块上) 正确使用您的模块。并节省您和其他人的时间 转储错误。

2)表现

简短:在你关心之前,你不关心表现。

这意味着在您的模块运行缓慢之前,性能始终很快 够了,你不需要关心这个。如果你的模块真的有效 减慢你需要进一步调查。但对于这些调查 你应该使用像Devel :: NYTProf这样的分析器来查看什么是慢的。

我会说。 99%的减速度不是因为你打字 检查,这是你的算法。你做了很多计算,打电话 经常使用等等。如果你完全完成其他解决方案,通常会有所帮助 使用另一种更好的算法,做缓存或其他什么,以及 性能命中不是你的类型检查。但即使检查是 性能打击。然后在重要的地方删除它。

没有理由离开类型检查性能没有的地方 事项。您认为类型检查在上述情况下是否重要? 我写过REST客户端的地方?这里有99%的性能问题 发送到Web服务的请求数量或者这样的时间 请求。不要使用类型检查或MooseX :: Declare等 绝对没有加速。

即使你看到性能劣势。有时它是可以接受的。 因为速度无关紧要,或者有时某些事情会给你带来更大的影响 值。 DBIx :: Class比使用DBI的纯SQL慢,但DBIx :: Class 给你很多这些。

答案 5 :(得分:1)

Params :: Validate效果很好,但当然检查args可以减慢速度。测试是强制性的(至少在我写的代码中)。

答案 6 :(得分:0)

我正在广泛使用Moose进行我正在进行的一个相当大的OO项目。穆斯的严格类型检查在几个场合保存了我的培根。最重要的是,它有助于避免将“undef”值错误地传递给方法的情况。仅仅在那些情况下它就节省了我几个小时的调试时间..

性能打击绝对存在,但可以进行管理。使用NYTProf 2个小时帮助我找到了一些我努力研究的Moose属性,我只是重构了我的代码并且性能提高了4倍。

使用类型检查。防御性编码是值得的。

帕特里克。

答案 7 :(得分:0)

是的,这绝对值得,因为它在开发,维护,调试等过程中会有所帮助。

如果开发人员意外地向方法发送了错误的参数,则会生成有用的错误消息,而不是将错误传播到其他地方。

答案 8 :(得分:0)

有时。每当我通过hash或hashref传递选项时,我通常会这样做。在这些情况下,错误记住或拼错选项名称非常容易,使用Params::Check进行检查可以节省大量的故障排除时间。

例如:

sub revise {
    my ($file, $options) = @_;

    my $tmpl = {
        test_mode => { allow => [0,1], 'default' => 0 },
        verbosity => { allow => qw/^\d+$/, 'default' => 1 },
        force_update => { allow => [0,1], 'default' => 0 },
        required_fields => { 'default' => [] },
        create_backup => { allow => [0,1], 'default' => 1 },
    };

    my $args = check($tmpl, $options, 1)
      or croak "Could not parse arguments: " . Params::Check::last_error();
    ...
}

在添加这些检查之前,我会忘记名称是使用下划线还是连字符,通过require_backup而不是create_backup等等。这是我自己编写的代码 - 如果是其他人要使用它,你应该肯定做某种白痴。 Params::Check可以很容易地进行类型检查,允许值检查,默认值,必需选项,将选项值存储到其他变量等等。