掉入错误的兔子洞

Down the error rabbit hole

好的错误信息是有用的,因为它们告诉我们,当出错时,我们不必检查什么。(见:LTA)我通过尝试用 Raku 替换 Bash 得出了这个结论。让我们来看看一些无辜的代码。

my $p = Proc.new;
$p.spawn(<does-not-exist>);
$p.sink;

CATCH {
    default { say .^name, ‚: ‘, .message; .resume }
}

# OUTPUT: «X::Proc::Unsuccessful: The spawned command 'does-not-exist' exited unsuccessfully (exit code: 1, signal: 0)␤»

这个错误信息是错误的。一个由 exec 运行和派生的命令,只有当可执行文件或脚本是可执行的,对于操作系统用户可访问的,一个正常的文件,而不是一个目录,才能产生一个非零的退出代码。(还有更多,但这些因操作系统而异。)正如命令的名字所暗示的那样,它不存在,因此不能产生1的退出码。 这一点我炮制了 brtfs 来创建一个快照。因为 brtfs 不是一个有效的命令。这是一个拼写错误。当我终于意识到 btrfs 的效果要好得多时,15分钟已经过去了。许多 unix 命令会根据提供的参数产生不同的错误代码。所以当需要使用重度 shell 脚本时,这种拼写错误真的会让人很困惑。

上面的例子是 Proc::* 产生异常的唯一方法。这就很麻烦了,因为语言使用者并不是要手工创建一个 Proc 对象。子程序 runshellqx{} 永远不会在 sink 上下文 中使用它。相反,它们会在检查错误时评估为 False。我们可以像许多 shell 语言一样,通过用 && 这样的方式链住命令来模仿 shell 脚本。然而,$shell 会检查 errno,并说明 “命令未找到"以帮助用户。Rakudo 却没有这样做。我们可以通过几行代码来改变这一点。

class X::Proc::CommandNotFound is X::Proc::Unsuccessful {
    method message {
        my $symlink = $.proc.command[0].IO.l ?? ' (symlink)' !! '';
        "The command '{$.proc.command[0]}'$symlink was not found."
    }
}

sub use-proc-fatal {
    &run.wrap(-> |c {
        my Proc:D $ret := callsame;
        if $ret.exitcode > 0 {
            with $ret.command[0].IO {
                X::Proc::CommandNotFound.new(:proc($ret)).throw unless .e;
           }
            X::Proc::Unsuccessful.new(:proc($ret)).throw if $ret.exitcode > 0 || $ret.signal > 0;
        }
        $ret
    });
}
use-proc-fatal;
say ‚### run does-not-exist‘;

run 'does-not-exist';
CATCH {
    default { say .^name, ‚: ‘, .message; }
}
run does-not-exist
X::Proc::CommandNotFound: The command 'does-not-exist' was not found.

这不是一个正确的解决方案。首先,Proc 永远不应该抛出异常。如果有的话,Raku 应该失败,因为它和 // 一起比较好。在 shell 脚本中,命令的执行不成功是很正常的。错误处理只是游戏的一部分。既然它如此常见,就应该有更多更好的异常。我有一个脚本,我多测试了几种情况,多了一些异常。让这些从运行和 shell 中返回的是 Failure,会让 qx 不再默默地失败。

在我的备份脚本中,我必须将每个命令都包在一个 sub 中,并抛出异常,以利用 CATCHLEAVE 块进行错误处理和清理(有临时目录 /file 和 btrfs 快照)。我真的希望在核心中能有一个使用 Proc::Fatal 来帮助完成这个任务。我相信这需要在核心中,因为 errno 隐藏在 Proc 里面,在 Proc::Async 里面,在 nqp::spawnprocasync 里面,在 MVM_proc_spawn_async 里面,我在那里停了下来,又爬回了兔子洞。事实上我一直没有找到那个该死的 exec*。另外 Bash 在大约50行代码之后就不再好玩了。简单的事情应该是简单的。

我很确定我错过了很多错误模式。所以这需要进一步的思考。如果你有这样的请分享。如果你现在写一篇关于 Raku 的博文,你可以期待一周500次点击。这比去年增长了20%。我们比股市还要好! 在这个世界上,钱可以印,但工作不能印,这是一个真正的壮举。

by gfldex

comments powered by Disqus