0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Rust代码启发之返回值异常错误处理

工程师邓生 来源:CrackingOysters 作者:CrackingOysters 2022-09-22 09:24 次阅读

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

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

返回值错误处理

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

poYBAGMruXSAOsonAABERI2P8U0826.jpg

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

异常错误处理

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

使用异常的代码类似

poYBAGMruYaARsVnAAAmqblhFCU436.jpg

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

错误处理也容易被忽略,不写相应的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是泛型参数。不懂泛型不重要,这里跟泛型没有关系。我们要知道的是Result是两个类型的集合:

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

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

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

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

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

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

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

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

Result和类型的代码可以是

pYYBAGMruZqAb4TrAAAkG9L3Hh0254.jpg

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

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

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

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

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

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

pYYBAGMrubWAA8t2AAAb_QfNdaI791.jpg

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

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

poYBAGMrugGABAnhAAD-8POIEvE692.jpg

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

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

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

poYBAGMruhSAQQ8nAAAoKm9J6kw469.jpg

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

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

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

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

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

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

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

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

poYBAGMrukGAEExGAAAUCHOhdbI596.jpg

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

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

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

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

pYYBAGMrunCAFYXtAADRtwuGU7w403.jpg

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

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

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

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





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    19

    文章

    2903

    浏览量

    102874
  • python
    +关注

    关注

    51

    文章

    4657

    浏览量

    83380
  • Rust
    +关注

    关注

    1

    文章

    219

    浏览量

    6373

原文标题:Rust代码启发之错误处理

文章出处:【微信号:Rust语言中文社区,微信公众号:Rust语言中文社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    嵌入式编程错误处理机制设计

    本文主要总结嵌入式系统C语言编程中,主要的错误处理方式。文中涉及的代码运行环境如下。
    发表于 04-28 09:59 513次阅读
    嵌入式编程<b class='flag-5'>错误处理</b>机制设计

    Rust语言中错误处理的机制

    Rust语言中,错误处理是一项非常重要的任务。由于Rust语言采用静态类型检查,在编译时就能发现很多潜在的错误,这使得程序员能够更加自信和高效地开发程序。然而,即使我们在编译时尽可能
    的头像 发表于 09-19 14:54 716次阅读

    利用函数参数和返回值提高嵌入式软件质量

    本帖最后由 eehome 于 2013-1-5 09:50 编辑 利用函数参数和返回值提高嵌入式软件质量提高软件代码的质量是每一个软件设计者都必须考虑的问题,这涉及软件的有效性和经济价值
    发表于 12-26 15:03

    main函数返回值的认知

    。main也可以使用return向操作系统返回一个,使用操作系统的命令可以检测main的返回值。一般约定在main返回0时,表示程序运行过程中没有出现
    发表于 10-24 11:08

    GetLastError返回值 相关资料分享

    GetLastError()返回值列表:〖0〗-操作成功完成。〖1〗-功能错误。〖2〗-系统找不到指定的文件。〖3〗-系统找不到指定的路径。〖4〗-系统无法打开文件。〖5〗-拒绝访问。〖6〗-句柄
    发表于 07-05 07:46

    Arduino/ESP8266函数不返回值导致异常的原因?

    () { } 所以 initFunc 应该返回一个 int 而不是,但是安装程序忽略了返回值。在 C 中,这会生成 警告:控件到达非空函数 [-Wreturn-type] 的末尾 ,但运行正常。 在
    发表于 05-08 07:36

    esp_now_send的返回值是什么意思?

    ESP_NOW_SEND_SUCCESS 或 ESP_NOW_SEND_FAIL,但这些都不是为我定义的,所以我不能在我的代码中使用它们甚至将它们与 esp_now_send 的返回值进行比较。 我正在
    发表于 06-02 06:27

    LabVIEW中的错误处理

    如何合理使用 LabVIEW 中的自定义错误处理功能;对于可预见的错误,是否可以选择直 接忽略,或者前几次尝试忽略直到该特定错误出现很多次后才通知用户需要纠正该错误 了;是否可以对
    发表于 05-24 11:07 6次下载

    嵌入式系统C语言编程中的错误处理资料总结

    本文主要总结嵌入式系统C语言编程中,主要的错误处理方式。文中涉及的代码运行环境如下:
    发表于 11-28 10:39 1742次阅读

    Rust中的错误处理方法

    Rust 中没有提供类似于 Java、C++ 中的 Exception 机制,而是使用 Result 枚举的方式来实现。
    的头像 发表于 02-20 09:37 581次阅读

    什么是函数的返回值

    函数的返回值是函数被调用后,执行所调用函数内代码后所得出的结果,并且将返回给主函数的值。
    的头像 发表于 04-04 17:21 3370次阅读

    rust语言基础学习: rust中的错误处理

    错误是软件中不可避免的,所以 Rust 有一些处理出错情况的特性。在许多情况下,Rust 要求你承认错误的可能性,并在你的
    的头像 发表于 05-22 16:28 1319次阅读

    ARM异常返回值的合法值有哪些?各返回值分别代表什么?

    ARM异常返回值的合法值有哪些?各返回值分别代表什么? ARM异常返回值的合法值包括:中断(IRQ)、中止(ABORT)、未定义指令(UND
    的头像 发表于 10-19 16:36 381次阅读

    C语言中的错误处理机制解析

    C 语言不提供对错误处理的直接支持,但是作为一种系统编程语言,它以返回值的形式允许您访问底层数据。
    的头像 发表于 02-26 11:19 183次阅读

    闭包在错误处理中的应用模式探索

    通过在函数和方法中返回错误对象作为它们的唯一或最后一个返回值——如果返回 nil,则没有错误发生——并且主调(calling)函数总是应该检
    的头像 发表于 03-15 09:57 110次阅读