《快学 Go 语言》第 10 课 —— 错误与异常

go语言的异常处理机制 errNO int// 定义错误方法如果我们直接打印一个error对象,实际上调用的是error.Error()Go语言异常机制是一种非常重要的特性,它可以帮助我们诊断程序中的错误,并提供一种优雅的方式来处理这些错误。在使用异常时,需要注意不要滥用异常,只在必要的时候抛出异常和捕获异常,提供足够的上下文信息,并且不要忽略任何异常。如果合理使用异常,可以让我们的代码更加健壮、清晰和可读。 阅读详情

Go 语言的异常处理语法绝对是独树一帜,在我见过的诸多高级语言中,Go 语言的错误处理形式就是一朵奇葩。一方面它鼓励你使用 C 语言的形式将错误通过返回值来进行传递,另一方面它还提供了高级语言一般都有的异常抛出和捕获的形式,但是又不鼓励你使用这个形式。后面我们统一将返回值形式的称为「错误」,将抛出捕获形式的称为「异常」。

Go 语言的错误处理在业界饱受批评,不过既然我们已经入了这个坑,那还是好好蹲着吧。

错误接口

Go 语言规定凡是实现了错误接口的对象都是错误对象,这个错误接口只定义了一个方法。

type error interface {
  Error() string
}

编写一个错误对象很简单,写一个结构体,然后挂在 Error() 方法就可以了。

package main

import "fmt"

type SomeError struct {
    Reason string
}

func (s SomeError) Error() string {
    return s.Reason
}

func main() {
    var err error = SomeError{"something happened"}
    fmt.Println(err)
}

---------------
something happened

package errors

func New(text string) error {
    return &errorString{text}
}

type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

var err = errors.New("something happened")

var thing = "something"
var err = fmt.Errorf("%s happened", thing)

错误处理首体验

在 Java 语言里,如果遇到 IO 问题通常会抛出 IOException 类型的异常,在 Go 语言里面它不会抛异常,而是以返回值的形式来通知上层逻辑来处理错误。下面我们通过读文件来尝试一下 Go 语言的错误处理,读文件需要使用内置的 os 包。

package main

import "os"
import "fmt"

func main() {
    // 打开文件
    var f, err = os.Open("main.go")
    if err != nil {
        // 文件不存在、权限等原因
        fmt.Println("open file failed reason:" + err.Error())
        return
    }
    // 推迟到函数尾部调用,确保文件会关闭
    defer f.Close()
    // 存储文件内容
    var content = []byte{}
    // 临时的缓冲,按块读取,一次最多读取 100 字节
    var buf = make([]byte, 100)
    for {
        // 读文件,将读到的内容填充到缓冲
        n, err := f.Read(buf)
        if n > 0 {
            // 将读到的内容聚合起来
            content = append(content, buf[:n]...)
        }
        if err != nil {
            // 遇到流结束或者其它错误
            break
        }
    }
    // 输出文件内容
    fmt.Println(string(content))
}

-------
package main

import "os"
import "fmt"
.....

var score, ok := scores["apple"]

第三个需要注意的地方是 append 函数参数中出现了 … 符号。在切片章节,我们知道 append 函数可以将单个元素追加到切片中,其实 append 函数可以一次性追加多个元素,它的参数数量是可变的。

var s = []int{1,2,3,4,5}
s = append(s,6,7,8,9)

第四个需要注意的地方是读文件操作 f.Read() ,它会将文件的内容往切片里填充,填充的量不会超过切片的长度(注意不是容量)。如果将缓冲改成下面这种形式,就会死循环!

var buf = make([]byte, 0, 100)

体验 Redis 的错误处理

上面读文件的例子并没有让读者感受到错误处理的不爽,下面我们要引入 Go 语言 Redis 的客户端包,来真实体验一下 Go 语言的错误处理有多让人不快。

使用第三方包,需要使用 go get 指令下载这个包,该指令会将第三方包放到 GOPATH 目录下。

go get github.com/go-redis/redis

package main

import "fmt"
import "strconv"
import "github.com/go-redis/redis"

func main() {
 // 定义客户端对象,内部包含一个连接池
    var client = redis.NewClient(&redis.Options {
        Addr: "localhost:6379",
    })

    // 定义三个重要的整数变量值,默认都是零
    var val1, val2, val3 int

    // 获取第一个值
    valstr1, err := client.Get("value1").Result()
    if err == nil {
        val1, err = strconv.Atoi(valstr1)
        if err != nil {
            fmt.Println("value1 not a valid integer")
            return
        }
    } else if err != redis.Nil {
        fmt.Println("redis access error reason:" + err.Error())
        return
    }

    // 获取第二个值
    valstr2, err := client.Get("value2").Result()
    if err == nil {
        val2, err = strconv.Atoi(valstr2)
        if err != nil {
            fmt.Println("value1 not a valid integer")
            return
        }
    } else if err != redis.Nil {
        fmt.Println("redis access error reason:" + err.Error())
        return
    }

    // 保存第三个值
    val3 = val1 * val2
    ok, err := client.Set("value3",val3, 0).Result()
    if err != nil {
        fmt.Println("set value error reason:" + err.Error())
        return
    }
    fmt.Println(ok)
}

------
OK

另外还有一个需要特别注意的是因为字符串的零值是空串而不是 nil,你不好从字符串内容本身判断出 Redis 是否存在这个 key 还是对应 key 的 value 为空串,需要通过返回值的错误信息来判断。代码中的 redis.Nil 就是客户端专门为 key 不存在这种情况而定义的错误对象。

相比于写习惯了 Python 和 Java 程序的朋友们来说,这样繁琐的错误判断简直太地狱了。不过还是那句话,习惯了就好。

异常与捕捉

Go 语言提供了 panic 和 recover 全局函数让我们可以抛出异常、捕获异常。它类似于其它高级语言里常见的 throw try catch 语句,但是又很不一样,比如 panic 函数可以抛出来任意对象。下面我们看一个使用 panic 的例子

package main

import "fmt"

var negErr = fmt.Errorf("non positive number")

func main() {
    fmt.Println(fact(10))
    fmt.Println(fact(5))
    fmt.Println(fact(-5))
    fmt.Println(fact(15))
}

// 让阶乘函数返回错误太不雅观了
// 使用 panic 会合适一些
func fact(a int) int{
    if a <= 0 {
        panic(negErr)
    }
    var r = 1
    for i :=1;i<=a;i++ {
        r *= i
    }
    return r
}

-------
3628800
120
panic: non positive number

goroutine 1 [running]:
main.fact(0xfffffffffffffffb, 0x1)
    /Users/qianwp/go/src/github.com/pyloque/practice/main.go:16 +0x75
main.main()
    /Users/qianwp/go/src/github.com/pyloque/practice/main.go:10 +0x122
exit status 2

package main

import "fmt"

var negErr = fmt.Errorf("non positive number")

func main() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("error catched", err)
        }
    }()
    fmt.Println(fact(10))
    fmt.Println(fact(5))
    fmt.Println(fact(-5))
    fmt.Println(fact(15))
}

func fact(a int) int{
    if a <= 0 {
        panic(negErr)
    }
    var r = 1
    for i :=1;i<=a;i++ {
        r *= i
    }
    return r
}

-------
3628800
120
error catched non positive number

defer func() {
  if err := recover(); err != nil {
   fmt.Println("error catched", err)
  }
}()

defer f.Close()

func panic(v interface{})
func recover() interface{}

defer func() {
    if err := recover(); err != nil {
        if err == negErr {
            fmt.Println("error catched", err)
        } else {
            panic(err)  // rethrow
        }
    }
}()

异常的真实应用

Go 语言官方表态不要轻易使用 panic recover,除非你真的无法预料中间可能会发生的错误,或者它能非常显著地简化你的代码。简单一点说除非逼不得已,否则不要使用它。

在一个常见的 Web 应用中,不能因为个别 URL 处理器抛出异常而导致整个程序崩溃,就需要在每个 URL 处理器外面包括一层 recover() 来恢复异常。

640?wx_fmt=png
图片

在 json 序列化过程中,逻辑上需要递归处理 json 内部的各种类型,每一种容器类型内部都可能会遇到不能序列化的类型。如果对每个函数都使用返回错误的方式来编写代码,会显得非常繁琐。所以在内置的 json 包里也使用了 panic,然后在调用的最外层包裹了 recover 函数来进行恢复,最终统一返回一个 error 类型。

640?wx_fmt=png
图片

你可以想象一下,内置 json 包的开发者在设计开发这个包的时候应该也是纠结的焦头烂额,最终还是使用了 panic 和 recover 来让自己的代码变的好看一些。

多个 defer 语句

有时候我们需要在一个函数里使用多次 defer 语句。比如拷贝文件,需要同时打开源文件和目标文件,那就需要调用两次 defer f.Close()。

package main

import "fmt"
import "os"

func main() {
    fsrc, err := os.Open("source.txt")
    if err != nil {
        fmt.Println("open source file failed")
        return
    }
    defer fsrc.Close()
    fdes, err := os.Open("target.txt")
    if err != nil {
        fmt.Println("open target file failed")
        return
    }
    defer fdes.Close()
    fmt.Println("do something here")
}

package main

import "fmt"
import "os"

func main() {
    fsrc, err := os.Open("source.txt")
    if err != nil {
        fmt.Println("open source file failed")
        return
    }
    defer func() {
        fmt.Println("close source file")
        fsrc.Close()
    }()

    fdes, err := os.Open("target.txt")
    if err != nil {
        fmt.Println("open target file failed")
        return
    }
    defer func() {
        fmt.Println("close target file")
        fdes.Close()
    }()
    fmt.Println("do something here")
}

--------
do something here
close target file
close source file

640?wx_fmt=png

阅读《快学 Go 语言》更多章节,扫一扫关注公众号「码洞」

【Golang】深入解读Go语言中的错误(error)与异常(panic) 在Go语言编程中,错误和异常处理是非常重要的一部分。Go语言通过其独特的错误处理机制和异常处理模型,提供了一种既清晰又高效的方式来处理程序运行时的错误和异常情况。 阅读详情

相关推荐

Go基础:Go语言错误和异常详解

Go语言采用显式错误处理机制,通过返回error类型表示错误,而非传统try-catch结构。error是一个内置接口,可通过errors.New()或fmt.Errorf()创建错误,支持自定义错误类型携带更多信息。对于严重错误,使用panic中断程序,recover在defer中捕获异常。defer确保资源释放等操作在函数返回前执行。建议优先使用错误返回值,保持错误信息清晰,谨慎使用panic和recover,仅在必要时处理严重错误。这种机制强调错误显式处理,提高代码可读性和健壮性。

数据知道的博客 6157

go语言错误类型

1.考虑的error的使用原因 error类型本身就是一个预定义好的接口,里面定义了一个method type error interface { Error() string//定义一个error()的方法 } 2如何完成error的第一个简单的调用,输出的格式err:=fmt.Errorf("%s",“error:this is error”) func main() { //方法一: //采用errors包的New方法 返回一个err的类型 var err error = errors.New(“th

华龙在飞翔 1172

RT-Thread与LWIP协议栈在GD32F450XX上的移植实战:从FreeRTOS到RT-Thread的切换指南

本文详细介绍了将LWIP协议栈从FreeRTOS移植到RT-Thread系统,并在GD32F450XX开发板上实现的完整实战指南。重点解析了sys_arch适配层、邮箱与信号量接口的关键改造,以及网络驱动ethernetif.c的适配步骤,帮助开发者解决线程通信差异,实现稳定网络连接。

linux6sysadmin的博客 824

recover的用法

Recover的用法: defer可以读取有名返回值 理论:如果不进行recover,便会导致整个程序挂掉,Recover()用法是:将Recover()写在defer中,并且在可能发生panic的地方之前,先调用此defer的东西(让系统方法域结束时,有代码要执行。)当程序遇到panic的时候(当然,也可以正常的调用出现的异常情况),系统将跳过后面的代码,进入defer,如果defer函数中recover(),则返回捕获到的panic的值。 总结:使用recover()捕捉panic异常的时候,则需要de

华龙在飞翔 3113

Synopsys工具链实战:如何用VCS、DC、ICC和Calibre跑通你的第一个数字IC设计项目?

本文详细介绍了如何使用Synopsys工具链(VCS、Design Compiler、ICC和Calibre)完成数字IC设计项目,从RTL代码到GDSII版图的全流程。涵盖环境配置、RTL设计验证、逻辑综合、物理实现及签核验证等关键步骤,帮助工程师掌握高效的数字IC设计流程。

weixin_42525005的博客 197

基于STM32的便携式心率血氧监测系统 | 保姆式教程 开源可复刻

本项目基于STM32F103C8T6核心控制器,完整实现了便携式心率血氧监测系统的全功能开发,覆盖MAX30102光电传感器驱动、PPG信号处理、心率血氧解算算法、人机交互、数据存储、报警保护、低功耗管理等核心功能,全程采用无跳步保姆式教学,从硬件选型、接线、CubeMX配置、代码开发、调试优化全流程拆解,跟着步骤一步步操作,就能顺利完成项目复刻

ZhongWeiXiang9的博客 767

《Go语言学习笔记》 - 第四章 - 错误处理

6. 错误处理 首先我们要区分错误与异常,错误指的是在可能出现问题的地方出现了问题,而异常指的是在不应该出现问题的地方出现了问题。Go语言中,错误和异常的处理机制如下: 6.1 error Go语言中的错误处理,官方推荐的做法是返回error状态,按照惯例,error总是最后一个返回参数。标准库提供了相关的创建函数,可方便的创建包含简单错误文本的error对象。 import ( "errors...

来自东印度的人 416

使用CNN/ResNet实现自动调制识别(RML2018.01a)

本文基于RML2018.01a大型数据集,给出CNN及其变体ResNet的信号识别的实现方式(附代码) 目录一、CNN的代码实现1.1 导入包1.2 数据读取及数据集划分1.3 构建CNN模型1.4 模型训练1.5 测试及模型可视化二、ResNet的代码实现 RML2018.01a数据集信息如下: 数据集来源 Over-the-Air Deep Learning Based Radio Signal Classification 调制类型 19种数字调制方式: 32PSK, 16APSK,

QAQIknow的博客 1万+

golang 错误处理

文章目录panic 和 recovererror错误和异常 panic 和 recover panic 和 recover 的函数签名如下: panic(i nterface{}) revover()interface{} panic的引发 1.程序主动调用panic函数。 2.程序产生运行时错误,由运行时检测并抛出。 发生panic后,程序会从调用panic的函数位置或发生panic的地方立即返回,逐层向上执行函数的defer语句,然后逐层打印函数调用堆栈,直到被recover捕获或运行到最外层

qq_49723651的博客 1672

Autosar PNC网络管理配置(1)-基于ETAS软件

Autosar PNC网络管理配置(1)-基于ETAS软件

赞的博客 2730

YOLOv8改进 | 注意力篇 | 一文带你改进GAM、CBAM、CA、ECA等通道注意力机制和多头注意力机制

官方论文地址:官方论文地址点击此处即可跳转官方代码地址:官方代码地址点击此处即可跳转GAM旨在通过设计一种机制,减少信息损失并放大全局维度互动特征,从而解决传统注意力机制在通道和空间两个维度上保留信息不足的问题。GAM采用了顺序的通道-空间注意力机制,并对子模块进行了重新设计。具体来说,通道注意力子模块使用3D排列来跨三个维度保留信息,并通过一个两层的MLP增强跨维度的通道-空间依赖性。

Snu77的博客 4万+

2024年最全Go Error 嵌套到底是怎么实现的?,2024年最新Golang大厂高级面试题灵魂100问

其实,Go 目前对 Error 的处理方式也是充满争议的。不过,官方团队正在积极和社区交流,提出改进方法。相信在不久的将来,一定会找到更好的解决方案。终于有了让人满意的输出结果,每个函数都增加了必要的上下文信息,而且也符合对错误类型的判断。函数,附加上我们想添加的文本信息,使返回内容更明确,处理起来更灵活。这也就解决了上文提出的无法判断错误类型的问题。将上文中的程序进行改造,使其内容输出更丰富。这个例子输出了我们想要的结果,但是还不够。因此,Go 1.13 为我们提供了。类型做不同处理,就无法实现了。

2401_84920041的博客 417

基于深度学习的X光安检危险品检测系统(YOLOv8 + UI界面 + 数据集)

随着全球安全形势的变化,机场、车站、地铁等场所的安检工作日益重要。X光安检系统已经成为现代公共安全领域中不可或缺的部分,能够有效地检测物品的内部结构,识别潜在的危险品。然而,传统的X光图像分析方法仍然依赖人工操作,检测效率较低,并且易受到人为因素的影响。近年来,深度学习技术的应用在图像分析领域取得了显著的突破,特别是在目标检测任务中,YOLO(You Only Look Once)模型因其高效性和精度,成为了许多目标检测任务的首选模型。

YOLO 1061

Golang语言异常机制解析:错误策略与优雅处理

本文深入探讨了Go语言的错误策略与异常机制。作者:鼠鼠我捏,要死了捏

qq_35716689的博客 39万+

税务某局 webpack 登录接口逆向分析

我们再回到接口逻辑去看一下,发现每次请求前,还有两个接口,getpublickey和sendsm4,这里分析了一下调用逻辑,sm4这个接口用到一个参数是由publickey返回的一个参数而来,这个参数又经过内部加密处理,我们找到这个参数加密的地方,大概是在这个位置。我们还是顺着逻辑将加载器扣下来,然后用一个变量去接收,有了加载器,我们直接把加密代码扣下来做下处理,把一些用到的参数整理下如下。打上断点分析后,_utils_getuuid这个很长的变量也是加载器的一个函数,我们将这段代码也扣下来,如下。

码王吴彦祖的博客 402

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Go 语言中的错误和异常:设计理念与优势

在 Go 语言中,错误和异常有着明确的定义。错误(Error):通常表示一种可以预期的问题,是程序运行过程中的一种正常情况。例如,文件不存在、网络连接失败等。在 Go 语言中,函数通常会返回一个错误值来表示可能出现的问题。异常(Panic):表示一种不可预期的严重问题,通常是程序出现了无法处理的错误情况,例如数组越界、空指针引用等。当异常发生时,程序会立即停止当前执行流程,并开始回溯调用栈,寻找可以处理异常的代码。

yin的博客 1390

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

【go每日一题】golang异常、错误 {源码、实践、总结}

Go 的错误处理设计与其他语言的异常不同。Go 中的 error 就是一个普通的值对象,而其他语言如 Java 中的 Exception 将会造成程序控制流的终止和其他行为,Exception 与普通的值不同。虽然 Go 也有类似的异常机制 —— panic,但它仅用于报告完全无法预料的错误(可能有 Bug),而不应该是一个健壮程序应该返回的程序错误(这一点与 Java 等语言不同)。 在我们最常用的函数返回值类型就是这个接口类型 Go 将 error 设计为一个接口,只需要实现 Error() stri

YiGeiGiaoGiao的博客 1144

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

【Golang】go语言异常处理快速学习

Go语言常见的异常处理知识大全,快速入门学习,形成整体知识架构,在实际中应用分析go语言异常处理的机制

2301_79819426的博客 1532

Golang 错误与异常 总结与最佳实践

区分错误与异常 很赞成参考文章中知乎高赞中“达达”老哥说的,首先要理清错误与异常之间的区别。我在阅读之后我总结的区别如下: 区分点 错误 异常 语言层面 error panic 不处理的结果 可能会导致逻辑业务上的错误,或者进一步产生异常 进程异常退出 预见性 可以预见 不可预见 总的来说,在Go语言中,可以按照是否可以提前指导,很分明的区分了错误(可以提前知道)与异常(不能提前知道)。 不同语言之间的错误与异常 错误与异常,拿来和Python和Java相比的话,总结起来应该是这

weixin_38371073的博客 1210

简单介绍Go语言错误处理异常捕获+异常抛出

这篇文章主要介绍了Go语言错误处理异常捕获和异常抛出,Go语言的作者认为java等语言的错误处理底层实现较为复杂,就实现了函数可以返回错误类型以及简单的异常捕获,虽然简单但是也非常精妙,大大的提高了运行效率,下文需要的朋友可以参考一下 Go 语言追求简洁优雅,所以,Go 语言不支持传统的 try…catch…finally 这种处理。 Go 中引入的错误处理方式为:defer, panic, recover,也仅仅是错处处理的模拟 Go语言的作者认为java等语言的错误处理底层实现较为复杂,就实现了

Free雅轩的博客 3268

【GO语言卵细胞级别教程】07.捕获异常和自定义错误

Go语言中的错误处理主要通过返回错误值来实现,而不是像一些其他语言一样使用异常。Go的错误处理有一些独特的特性,以下是一些关于Go语言错误处理的要点:

5万+

【Go语言学习系列11】错误处理与异常

深入剖析Go语言错误处理机制与最佳实践,包括错误值设计、错误包装以及panic/recover异常控制流的正确使用方法

GopherTribe的博客 1139
上一篇: 《快学 Go 语言》第 9 课 —— 接口
下一篇: 摸着石头过河:知乎核心业务 Go 语言改造实践
码洞
博客等级 码龄10年 41粉丝 137原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值