Rust代码启发之错误处理

DFINITY周报—SNS本地测试版本上线

文章来自于/D Plus 投稿、转载请联系/D Plus小助手 IC区块高度:1201405330 ICP销毁:91675 ICP当前总供应量:489083556 ICP质押量:264278525 容器智能合约:153328 9月15日:DFINITY开发者团…

编写程序,错误处理是不可避免的。

Rust代码启发之错误处理

编辑切换为居中

添加图片注释,不超过 140 字(可选)

但程序员总是偏向正常的情况,而容易忽略有错误的情况。

返回值错误处理

最开始,错误是通过返回值来表示,比如非零表示错误,0表示成功。而处理错误代码类似

hr = step1();

if hr != 0 {
handle error;
}
hr = step2();
if hr != 0 {
handle error;
}
hr = step3();
if hr != 0 {
handle error;
}

这样的代码,错误处理代码和业务逻辑交织在一起,也容易忽略处理错误。以及把返回值只用于错误返回,有点浪费的感觉。因为很多时候把计算结果作为返回值,更符合思考的逻辑。

异常错误处理

后面出现了异常的方式,在出错的时候,抛出异常。异常一层一层往上抛,如果没有处理异常,那么程序就会被terminate. 比如C++和Java采用这种方式。

使用异常的代码类似

try {
step1();
step2();
step3();
} catch(...) {
handle error.
}

看起来错误处理代码与业务逻辑分开,比较清晰。但有如下的不足,

  • 错误处理也容易被忽略,不写相应的catch,

  • 上层调用者在嵌套很多层的时候,很难知道底层是否会跑异常。

  • 这么写代码,在catch的地方,分不清楚是在哪里出了错误,step1?step3?

注:python把异常还用于程序控制流改变,如StopInteractionException用于跳出循环。

Java里异常还分checked exception和unchecked exception。checked exception是必须要处理的异常,从而可以避免被忽略。但checked exception有其局限性,比如添加新的checked exception,会改变接口签名,变得不能向前兼容。

综上,我们需要一种错误处理

  • 避免无意识地忽略。

  • 可阅读性强。

其中返回值和异常都可能会被无意识忽略。可读性,异常好于返回值,且避免占用了返回值。而不可忽略的Java checked exception有它自己的问题。

就没有其他更好的方式了吗?Rust给出了它的答案,使用Result 类型。

什么是Result和类型?

Result的完整形态是Result<T, E>,其中T和E是泛型参数。不懂泛型不重要,这里跟泛型没有关系。我们要知道的是Result是两个类型的集合:

  • 一个是没有错误时的计算结果

  • 一个是出错时,要返回的错误

第一点,我们可以看到,现在返回值可以用于返回函数计算的结果了,没有被错误占领。

第二点,因为返回的值又不是计算结果,所以程序员不能直接使用返回值,需要先检查具体的类型,没有出错时,才能使用计算结果。这样又避免了无意识的忽略错误。

我们可以简陋地认为Result类型,是C++里面的tag union,即包含一个tag的union。其中tag是错误标记,如果是0表示成功,非零表示错误,而union则存放着具体的错误或者具体的计算结果。(很多时候Result,称作是和类型 sum type)

可以避免无意识地忽略错误,那么可读性呢?

因为返回值不是计算结果,需要检查一下才能继续下一步,这不就跟错误返回值一样了吗?

注:先把话说明,没有错误处理的代码是可读性最好的。因为只有happy path,第一步,第二步等等。但我们讨论在可能出错的时候的可读性。

Result和类型的代码可以是

match step1() {
Ok(o) => {
match step2() { }
}
Err(e) => { handle erro}}

哇咔咔,这看上去可读性很差那。实话说,这么写的代码的确没有什么可读性。

但Rust提供了另外一个写法,如下

let res = step1()?;let res= step2()?;let res = step3()?;

这个写法看起来很像异常的情况。业务逻辑和错误处理没有交织在一起。

眼尖的读者会发现每个函数都有个问号?。而错误处理就藏在?后面。

问号的存在,让Rust自动帮你检查返回值,在出错的时候直接返回错误,不再继续往下走了。问号可以展开为如下的形式(简化版本,方便理解,实际版本请看官方文档),

let res = match step1() {
Ok(o) => o,
Err(e) => return e,}

到这里,我们可以看到Rust的创新点在于将错误与计算结果放在了返回值,而不是单纯地返回错误,或者返回计算结果和从第三个路径返回异常。并且提供了问号来简写错误处理。所以同时提供了避免无意识忽略错误和提供可读性。

但错误处理远远不止这点内容。在我写了GitHub的webhook微服务 https://github.com/Celthi/github-webhook-gateway 以后,我发现我写了一大坨下面的代码

let res = client
.post(...
))
.header("Authorization", &config_env::get__api_token())
.header("Accept", "application/json")
.json(&task)
.send()
.await;
match res {
Ok(body) => {
println!("Succeed posting task {:?}", body);
if body.status() == reqwest::StatusCode::OK {
if let Ok(result) = body.json::<serde_json::Value>().await {
if let Some(code) = result.get("code") {
if let Some(code) = code.as_u64() {
if code != 200 {

if let Err(e) = github::issue::post_issue_comment(...
).await
{
eprintln!("{}", e);
}
}
}
}
}
}
}}

写成这样,说明我对Rust的错误处理仍然没有理解到位,于是我试着重构这段代码,并提了个问题How reduce the nested if and indents?

经过重构以后,我发现了如下的一些情况

有时候只想处理成功的情况,我称之为“最大努力做事”。所以代码逻辑是这样

if let Ok() = step1() {
if let Ok() = step2() {
if let Ok() = step3 () {
...
}
}}

这也是我自己代码那么多缩进的原因。它可以通过如下方式来改善,

方式一、首先先把代码段提到一个单独的函数post_sending_task(),然后将返回值改成Result,所以调用的地方代码是

let _ = best_delivery(); //这里使用使用_,说明我们不关心失败的情况

在这个best_delivery()里面,我们就可以使用问号表达式了。

方式二、使用组合子,如将Option转换成Result,从而可以使用问号,如

let res = get_something().ok_or_else(|| err)?;

这里ok_or_else是option上的组合子。什么是组合子,简单理解是将东西组合在一起的函数。至于”子“,一种称谓罢了,要说相似的话,第一反应类似套接字里面的”字“的功能。

方式三、提前返回。通过反转if的条件,提前返回,比如,

if condtion_not_care {
return ;}

提前返回没有问号那么可读性强,但是减少了缩进的层数。

方式四、如果获取结果的同时必须处理错误的情况,那么使用下面的形式,

let res = match step1() {
Ok(o)=> o,
Err(e) => { handle error }}

注意,问号表达式是适合于获取结果且不处理错误,直接往上抛。

经过这四个个方式的改善,我的代码可读性提高了,变成了


Rust代码启发之错误处理

错误处理与日志、错误报告

错误处理的时候,通常要写日志。但是错误处理和日志是两码事。不是所有的错误处理都要写日志,而且不同的错误,写到的日志级别是不一样的,如调试,信息,错误,严重等等级别。

错误处理是处理出错的情况,而日志是记录感兴趣的信息。它们有重合,但是关注点不一样。以后再写文章。

错误报告(error report)跟错误处理也是两码事,虽然经常关联在一起,也留作以后再写文章。

IC近期最新动态更新:IC路线图更新、INTERSTELLAR峰会、DFINITY成为万向峰会钻石赞助商

文章来自于/D Plus 投稿、转载请联系/D Plus小助手 01IC路线图更新在7月,DFINITY在首次IC全球研发会议上宣布了优化路线图的最新战略方向更新-放弃以里程碑概念的路线图,将迭代侧重点放在底层核心协议、系统Dapp、开发者体验等领域上,并通过…

Click to rate this post!
[Total: 0 Average: 0]

人已赞赏
Rust开发名家说每日优选

Web3写作者如何做研究?

2022-9-18 4:49:06

名家说每日优选

Facebook另一个公链「儿子」:0L Network的打怪之路

2022-9-18 4:51:01

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
有新消息 消息中心
搜索