【Go系列】 Go的错误处理

承上启下

        上一篇文章中介绍了struct和interface,在Go语言中,是没有Class这个概念的,我们可以通过Struct和方法的组合,来实现Class。我们通过Struct嵌套来实现继承这样的一种机制,并且不用设置虚函数这样的特殊说明。同时,实现接口的方式也是隐式实现,当我们实现了一组接口的所有的方法时,默认一定使用了这组接口了。只能说这样的方法很不直观,但是编译器的静态代码分析还是能够发现的。但是如果是对于程序中的error,Go又会怎么处理呢?

开始学习

错误类型(error

在Go语言中,error 是一个内建的接口类型,用于表示错误状态。它是Go错误处理的核心部分,提供了一种标准的方式来传达函数或方法执行失败的信息。以下是 error 接口的定义:

type error interface {Error() string
}

这个接口非常简单,只包含一个 Error() 方法,该方法返回一个描述错误的字符串。

创建错误

创建错误通常有以下几种方式:

errors.New:这是最简单的方式,用于创建一个包含给定错误消息的新错误。

err := errors.New("something went wrong")

​​​​​​fmt.Errorf:这个函数允许你使用格式化字符串创建错误,类似于 fmt.Sprintf

err := fmt.Errorf("invalid argument: %v", value)

自定义错误类型:你可以通过实现 error 接口来自定义错误类型。

type MyError struct {Msg stringCode int
}func (e *MyError) Error() string {return fmt.Sprintf("error %d: %s", e.Code, e.Msg)
}func doSomething() error {return &MyError{Msg: "operation failed", Code: 42}
}

错误处理

在Go中,错误处理通常是显式的,以下是一些常见的错误处理模式:

检查错误:在调用可能返回错误的函数或方法后,应该立即检查错误。

result, err := doSomething()
if err != nil {// 处理错误return err
}
// 使用result

错误传递:如果当前函数无法处理错误,它应该将错误返回给调用者。

func doSomething() error {result, err := doAnotherThing()if err != nil {return err // 传递错误}// 使用resultreturn nil
}

错误包装:有时,你可能想在返回错误时添加额外的上下文信息。可以使用 fmt.Errorf 和 %w 标志来包装错误。

func doSomething() error {err := doAnotherThing()if err != nil {return fmt.Errorf("failed to do something: %w", err)}return nil
}

使用 %w 标志包装的错误可以通过 errors.Is 或 errors.As 进行检查。

错误检查和断言

  • errors.Is:用于检查错误链中是否包含特定错误。
if errors.Is(err, targetErr) {// err 是 targetErr 或其包装
}
  • errors.As:用于将错误断言为特定类型。
var targetErr *MyError
if errors.As(err, &targetErr) {// err 是 *MyError 类型
}

Defer函数 

在Go语言中,defer 语句用于确保在函数返回之前执行特定的函数调用(延迟调用)。这对于资源清理、解锁、关闭文件描述符等场景非常有用,因为它可以确保这些操作总是被执行,即使在发生错误或者提前返回的情况下。

defer 语句的语法

defer 语句的语法非常简单:

defer 函数调用

这里的“函数调用”可以是任何函数或方法调用,包括内置函数、用户定义的函数以及方法。

defer 语句的行为

以下是 defer 语句的一些关键行为:

  1. 延迟执行defer 语句会在包含它的函数即将返回之前执行。无论函数是正常返回还是由于错误返回,defer 语句都会被执行。

  2. 参数评估defer 语句中的参数(如果有的话)是在执行 defer 语句时评估的,而不是在执行延迟函数时。

  3. 执行顺序:如果同一个函数中有多个 defer 语句,它们会按照后进先出(LIFO)的顺序执行。也就是说,最后一个 defer 的函数调用会最先执行。

下面是一个使用 defer 的示例,展示了如何在文件操作后确保文件被关闭:

func main() {f, err := os.Open("file.txt")if err != nil {log.Fatalf("failed to open file: %s", err)}defer f.Close() // 延迟关闭文件// 读取文件内容...
}

在这个例子中,无论后续的文件操作是否成功,f.Close() 都会在 main 函数返回之前被调用。

嵌套 defer

你可以在 defer 语句中调用另一个函数,该函数内部也可以有 defer 语句:

func main() {defer func() {// 这里的 defer 会最后执行defer fmt.Println("Deferred inside defer")fmt.Println("Outer defer")}()fmt.Println("Hello")
}

在这个例子中,输出顺序将是:

Hello
Outer defer
Deferred inside defer

这是因为内部的 defer 语句在执行外部的 defer 时被调用,然后才执行外部的 defer 语句。

Recover

在Go语言中,recover 是一个内建函数,它用于捕获运行时的恐慌(panic)。当程序发生恐慌时,正常的函数执行流程会被立即停止,程序将进入恐慌状态,执行所有已注册的延迟函数(deferred functions)。如果在延迟函数中调用了 recover,则可以捕获当前的恐慌,并且使程序恢复正常执行而不是崩溃。

recover 的语法

recover 的语法非常简单,它不接受任何参数,并且返回一个 interface{} 类型的值:

recover()

如果 recover 被直接调用(不在延迟函数中),它将不会做任何事情,并且返回 nil

recover 的行为

下面是 recover 的一些关键行为:

  1. 只能在延迟函数中生效recover 只能在 defer 语句调用的延迟函数中使用。如果 recover 在其他任何地方被调用,它将不会捕获任何恐慌。

  2. 返回恐慌值:如果当前的goroutine正处于恐慌状态,recover 将捕获到恐慌值,并返回该值,使得程序可以继续执行正常的流程。

  3. 恢复正常执行:一旦 recover 成功捕获了恐慌,程序将不会继续传播恐慌,而是会继续执行延迟函数中的剩余代码,然后返回到恐慌发生点的函数调用者。

下面是一个使用 recover 的示例:

package mainimport "fmt"func main() {defer func() {if r := recover(); r != nil {fmt.Println("Recovered in main:", r)}}()fmt.Println("Start")panic("Something bad happened")fmt.Println("End") // 这行代码不会被执行
}// 输出:
// Start
// Recovered in main: Something bad happened

在这个例子中,panic 触发了一个错误,但随后在延迟函数中通过 recover 被捕获。因此,程序没有崩溃,而是打印了恢复信息。

 异常处理对比

  defer 和 recover 是Go语言中用于错误处理的机制,而 try-catch 是许多其他面向对象编程语言(如Java、C++、C#等)中用于异常处理的机制。Go语言最为人所诟病的就是满屏的if err != nil这样的语法,而在其他的高级语言中大多是一个try{}catch{}finally{}直接捕获了所有的代码。这是由于在Go中建议对每个error都单独做处理,如果非要实现try catch的方法,更多时候是通过defer recover实现的。

defer 和 recover

  • 错误处理:在Go中,defer 和 recover 通常用于处理恐慌(panic),这类似于其他语言中的异常。defer 语句用于延迟执行一个函数调用,而 recover 用于捕获恐慌。

  • 使用场景defer 和 recover 通常用于处理运行时错误,这些错误是意外的,并且可能导致程序无法继续执行。

  • 语法

    func someFunction() {defer func() {if r := recover(); r != nil {// 处理恐慌}}()// 可能发生恐慌的代码
    }
    
  • 显式性defer 和 recover 需要显式地声明,它们不会自动捕获错误。

  • 性能defer 和 recover 的性能开销相对较低,因为它们只在发生恐慌时才执行。

try-catch

  • 异常处理try-catch 用于捕获和处理异常。异常通常表示程序运行时的错误或异常情况。

  • 使用场景try-catch 用于处理各种错误,包括运行时错误和可预见的错误情况。

  • 语法(以Java为例):

    try {// 可能抛出异常的代码
    } catch (SomeException e) {// 处理异常
    }
    
  • 自动捕获try-catch 块自动捕获在 try 块中抛出的异常,无需显式声明。

  • 性能try-catch 可能会有更高的性能开销,因为异常的抛出和捕获涉及堆栈跟踪的创建。

对比

  • 错误/异常类型:Go使用 error 类型来表示可预见的错误,而 panic 和 recover 用于处理意外情况。try-catch 通常用于处理所有类型的错误和异常。

  • 传播机制:在Go中,错误必须显式地返回和检查。恐慌会自动传播,直到被 recover 捕获。在 try-catch 机制中,异常会自动向上传播,直到遇到匹配的 catch 块。

  • 资源管理:Go使用 defer 来确保资源被正确释放,而其他语言可能使用 finally 块或RAII(Resource Acquisition Is Initialization)。

  • 控制流try-catch 允许异常改变程序的控制流,而Go的 defer 和 recover 主要用于恢复程序状态,而不是改变控制流。

  • 代码风格:Go鼓励通过返回错误值来进行错误处理,这有助于保持代码的清晰和简洁。try-catch 可能导致代码中异常处理逻辑的分散。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/44941.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何防止第三方DLL注入自己的进程?

PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY 结构 struct _PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY {union {DWORD Flags;struct {DWORD MicrosoftSignedOnly : 1;DWORD StoreSignedOnly : 1;DWORD MitigationOptIn : 1;DWORD AuditMicrosoftSignedOnly : 1;DWORD Audit…

C语言 ——— 实用调试技巧(Visual Studio)

目录 Debug 和 Release 的区别 F10 --- 逐过程调试 & F11 --- 逐语句调试 F9 --- 新建/切换断点 & F5 --- 开始调试 shift F5 & ctrl F5 Debug 和 Release 的区别 Debug:通常为调试版本,它包含调试信息,并且不作任何优化…

一 GD32 MCU 开发环境搭建

GD32 系列为通用型 MCU ,所以开发环境也可以使用通用型的 IDE ,目前使用较多的是 KEIL、 IAR 、 GCC 和 Embedded Builder ,客户可以根据个人喜好来选择相应的开发环境。 目录 1、使用 Keil 开发 GD32 目前市面通用的MDK for ARM版本有Kei…

华为OD机试真题2024版-路口最短时间问题

题目描述 假定街道是棋盘型的,每格距离相等,车辆通过每格街道需要时间均为 timePerRoad; 街道的街口(交叉点)有交通灯,灯的周期 T(lights[row][col]) 各不相同; 车辆可直行、左转和右转&…

企业网三层架构

企业网三层架构:是一种层次化模型设计,旨在将复杂的网络设计分成三个层次,每个层次都着重于某些特定的功能,以提高效率和稳定性。 企业网三层架构层次: 接入层:使终端设备接入到网络中来,提供…

Python爬虫教程第二篇:进阶技巧与实战案例

Python爬虫教程第二篇:进阶技巧与实战案例 在上一篇教程中,我们学习了Python爬虫的基础概念、基本流程以及一个简单的入门实践案例。本篇教程将带领大家进一步探索Python爬虫的进阶技巧,并提供一个实战案例,帮助大家提升爬虫技能…

Android12 MultiMedia框架之GenericSource extractor

前面两节学习到了各种Source的创建和extractor service的启动,本节将以本地播放为例记录下GenericSource是如何创建一个extractor的。extractor是在PrepareAsync()方法中被创建出来的,为了不过多赘述,我们直接从GenericSource的onPrepareAsyn…

Mojolicious命令行工具:自动化Web开发的瑞士军刀

Mojolicious是一个高性能的、基于Perl的Web开发框架,它提供了一整套工具来简化Web开发流程。其中,Mojolicious的命令行工具集是其强大功能的一部分,允许开发者快速生成项目模板、运行开发服务器、执行各种开发任务等。本文将详细介绍Mojolici…

qt 自定义信号号槽 简单举例

在Qt中,自定义信号和槽是一种非常灵活的方式来处理对象之间的通信。以下是一个简单的例子,展示了如何定义和使用自定义的信号和槽。 首先,我们定义一个名为MyClass的类,该类继承自QObject,并声明一个自定义信号和一个…

13_Shell系统函数

13_Shell系统函数和自定义函数 一、系统函数 basename 获取文件名 #!/bin/bash#basename 相对路径文件名 basename ./1.sh#basename 绝对路径文件名 basename /tmp/1.sh#basename 去除文件后缀名 basename /tmp/1.sh .shdirname 获取文件所在目录名 #!/bin/bash#dirname 相对路…

Redis持久化RDB,AOF

目 录 CONFIG动态修改配置 慢查询 持久化 在上一篇主要对redis的了解入门,安装,以及基础配置,多实例的实现:redis的安装看我上一篇: Redis安装部署与使用,多实例 redis是挡在MySQL前面的,运行在内存…

Week 6-杨帆-学习总结

- 46 语义分割和数据集 语义分割概念 语义分割是一种计算机视觉任务,其目标是将图像分割成属于不同语义类别的区域。与目标检测不同,语义分割关注的是像素级别的标注和预测,能够识别并理解图像中每一个像素的内容。这使得语义分割在理解图像…

产品经理-研发流程-敏捷开发-迭代-需求评审及产品规划(15)

敏捷开发是以用户的需求进化为核心,采用迭代、循序渐进的方法进行软件开发。 通俗来说,敏捷开发是一个软件开发流程,是一个采用了迭代方法的开发流程 简单来说,迭代就是把一个大产品拆分出一些最小的实现单位。完成不同的迭代就最…

机器学习筑基篇,Jupyter Notebook 精简指南

[ 知识是人生的灯塔,只有不断学习,才能照亮前行的道路 ] 0x00 Jupyter Notebook 简明指南 描述:前面我们已经在机器学习工作站(Ubuntu 24.04 Desktop Geforce RTX 4070Ti SUPER)中安装 Anaconda 工具包,其…

老物件线上3D回忆展拓宽了艺术作品的展示空间和时间-深圳华锐视点

在数字技术的浪潮下,3D线上画展为艺术家们开启了一个全新的展示与销售平台。这一创新形式不仅拓宽了艺术作品的展示空间,还为广大观众带来了前所未有的观赏体验。 3D线上画展制作以其独特的互动性,让艺术不再是单一的视觉享受。在这里&#x…

数据处理-Matplotlib 绘图展示

文章目录 1. Matplotlib 简介2. 安装3. Matplotlib Pyplot4. 绘制图表1. 折线图2. 散点图3. 柱状图4. 饼图5. 直方图 5. 中文显示 1. Matplotlib 简介 Matplotlib 是 Python 的绘图库,它能让使用者很轻松地将数据图形化,并且提供多样化的输出格式。 Ma…

如何定义版本号--语义化版本

前言 版本号(version number)是版本的标识号。每一个操作系统(或广义的讲,每一个软件)都有一个版本号。版本号能使用户了解所使用的操作系统是否为最新的版本以及它所提供的功能与设施。 例如在Python项目依赖中会看到 requires-python &q…

zdppy+onlyoffice实现重命名文件的功能

参考文档:https://api.onlyoffice.com/zh/editors/rename 步骤图: 实现步骤: 用户在 文档编辑器中为文档指定一个新名称。 文档编辑器 将文档的新名称通知给 文档管理器。 文档管理器 将文档的新名称发送到 文档存储服务,在这里…

使用jsencrypt在web前端对字符串进行Ras加密

话不多说&#xff0c;上代码 实例代码 下面方法&#xff0c;在网页中先引入jsencrypt.min.js。然后调用ToEncrypt方法示例输出加密&#xff0c;解密后的结果。 <script src"/js/jsencrypt.min.js"></script> //加密测试function ToEncrypt(){// 假设…

synchronized关键字详解

文章目录 synchronized使用示例实现原理锁的升级synchronized与可见性synchronized与原子性synchronized与有序性 synchronized synchronized是Java提供的关键字译为同步&#xff0c;是Java中用于实现线程同步的一种机制。它可以确保在同一时间只有一个线程能够执行某段代码&a…