autodie::hints - 为 autodie 提供有关用户子例程的提示
package Your::Module;
our %DOES = ( 'autodie::hints::provider' => 1 );
sub AUTODIE_HINTS {
return {
foo => { scalar => HINTS, list => SOME_HINTS },
bar => { scalar => HINTS, list => MORE_HINTS },
}
}
# Later, in your main program...
use Your::Module qw(foo bar);
use autodie qw(:default foo bar);
foo(); # succeeds or dies based on scalar hints
# Alternatively, hints can be set on subroutines we've
# imported.
use autodie::hints;
use Some::Module qw(think_positive);
BEGIN {
autodie::hints->set_hints_for(
\&think_positive,
{
fail => sub { $_[0] <= 0 }
}
)
}
use autodie qw(think_positive);
think_positive(...); # Returns positive or dies.
在处理 Perl 的内置函数时,autodie pragma 非常智能。这些函数的行为是固定的,autodie
确切地知道它们如何尝试发出失败信号。
但是,对于模块中的用户定义子例程呢?如果对用户定义的子例程使用 autodie
,则它假定以下行为来表明失败
标量上下文中为假值
列表上下文中为空列表
列表上下文中包含单个未定义值的列表
所有其他返回值(包括单零列表和包含单个空字符串的列表)都被视为成功。但是,实际代码并不总是那么容易。您正在处理的代码可能在失败时返回包含单词“FAIL”的字符串,或包含 (undef, "human error message")
的两元素列表。为了使 autodie 与这些类型的子例程一起使用,我们有提示界面。
提示界面允许将提示提供给 autodie
,说明它应如何从用户定义的子例程中检测到失败。虽然这些提示可以由 autodie
的最终用户提供,但理想情况下应将其写入模块本身或写入 autodie
本身的帮助程序模块或子类。
提示是针对自动死亡子例程的返回值进行检查的子例程或值。如果匹配返回 true,则 autodie
认为子例程已失败。
如果提供的提示是子例程,则 autodie
会将完整的返回值传递给该子例程。如果提示是任何其他值,则 autodie
会针对所提供的值进行智能匹配。在 Perl 5.8.x 中没有智能匹配运算符,因此这些版本中仅支持子例程提示。
提示可以同时针对标量和列表上下文提供。请注意,自动死亡子例程永远不会看到空上下文,因为 autodie
始终需要捕获返回值以进行检查。在空上下文中调用的自动死亡子例程的行为就像在标量上下文中调用一样,但在检查其返回值后会将其丢弃。
提示可能包括子例程引用、重载智能匹配的对象、正则表达式,并且根据 Perl 版本可能还包括其他内容。您可以指定不同的提示来识别标量和列表上下文中的失败方式。
这些示例适用于 AUTODIE_HINTS
子例程以及调用 autodie::hints->set_hints_for()
时。
最常见的上下文特定提示是
# Scalar failures always return undef:
{ scalar => sub { !defined($_[0]) } }
# Scalar failures return any false value [default expectation]:
{ scalar => sub { ! $_[0] } }
# Scalar failures always return zero explicitly:
{ scalar => sub { defined($_[0]) && $_[0] eq '0' } }
# List failures always return an empty list:
{ list => sub { !@_ } }
# List failures return () or (undef) [default expectation]:
{ list => sub { ! @_ || @_ == 1 && !defined $_[0] } }
# List failures return () or a single false value:
{ list => sub { ! @_ || @_ == 1 && !$_[0] } }
# List failures return (undef, "some string")
{ list => sub { @_ == 2 && !defined $_[0] } }
# Unsuccessful foo() returns 'FAIL' or '_FAIL' in scalar context,
# returns (-1) in list context...
autodie::hints->set_hints_for(
\&foo,
{
scalar => qr/^ _? FAIL $/xms,
list => sub { @_ == 1 && $_[0] eq -1 },
}
);
# Unsuccessful foo() returns 0 in all contexts...
autodie::hints->set_hints_for(
\&foo,
{
scalar => sub { defined($_[0]) && $_[0] == 0 },
list => sub { @_ == 1 && defined($_[0]) && $_[0] == 0 },
}
);
这种“在所有上下文中”的构造非常常见,可以使用“fail”键进行缩写。这会将 scalar
和 list
提示同时设置为相同的值
# Unsuccessful foo() returns 0 in all contexts...
autodie::hints->set_hints_for(
\&foo,
{
fail => sub { @_ == 1 and defined $_[0] and $_[0] == 0 }
}
);
# Unsuccessful think_positive() returns negative number on failure...
autodie::hints->set_hints_for(
\&think_positive,
{
fail => sub { $_[0] < 0 }
}
);
# Unsuccessful my_system() returns non-zero on failure...
autodie::hints->set_hints_for(
\&my_system,
{
fail => sub { $_[0] != 0 }
}
);
如果您正在使用在失败时返回特殊内容的模块,则可以手动为每个所需的子例程创建提示。指定提示后,它们将可用于之后加载的所有文件和模块,因此您可以将此工作移至模块中,它仍然有效。
use Some::Module qw(foo bar);
use autodie::hints;
autodie::hints->set_hints_for(
\&foo,
{
scalar => SCALAR_HINT,
list => LIST_HINT,
}
);
autodie::hints->set_hints_for(
\&bar,
{ fail => SOME_HINT, }
);
可以将子例程引用(推荐)或完全限定的子例程名称作为第一个参数传递。这意味着你可以在可能加载的模块上设置提示
use autodie::hints;
autodie::hints->set_hints_for(
'Some::Module:bar', { fail => SCALAR_HINT, }
);
当你的项目使用大量第三方模块时,此技术最有用。你可以在一个地方定义所有可能的提示。它甚至可以是 autodie 的子类。例如
package my::autodie;
use parent qw(autodie);
use autodie::hints;
autodie::hints->set_hints_for(...);
1;
你现在可以使用 use my::autodie
,它将像标准 autodie
一样工作,但现在它会知道你设置的任何提示。
autodie
提供了一个被动接口,允许你为你的模块声明提示。如果加载了 autodie
,它将找到并使用这些提示,但如果没有加载 autodie
,这些提示将没有效果(或依赖项)。要设置这些提示,你的模块需要声明它确实执行 autodie::hints::provider
角色。你可以通过编写自己的 DOES
方法、使用诸如 Class::DOES
之类的系统为你处理繁重的工作,或声明一个带有 autodie::hints::provider
键和相应真值的 %DOES
包变量来完成此操作。
请注意,检查 %DOES
哈希是仅限于 autodie
的捷径。其他模块不使用此机制检查角色,尽管你可以从 CPAN 使用 Class::DOES
模块来允许它。
此外,你必须定义一个 AUTODIE_HINTS
子例程,该子例程返回一个哈希引用,其中包含子例程的提示
package Your::Module;
# We can use the Class::DOES from the CPAN to declare adherence
# to a role.
use Class::DOES 'autodie::hints::provider' => 1;
# Alternatively, we can declare the role in %DOES. Note that
# this is an autodie specific optimisation, although Class::DOES
# can be used to promote this to a true role declaration.
our %DOES = ( 'autodie::hints::provider' => 1 );
# Finally, we must define the hints themselves.
sub AUTODIE_HINTS {
return {
foo => { scalar => HINTS, list => SOME_HINTS },
bar => { scalar => HINTS, list => MORE_HINTS },
baz => { fail => HINTS },
}
}
这允许你的代码设置提示,而无需依赖加载或甚至安装 autodie
和 autodie::hints
。通过这种方式,当安装了 autodie
时,你的代码可以做正确的事情,但不需要依赖它才能运行。
当用户定义的子例程被 autodie
包装时,如果提示可用,它将使用提示,否则将恢复到本文档介绍中描述的默认行为。如果我们期望提示存在,但(出于某种原因)它尚未加载,这可能会出现问题。
我们可以要求 autodie
坚持使用提示,方法是在子例程名称的开头加上感叹号。一个单独的感叹号表示它后面的所有子例程都必须声明提示。
# foo() and bar() must have their hints defined
use autodie qw( !foo !bar baz );
# Everything must have hints (recommended).
use autodie qw( ! foo bar baz );
# bar() and baz() must have their hints defined
use autodie qw( foo ! bar baz );
# Enable autodie for all of Perl's supported built-ins,
# as well as for foo(), bar() and baz(). Everything must
# have hints.
use autodie qw( ! :all foo bar baz );
如果指定的子例程没有提示,这将导致编译时错误。强烈建议坚持使用 Perl 内置函数的提示(例如,open
和 close
)。
强烈建议坚持使用提示。
你已使用子例程引用调用了 autodie::hints->set_hints_for()
,但该引用无法解析回子例程名称。它可能是匿名子例程(无法自动终止),或由于其他原因而缺少名称。
如果你使用具有实际名称的子例程收到此错误,那么你可能已找到 autodie 中的错误。请参阅 autodie 中的“BUGS”,了解如何报告此错误。
在定义提示时,你可以提供 list
和 scalar
关键字,或提供单个 fail
关键字。你无法混合匹配它们。
你已提供标量提示而不提供列表提示,反之亦然。你必须同时提供 scalar
和 list
提示,或单个 fail
提示。
Damian Conway 博士建议使用提示界面并提供示例用法。
Jacinta Richardson 将我的许多想法翻译成此文档。
版权所有 2009,Paul Fenwick <[email protected]>
此模块是免费软件。你可以在与 Perl 相同的条款下分发它。