19

有很多关于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!";

也许是因为没有某种辅助模块,工作量太大,但也许是因为在实践中我们不会向函数发送过多的参数,也不会向期望标量的方法发送 arrayrefs - 或者如果我们这样做,我们有use warnings;并且我们很快就听说了——一种 鸭子打字方法。

那么 Perl 类型检查是否值得性能打击,或者它的优势主要体现在 C 或 Java 等已编译的强类型语言中?

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

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

4

9 回答 9

14

如果检查参数是否正是您所需要的对您很重要,那么它是值得的。仅当您已经具有正确的功能时,性能才重要。得到错误答案或核心转储的速度有多快并不重要。:)

现在,这听起来像是一件愚蠢的事情,但请考虑一些并非如此的情况。我真的在乎这里有@_什么吗?

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

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

但是,有时您需要保护条件,因为您的情况并不那么简单。接下来您必须将数据传递给可能期望它们在特定范围内或特定类型内,并且不会优雅地失败。

当我考虑警戒条件时,我会考虑如果输入不好会发生什么,以及我对失败的关心程度。我必须根据每种情况的要求来判断。我知道这作为一个答案很糟糕,但我更喜欢它而不是束缚和纪律的方法,即使无关紧要,你也必须经历所有的混乱。

我害怕Params::Validate,因为它的代码通常比我的子程序长。Moose 的东西非常吸引人,但你必须意识到这是一种声明你想要的东西的方式,你仍然可以得到你可以手工构建的东西(你只是不必看到它或做它)。我最讨厌 Perl 的地方是缺少可选的方法签名,这是 Perl 6 和 Moose 中最吸引人的特性之一。

于 2010-02-25T00:56:21.847 回答
7

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

MooseX::Method::Signatures 是一个优雅的解决方案,可以添加简单的声明方式来解释方法的参数。Method::Signatures::Simple 和 Params::Validate 很好,但缺少我认为 Moose 最吸引人的特性之一:类型系统。我已经在几个项目中使用了 MooseX::Declare 和扩展 MooseX::Method::Signatures,我发现编写额外检查的障碍是如此之小,几乎是诱人的。

于 2010-02-25T01:53:06.287 回答
6

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

于 2011-12-15T01:24:00.467 回答
4

我看到的对此提出的反对意见是,检查每个函数调用的参数是多余的,而且浪费了 CPU 时间。这个论点的支持者倾向于一种模型,在该模型中,所有传入的数据在首次进入系统时都经过严格检查,但内部方法没有参数检查,因为它们只能由代码调用,该代码将传递已经通过系统检查的数据。边界,因此假定它仍然有效。

理论上,我真的很喜欢这样的声音,但我也可以看到,如果有人以一种在建立初始验证边界。只需对内部函数进行一次外部调用,所有赌注都将取消。

在实践中,我目前正在使用 Moose,而 Moose 并没有真正让您选择绕过属性级别的验证,而且 MooseX::Declare 处理和验证方法参数比手动展开 @_ 更简单,所以这几乎是一个有争议的问题。

于 2010-02-25T09:10:15.957 回答
2

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

1) 测试

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

如果您编写一个模块,您会遇到两个问题,或者说两个不同的人使用您的模块。

您作为开发人员和使用您的模块的用户。测试首先有助于您的模块正确并做正确的事情,但它对只使用您的模块的用户没有帮助。

对于后者,我有一个例子。我使用 Moose 和其他一些东西编写了一个模块,我的代码总是以分段错误结束。然后我开始调试我的代码并搜索问题。我花了大约 4 个小时来查找错误。最后的问题是我使用了 Moose 和 Array Trait。我使用了“map”函数,我没有提供子程序函数,只是一个字符串或其他东西。

当然这是我的一个绝对愚蠢的错误,但我花了很长时间来调试它。最后,仅检查参数是子引用的输入将花费开发人员 10 秒的时间,并且会花费我和其他人更多的时间。

我也知道其他例子。我已经用 Moose 将一个 REST 客户端写到了一个完全 OOP 的接口中。最后你总是得到对象,你可以更改属性,但确保它不会为你所做的每一个更改调用 REST API。相反,您更改您的值,最后调用一个 update() 方法来传输数据并更改值。

现在我有一个用户然后写道:

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

当然我得到了一个错误,那个 update() 不起作用。但确定它不起作用,因为 update() 方法不接受 hashref。它仅将对象的实际状态与在线服务同步。正确的代码是。

$obj->foo('bar');
$obj->更新();

第一件事有效,因为我从未检查过论点。如果有人给出比我预期的更多的论点,我不会抛出错误。该方法刚刚开始正常。

子更新{
  我的 ( $self ) = @_;
  ...
}

当然,我所有的测试绝对可以 100% 正常工作。但是处理这些不是错误的错误也花费了我的时间。并且它可能会花费用户更多的时间。

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

2) 性能

简而言之:在您关心之前,您不会关心性能。

这意味着在你的模块工作变慢之前,性能总是足够快,你不需要关心这个。如果您的模块确实可以减慢您的速度,则需要进一步调查。但是对于这些调查,您应该使用像 Devel::NYTProf 这样的分析器来查看什么是慢的。

我会说。99% 的慢不是因为你做类型检查,而是你的算法。您进行大量计算,经常调用函数等。如果您完全其他解决方案使用另一种更好的算法,进行缓存或其他操作,通常会有所帮助,并且性能损失不是您的类型检查。但即使检查是性能损失。然后在重要的地方删除它。

没有理由将类型检查留在性能无关紧要的地方。您认为类型检查在上述情况下是否重要?我在哪里写了一个 REST 客户端?这里 99% 的性能问题是发送到 Web 服务的请求量或此类请求的时间。不要使用类型检查或 MooseX::Declare 等可能会加速绝对没有。

即使您看到性能劣势。有时这是可以接受的。因为速度并不重要,或者有时某些东西会给你带来更大的价值。DBIx::Class 比使用 DBI 的纯 SQL 慢,但 DBIx::Class 为您提供了很多。

于 2010-05-12T09:23:32.730 回答
1

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

于 2010-03-10T07:55:26.507 回答
1

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

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

于 2012-07-03T12:32:09.313 回答
0

我正在将 Moose 广泛用于我正在从事的一个相当大的 OO 项目。Moose 严格的类型检查在某些情况下救了我的培根。最重要的是,它有助于避免“undef”值被错误地传递给方法的情况。仅在这些情况下,它就为我节省了数小时的调试时间。

性能影响肯定存在,但可以管理。使用 NYTProf 的 2 小时帮助我找到了一些我过于努力的 Moose 属性,我刚刚重构了我的代码并获得了 4 倍的性能提升。

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

帕特里克。

于 2011-12-15T22:07:15.990 回答
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使得进行类型检查、允许值检查、默认值、所需选项、将选项值存储到其他变量等变得相当容易。

于 2013-02-13T21:07:22.367 回答