Go异常处理机制

ops/2024/9/23 22:40:31/

Go 语言的异常处理机制一直是社区讨论和争议的焦点。Go 采用了一种独特的错误处理方式,主要通过返回错误值来处理异常情况,而不是使用传统的 try-catch-finally 异常处理模型。以下是一些社区中关于 Go 异常处理的常见争议点:

1.社区争论意见

  1. 显式错误检查

    支持者: 认为显式错误检查可以减少因忽略错误处理而导致的隐蔽错误,提高代码的可读性和可维护性。
    反对者: 则认为,强制的显式错误检查会导致代码冗余,尤其是在有深层嵌套调用时。
  2. 缺乏泛型错误处理

    反对者:在 Go 1.0 至 Go 1.16 版本中,错误处理缺乏泛型机制,导致开发者需要对每个错误类型进行单独处理。
    支持者: Go 1.17 引入了错误封装和错误链的概念,这在一定程度上缓解了这个问题。
  3. Panic 和 Recover 的使用

    反对者:panic 和 recover 用于处理运行时的异常情况,但它们的使用在社区中存在争议。一些开发者认为 panic 应该仅用于不可恢复的错误,而其他人可能会滥用它们来处理常规的错误情况。
    recover 的使用也受到争议,因为过度使用可能会使控制流复杂化,并且难以追踪程序的执行路径。
  4. 错误传播

    反对者:在深层嵌套的函数调用中,错误需要逐层传递,这可能会导致代码难以阅读和维护。
  5. 与面向对象语言的对比

    反对者:来自面向对象编程背景的开发者可能会对 Go 的错误处理方式感到不适应,因为它们习惯于使用异常处理机制。

特别是调用栈很深的情况下,例如下面的演示代码:

package mainimport ("fmt"
)func divide(dividend, divisor float64) (float64, error) {if divisor == 0 {return 0, fmt.Errorf("除数不能为0")}return dividend / divisor, nil
}func middleFun(dividend, divisor float64) (float64, error) {result, err := divide(dividend, divisor)if err != nil {return result, err}// do sthreturn result, nil
}func outerFun(dividend, divisor float64) (float64, error) {result, err := middleFun(dividend, divisor)if err != nil {return result, err}// do sthreturn result, nil
}func main() {result, err := outerFun(10, 3)if err != nil {fmt.Printf("发生错误: %v\n", err)return}fmt.Printf("结果: %v\n", result)
}

当然,仁者见仁,习惯Go开发的人会觉得这种设计很哲学,甚至当听到别人说这种设计不好的还会嗤之以鼻,心里想,肯定是java或者别的OOP语言转过来的。

2. Java异常处理

2.1.异常与错误

在Java中,异常(Exception)和错误(Error)都是Throwable类的子类,但它们在Java异常处理机制中扮演不同的角色

异常是程序正常运行中出现的非预期情况,通常是可以被程序处理的。通过try-catch块捕获并处理异常,以避免程序异常终止,例如下面的代码示例

try {// 可能抛出异常的代码
} catch (IOException e) {// 处理IOException
} finnaly {// 不管有没有异常,这里都会执行
}

错误是程序运行时遇到的严重问题,通常是编程错误或系统问题,如OutOfMemoryErrorStackOverflowError。可能会导致程序崩溃退出。

异常与错误的比较

  • 可恢复性:异常通常是可恢复的,而错误通常是不可恢复的。
  • 处理方式:异常需要程序员显式捕获和处理,错误则通常不被捕获。
  • 使用场景:异常用于控制程序流程中的异常情况,错误用于指示程序无法处理的严重问题。
  • 编译检查:受检异常需要编译时检查,错误不需要。

2.2.受检异常与运行期异常

受检异常是编译时检查的异常,它们通常是可预见的异常情况,如 IOExceptionSQLException 等。

  • 在方法中通过 throws 关键字声明抛出,方法调用者必须显式捕捉异常,并进行相关处理。
  • 强制程序员处理这些异常,以避免程序在运行时因未处理的异常而意外终止。
public void readFile(String path) throws IOException {// 可能抛出 IOException 的代码
}

运行时异常是编译时不检查的异常,通常是编程错误导致的,如 NullPointerExceptionIndexOutOfBoundsException 等。

  • 不需要在方法中声明抛出,也不需要强制捕获,但建议捕获并处理以提高程序的健壮性。
  • 指出程序中的逻辑错误或不正确的使用情况,鼓励程序员在开发过程中修复这些问题。
public void processArray(int[] array, int index) {if (index >= array.length) {throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + array.length);}// 正常处理数组元素
}

3. Go异常处理

3.1.通过错误码返回值

Go 语言中没有传统的异常(exception)机制,而是使用返回错误值的方式来处理错误情况。Go函数允许多重返回值,可以申明两个返回值,一个是函数的结果,另一个是错误对象。

result, err := SomeFunction()
if err != nil {// 处理错误
}

Go 中的错误是一个内置的接口类型 error,任何类型都可以实现这个接口,只要它们提供了一个 Error() 方法返回错误信息字符串。

type MyError struct {// 错误相关的字段
}func (e *MyError) Error() string {return "my error message"
}

使用 fmt.Errorf 可以创建新的错误,并在其中包含原始错误的信息

err := fmt.Errorf("wrap error: %w", originalError)

函数返回错误码,可以类比java的受检异常。不同的是,java编译器会强制开发者捕捉异常,而Go的函数返回值,IDE只是警告提示,容易被人忽略。

3.2.panic函数

Go倾向于使用简洁的控制结构和显式的错误检查。但如果程序设计不合理或者考虑不周到,没有返回某些错误,这对于某些底层代码很有可能是致命的,可能会引起程序奔溃。这个时候,可以祭出panic函数作为兜底。

在 Go 语言中,panic 是一个内置的关键词,用于异常情况,当程序遇到无法恢复的错误时,可以通过调用 panic 来立即中断当前函数的执行,并且开始逐层向上 unwind 调用栈,同时清理 defer 语句。panic 通常用于以下情况:

  1. 不可恢复的错误:当程序遇到无法处理的错误,比如违反了程序的预期条件。

  2. 触发异常流程panic 触发了一个异常流程,这会导致当前 goroutine 停止执行,并开始执行栈展开。

  3. recover 配合使用panic 可以与 recover 一起使用来实现错误恢复。recover 能够捕获 panic,并恢复程序的执行。

  4. 栈追踪:当 panic 发生时,Go 运行时会打印出栈追踪信息,这对于调试程序非常有帮助。

  5. 延迟函数(defer:在 panic 过程中,任何注册的延迟函数(使用 defer 关键字注册的)都会被执行。

  6. 程序终止:如果程序中的 panic 没有被捕获和恢复,程序将终止执行。

  7. 使用场景panic 通常用于测试代码中,或者在初始化阶段检测到严重问题时。在正常的业务逻辑中,推荐使用错误返回值来处理错误情况。

例如下面的代码:

func someFunction() {if someCondition {panic("An unexpected condition occurred")}// ...
}

搭配recover

  • recover 是一个内置函数,只能在延迟函数中使用,并且只有在 panic 发生时才有效果。
  • 使用 recover 可以捕获 panic,并恢复程序执行,但通常只在调试或资源清理时使用。
  • defer语句不管有没有发生panic会执行,但recover只有发生panic才会触发。
  • defer可以类比java的finnalypanic+recover可以类比java的try catch
defer func() {if r := recover(); r != nil {log.Printf("Recovered in someFunction, error: %v", r)}
}()
  • Go建议慎重使用panic, 而java的try catch使用非常广泛,有些程序员甚至不管三七二十一,在每个方法都加一个try catch,这只能说是一种“反模式”。

http://www.ppmy.cn/ops/106347.html

相关文章

工商业光伏难点解析

1、初始投资较高:工商业光伏项目需要一定的建设成本,包括光伏组件、逆变器、支架等设备采购及安装费用,这对企业资金投入是一个考验,前期想要把控好安装的成本,可以用鹧鸪云的光伏业务管理软件,前期直接可以…

ffmpeg音视频开发从入门到精通——常用结构体介绍(一)

在这里插入代码片[toc] FFmpeg头文件介绍 包含了FFmpeg库的头文件&#xff0c;这些头文件提供了编解码器、多媒体格式处理等功能。 #ifdef __cplusplus extern "C" { #endif // 包含FFmpeg的头文件 #include <libavcodec/avcodec.h> #include <libavform…

MySQL学习记录

SQL语句 通用语法 学习具体的SQL语句之前&#xff0c;先来了解一下SQL语言的同于语法。 1). SQL语句可以单行或多行书写&#xff0c;以分号结尾。 2). SQL语句可以使用空格/缩进来增强语句的可读性。 3). MySQL数据库的SQL语句不区分大小写&#xff0c;关键字建议使用大写。 …

毒枸杞事件启示录:EasyCVR视频AI智能监管方案如何重塑食品卫生安全防线

一、方案背景 近年来&#xff0c;食品安全问题频发&#xff0c;引发了社会各界的广泛关注。其中&#xff0c;毒枸杞事件尤为引人关注。新闻报道&#xff0c;在青海格尔木、甘肃靖远等地&#xff0c;部分商户为了提升枸杞的品相&#xff0c;违规使用焦亚硫酸钠和工业硫磺进行“…

nova学习小结

写在前面 基于yoga版本源码进行学习 api流程 该入口从配置加载app 看下osapi_compute流程&#xff0c;v2.1用oscomputeversion_v2 将api_paste.ini内容加载为server&#xff0c;再调oslo_service.service.ProcessLauncher拉起server。将api-paste加载为server&#xff0c;ser…

深入理解XML与JSON:数据交换格式的比较与应用

1.XML与JSON的概念 XML是一种标记语言&#xff0c;它允许开发者定义自己的标签来描述数据。其结构由元素、属性和文本内容组成。格式如下&#xff1a; <bookstore><book><title>XML Developers Guide</title><author>John Doe</author>&…

2024 高教社杯 数学建模国赛 (B题)深度剖析|生产过程中的决策问题|数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题&#xff01; CS团队倾注了大量时间和心血&#xff0c;深入挖掘解…

Spring Security与Apache Shiro:Java安全框架的比较

引言 在Java企业级应用开发中&#xff0c;安全性是一个不可忽视的重要方面。随着Web应用的复杂性日益增加&#xff0c;选择合适的安全框架对于保护应用免受未授权访问和其他安全威胁至关重要。Spring Security和Apache Shiro是两个广泛使用的Java安全框架&#xff0c;它们提供…