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

    文章

    2952

    浏览量

    104457
  • python
    +关注

    关注

    55

    文章

    4765

    浏览量

    84353
  • Rust
    +关注

    关注

    1

    文章

    228

    浏览量

    6541

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

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

收藏 人收藏

    评论

    相关推荐

    Rust语言中错误处理的机制

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

    如何处理STM32的HAL库函数返回异常问题?

    (1)官方提供的例程里面,例如返回的结果不是 HAL_OK 的结果,一般直接跳转到 错误处理的函数里面了。这样写的目的是给开发者根据实际情况自己写异常处理
    发表于 04-17 06:39

    嵌入式C编程常用的异常错误处理

    嵌入式C编程中,异常错误处理是确保系统稳定性和可靠性的重要部分。以下是一些常见的异常错误处理方法及其详细说明和示例: 1. 断言 (Assertions) 断言用于在开发阶段捕获程
    发表于 08-06 14:32

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

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

    WebApi接口返回值的四种类型

    Webapi的接口返回值主要有四种类型 void无返回值 IHttpActionResult HttpResponseMessage 自定义类型 void无返回值 大家都知道void声明的是一个无
    发表于 11-27 14:52 1.3w次阅读

    C语言程序开发中关于函数返回值的问题

    C语言函数可以通过返回值表示输出结果,例如 log() 函数的返回值会根据不同的输入,返回不同的。再比如,我们定义一个函数 myopen(),用于打开某个文件,那么,这个函数要么能够
    发表于 09-06 10:01 918次阅读

    return-函数的返回值是什么

    return关键字后接变量名或表达式可以将函数的计算结果返回到调用处。变量或表达式等同于接收果汁、豆浆的杯子。如果函数没有返回值,return可以省略不写。没有返回值的意思是程序执行完毕之后,不需要给调用函数处提供数据。
    的头像 发表于 02-23 10:52 1129次阅读
    return-函数的<b class='flag-5'>返回值</b>是什么

    什么是函数的返回值

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

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

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

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

    ARM异常返回值的合法有哪些?各返回值分别代表什么? ARM异常返回值的合法
    的头像 发表于 10-19 16:36 801次阅读

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

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

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

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

    一站式统一返回值封装、异常处理异常错误码解决方案—最强的Sping Boot接口优雅响应处理

    1. 前言 统一返回值封装、统一异常处理异常错误码体系的意义在于提高代码的可维护性和可读性,使
    的头像 发表于 06-20 15:42 432次阅读

    HTTP相关返回值异常如何解决(上篇)

    ​ 今天我们讲讲HTTP相关返回值异常如何解决(实例持续更新中) HTTP介绍 HTTP(超文本传输协议,Hypertext Transfer Protocol)是用于在网络上进行数据交换的应用层
    的头像 发表于 10-20 16:40 191次阅读
    HTTP相关<b class='flag-5'>返回值</b><b class='flag-5'>异常</b>如何解决(上篇)

    socket编程中的错误处理技巧

    错误处理能够确保程序在遇到异常情况时不会崩溃,而是能够优雅地处理问题。 提升用户体验 :通过适当的错误处理,可以给用户提供清晰的错误信息
    的头像 发表于 11-01 17:47 740次阅读