【问题标题】:Is checking Perl function arguments worth it?检查 Perl 函数参数值得吗?
【发布时间】:2011-01-20 19:07:43
【问题描述】:

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

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

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

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

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

【问题讨论】:

    标签: performance perl types moose typechecking


    【解决方案1】:

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

    现在,这听起来很愚蠢,但请考虑一些并非如此的情况。我真的关心@_ 里的内容吗?

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

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

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

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

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

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

        【解决方案3】:

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

        【讨论】:

          【解决方案4】:

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

          理论上,我真的很喜欢这样的声音,但我也可以看到,如果有人以某种方式使用系统(或系统需要增长以允许使用),它会多么容易像纸牌屋一样倒塌。在建立初始验证边界时无法预料。只需对内部函数进行一次外部调用,所有赌注都将结束。

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

          【讨论】:

            【解决方案5】:

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

            1) 测试

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

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

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

            对于后者,我有一个例子。我用 Moose 写了一个模块 和其他一些东西,我的代码总是以分段错误结束。 然后我开始调试我的代码并搜索问题。我花在周围 4小时的时间来发现错误。最后的问题是我有 使用具有阵列特征的 Moose。我使用了“地图”功能,但我没有 提供一个子程序函数,只是一个字符串或其他东西。

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

            我还知道其他例子。我写了一个 REST 客户端到一个接口 与 Moose 完全 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 给你很多这些。

            【讨论】:

              【解决方案6】:

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

              【讨论】:

                【解决方案7】:

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

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

                【讨论】:

                  【解决方案8】:

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

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

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

                  帕特里克。

                  【讨论】:

                    【解决方案9】:

                    有时。每当我通过 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 使进行类型检查、允许值检查、默认值、必需选项、将选项值存储到其他变量等等变得相当容易。

                    【讨论】:

                      猜你喜欢
                      • 2011-01-24
                      • 2013-02-18
                      • 1970-01-01
                      • 2022-01-05
                      • 2012-12-26
                      • 1970-01-01
                      • 2023-01-03
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多