【Golang】深入解读Go语言中的错误(error)与异常(panic)

news2024/10/1 15:31:26

在这里插入图片描述

✨✨ 欢迎大家来到景天科技苑✨✨

🎈🎈 养成好习惯,先赞后看哦~🎈🎈

🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Python全栈,Golang开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

所属的专栏:Go语言开发零基础到高阶实战
景天的主页:景天科技苑

在这里插入图片描述

文章目录

  • Go语言中的错误与异常
    • 1. Go语言错误 error
      • (1)自定义错误
        • 1. 使用errors.New()
        • 2.使用fmt.Errorf()
        • 3. 综合案例
      • (2)自定义错误类型(难点)
        • 1、定义一个结构体
        • 2、为该结构体创建个方法,实现go内置error接口中的Error方法
        • 3、创建个方法测试
        • 4、main方法中写逻辑
    • 2. Go语言的异常处理机制
      • (1)使用panic
      • (2)使用recover
        • 1. recover案例展示
        • 2. recover结合多个defer执行顺序

Go语言中的错误与异常

在Go语言编程中,错误和异常处理是非常重要的一部分。Go语言通过其独特的错误处理机制和异常处理模型,提供了一种既清晰又高效的方式来处理程序运行时的错误和异常情况。

错误:指的是程序中预期会发生的结果,预料之中

打开一个文件:文件正在被占用,可知的。

异常:不该出现问题的地方出现了问题,预料之外

调用一个对象,发现这个对象是个空指针对象,发生错误。

错误是业务的一部分,而异常不是。

go语言开发过程中遇到最多的代码,就是error。

需要将所有的错误情况都考虑到,并写到你的代码中。

1. Go语言错误 error

鼓励工程师在代码中显式的检查错误,而非忽略错误。

错误是开发中必须要思考的问题

  • 某些系统错误 ,文件被占用,网络有延迟
  • 人为错误:核心就是一些不正常的用户会怎么来给你传递参数,sql注入

在实际工程项目中,
我们希望通过程序的错误信息快速定位问题,但是又不喜欢错误处理代码写的冗余而又啰嗦。
Go语言没有提供像Java. c#语言中的try…catch异常处理方式,
而是通过函数返回值逐层往上抛, 如果没有人处理这个错误,程序就终止 panic

这种设计,鼓励工程师在代码中显式的检查错误,而非忽略错误。
好处就是避免漏掉本应处理的错误。但是带来一个弊端,让代码繁琐。

Go中的错误也是一种类型。错误用内置的error类型表示。就像其他类型的,如int, float64。
错误值可以存储在变量中,从函数中返回,传递参数 等等。

代码示例:

package main

import (
    "fmt"
    "os"
)

// 错误是开发中必须要思考的问题
// - 某些系统错误 ,文件被占用,网络有延迟
// - 人为错误:核心就是一些不正常的用户会怎么来给你传递参数,sql注入
func main() {
    //工作目录是指在执行程序时,操作系统认为当前正在工作的目录。
    //一般情况下,我们通过在终端或文件浏览器中打开文件时所处的目录就是当前工作目录。
    //os.Getwd()只能获取当前项目的工作目录,并不是当前文件所在路径
    path, _ := os.Getwd()
    fmt.Println("Path:", path)
    err := os.Chdir("F:\\goworks\\src\\jingtian\\yufa\\错误与异常")
    if err != nil {
        return
    }
    //再次查看路径
    path2, _ := os.Getwd()
    fmt.Println("再次查看Path:", path2)
    //打开一个文件 os 系统包,所有可以用鼠标和键盘能执行的事件,都可以用程序实现
    //os.Open()返回 一个文件对象和错误error
    // func Open(name string) (*File, error)
    file, err2 := os.Open("aaa.txt")
    // 在开发中,我们需要思考这个错误的类型  PathError
    // 1、文件不存在 err
    // 2、文件被占用 err
    // 3、文件被损耗 err
    // 调用方法后,出现错误,需要解决
    if err2 != nil {
        fmt.Println(err2)
        return
    }

    fmt.Println(file.Name())
}

// 在实际工程项目中,
// 我们希望通过程序的错误信息快速定位问题,但是又不喜欢错误处理代码写的冗余而又啰嗦。
// Go语言没有提供像Java. c#语言中的try...catch异常处理方式,
// 而是通过函数返回值逐层往上抛, 如果没有人处理这个错误,程序就终止 panic

// 这种设计,鼓励工程师在代码中显式的检查错误,而非忽略错误。
// 好处就是避免漏掉本应处理的错误。但是带来一个弊端,让代码繁琐。

// Go中的错误也是一种类型。错误用内置的error类型表示。就像其他类型的,如int, float64。
// 错误值可以存储在变量中,从函数中返回,传递参数 等等。

如果文件不存在,运行就会捕获错误
在这里插入图片描述

文件存在,就可以正常打印文件名称
在这里插入图片描述

(1)自定义错误

1. 使用errors.New()

errors.New()函数是最简单的创建错误的方式。它接受一个字符串作为参数,并返回一个实现了error接口的错误值。
返回值的类型为 *errors.errorString,可以使用Error()方法,将err转化为字符串
看下源码
在这里插入图片描述

package main

import (
    "errors"
    "fmt"
)

func main() {
    err := errors.New("这是一个错误")
    if err != nil {
        fmt.Println(err)
        fmt.Printf("错误类型%T\n", err)
        //可以使用Error()方法,将err转化为字符串
                // 注意,如果err为空,调用Error()方法会报错,所以该方法一定要在if err != nil条件中执行

        fmt.Printf("错误类型%T\n", err.Error())
    }
}

在这里插入图片描述

2.使用fmt.Errorf()

fmt.Errorf()函数比errors.New()更灵活,它允许你使用格式化字符串来创建错误。这对于需要动态构建错误信息的场景非常有用。

package main

import (
    "fmt"
)

func main() {
    err := fmt.Errorf("错误代码: %d, 错误信息: %s", 1001, "操作失败")
    if err != nil {
        fmt.Println(err.Error())
    }
}

在这里插入图片描述

3. 综合案例
package main

import (
    "errors"
    "fmt"
)

// 自己定义一个错误
// 1、errors.New("xxxxx")
// 2、fmt.Errorf()
// 都会返回  error 对象, 本身也是一个类型

func main() {
    //根据用户传参数据,用我们定义好的函数处理
    age_err := setAge(-3)
    if age_err != nil {
        fmt.Println(age_err)
    }
    fmt.Printf("%T\n", age_err) // *errors.errorString
    //如果age_err为空,调用Error()方法会报错
    fmt.Printf("%T\n", age_err.Error()) // string

    // 方式二
    errInfo1 := fmt.Errorf("我是一个错误信息:%d\n", 500)
    //errInfo2 := fmt.Errorf("我是一个错误信息:%d\n", 404)
    if errInfo1 != nil {
        // 处理这个错误
        fmt.Println(errInfo1)
        fmt.Printf("%T\n", errInfo1.Error())
    }

}

// 设置年龄的函数,一定需要处理一些非正常用户的请求
// 返回值为 error 类型  这个是内置类型
// 作为一个开发需要不断思考的事情,代码的健壮性和安全性
func setAge(age int) error {
    if age < 0 {
        // 给出一个默认值
        age = 3
        // 抛出一个错误 errors 包
        return errors.New("年龄不合法")
    }
    // 程序正常的结果,给这个用户赋值
    fmt.Println("年龄设置成功:age=", age)
    //年龄合法,就返回nil
    return nil
}

在这里插入图片描述

(2)自定义错误类型(难点)

虽然errors.New()和fmt.Errorf()很方便,但在某些情况下,你可能需要更丰富的错误信息。这时,可以通过自定义类型来实现go内置的error接口。
自定义错误类型步骤

1、定义一个结构体
// JingTian 定义一个结构体
type JingTian struct {
    msg  string
    code int
}
2、为该结构体创建个方法,实现go内置error接口中的Error方法

内置接口
在这里插入图片描述

// 注意,Error()方法开头大写 string
// 实现内置error接口的Error方法
// 注意,方法的调用者用指针类型,因为结构体是值类型,不同函数里面的操作是互相独立的
func (e *JingTian) Error() string {
    //  fmt.Sprintf() 返回string
    return fmt.Sprintf("错误信息:%s,错误代码:%d\n", e.msg, e.code)
}
3、创建个方法测试
// 使用错误测试
func test(i int) (int, error) {
    // 需要编写的错误
    if i != 0 {
        //自带的 errors.New()和fmt.Errorf() 只能返回字符串类型的错误信息,不常用,信息太少了
        // 更多的时候我们会使用自定义类型的错误
        //注意,返回的错误用内存地址,因为结构体是值类型
        return i, &JingTian{msg: "非预期数据", code: 500}
    }
    // 正常结果
    return i, nil
}
4、main方法中写逻辑
func main() {

    i, err := test(1)

    if err != nil {
        fmt.Println(err)
        //查看错误类型
        fmt.Printf("%T\n", err)
        ks_err, ok := err.(*JingTian)
        if ok {
            if ks_err.print() {
                // 处理这个错误中的子错误的逻辑
            }
            fmt.Println(ks_err.msg)
            fmt.Println(ks_err.code)
        }
    }
    fmt.Println(i)

}

完整代码

package main

import (
    "fmt"
)

// JingTian 定义一个结构体
type JingTian struct {
    msg  string
    code int
}

// 注意,Error()方法开头大写 string
// 实现内置error接口的Error方法
// 注意,方法的调用者用指针类型,因为结构体是值类型,不同函数里面的操作是互相独立的
func (e *JingTian) Error() string {
    //  fmt.Sprintf() 返回string
    return fmt.Sprintf("错误信息:%s,错误代码:%d\n", e.msg, e.code)
}

// 自定义错误,里面还可以写一些方法
// 处理error的逻辑
func (e *JingTian) print() bool {
    fmt.Println("hello,world")
    return true
}

// 使用错误测试
func test(i int) (int, error) {
    // 需要编写的错误
    if i != 0 {
        //自带的 errors.New()和fmt.Errorf() 只能返回字符串类型的错误信息,不常用,信息太少了
        // 更多的时候我们会使用自定义类型的错误
        //注意,返回的错误用内存地址,因为结构体是值类型
        return i, &JingTian{msg: "非预期数据", code: 500}
    }
    // 正常结果
    return i, nil
}

func main() {

    i, err := test(1)

    if err != nil {
        fmt.Println(err)
        //查看错误类型
        fmt.Printf("%T\n", err)
        ks_err, ok := err.(*JingTian)
        if ok {
            if ks_err.print() {
                // 处理这个错误中的子错误的逻辑
            }
            fmt.Println(ks_err.msg)
            fmt.Println(ks_err.code)
        }
    }
    fmt.Println(i)

}

在这里插入图片描述

go语言中,业务逻辑其实代码量并不大,大多数是在处理错误逻辑
比如下购物商城代码:
购物

下单:

1、查询商品信息 (FindGoodsError: msg、code | fun-商品不在了 ,fun-商品失效 ,fun-网络错误 )

2、查询库存 (FindxxxError: msg、code | fun-库存为0 ,fun-库存>0)

3、查询物流是否可以到达 (xxxError: msg、code | fun ,fun)

4、查询价格生成订单 (xxxError: msg、code | fun ,fun)

5、支付成功与否 (xxxError: msg、code | fun ,fun)

6、成功就产生一个订单,推送到商家和物流 (xxxError: msg、code | fun ,fun)

2. Go语言的异常处理机制

panic 和 recover
与错误处理不同,Go语言中的异常处理是通过panic和recover关键字来实现的。panic用于表示程序中的严重错误,它会导致程序中断执行;recover用于捕获panic,并恢复程序的正常执行流程。

(1)使用panic

什么时候会发生恐慌panic,我们不知道什么时候会报错
程序运行的时候会发生panic
手动抛出panic。我们在一些能够预知到危险的情况下,可以主动抛出
panic可以接受任何类型的值作为参数,通常是一个字符串或错误对象,用于描述发生的异常。
使用 panic() 程序就会终止,停在这里强制结束

看下源码
在这里插入图片描述

package main

import "fmt"

func main() {
    panic("发生了异常")
    //panic后面的代码,程序不会执行
    fmt.Println("hello world")
}

在这里插入图片描述

在上面的例子中,程序会在执行到panic时中断,并打印出"发生了异常"这条消息。

如果有panic发生,我们尽可能接收它,并处理
出现了panic之后,如果panic语句前面有defer语句,先执行所有的defer语句。panic语句后面的defer语句不再执行

package main

import "fmt"

// panic  recover
//
// 出现了panic之后,如果panic语句前面有defer语句,先执行所有的defer语句。
//
// defer : 延迟函数,倒序执行,处理一些问题。
func main() {
    defer fmt.Println("main--1")
    defer fmt.Println("main--2")
    fmt.Println("main--3")
    testPanic(1) // 外部函数,执行完panic之前的所有defer
    //后面的都不执行了
    defer fmt.Println("main--4")
    fmt.Println("main--5")
}
func testPanic(num int) {
    defer fmt.Println("testPanic--1")
    defer fmt.Println("testPanic--2")
    fmt.Println("testPanic--3")
    // 如果在函数中一旦触发了 panic,会终止后面要执行的代码。
    // 如果panic语句前面存在defer,正常按照defer逻辑执行。panic语句后面的defer语句不再执行
    if num == 1 {
        panic("出现预定的异常----panic")
    }
    defer fmt.Println("testPanic--4")
    fmt.Println("testPanic--5")
}

在这里插入图片描述

(2)使用recover

go 语言是追求简洁的,他没有使用 try…catch 语句
如果有些情况,必须要处理异常,就需要使用panic,抛出了异常,recover,接收这个异常来处理。
recover结合defer处理 panic 恐慌
recover必须在defer语句中调用,才能捕获到panic。defer语句会延迟函数的执行,直到包含它的函数即将返回时,才执行defer语句中的函数。
一般我们在某个函数抛出的panic,就在那个函数里面解决了
recover会返回panic的参数

recover返回任意类型
在这里插入图片描述

1. recover案例展示
package main

import (
    "fmt"
)

func main() {
    //defer语句绑定的匿名函数
    defer func() {
        r := recover()
        if r != nil {
            fmt.Println("捕获到异常:", r)
        }
    }()
    panic("发生了异常")
    fmt.Println("这行代码不会被执行")
}

在这里插入图片描述

在这个例子中,尽管在defer之后有panic调用,但defer中的函数仍然会执行,并捕获到panic抛出的异常。然后,程序会跳过panic之后的代码,继续执行defer之后的逻辑。

2. recover结合多个defer执行顺序
package main

import "fmt"

// panic  recover
//
// 出现了panic之后,如果有defer语句,先执行所有的defer语句。
//
// defer : 延迟函数,倒序执行,处理一些问题。
func main() {
    defer fmt.Println("main--1")
    defer fmt.Println("main--2")
    fmt.Println("main--3")
    testPanic2(1) // 外部函数,如果在函数内部已经处理panic,那么程序会继续执行
    defer fmt.Println("main--4")
    fmt.Println("main--5")
}
func testPanic2(num int) {
    // 出去函数的时候处理这里面可能发生的panic
    // recover func recover() any 返回panic传递的值
    // panic   func panic(v any)
    defer func() {
        if msg := recover(); msg != nil {
            fmt.Println("recover执行了... panic msg:", msg)
            // 处理逻辑
            fmt.Println("---------程序已恢复----------")
        }
    }()

    defer fmt.Println("testPanic--1")
    defer fmt.Println("testPanic--2")
    fmt.Println("testPanic--3")
    // 如果在函数中一旦触发了 panic,会终止后面要执行的代码。
    // 如果存在defer,正常按照defer逻辑执行
    if num == 1 {
        panic("出现预定的异常----panic")
    }
    defer fmt.Println("testPanic--4")
    fmt.Println("testPanic--5")
}

在这里插入图片描述

执行逻辑:

1、panic 触发

2、触发panic当前函数panic前面的所有defer语句,倒序执行

3、直到遇到recover处理了这个panic…函数结束

4、main,继续向下执行。

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

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

相关文章

【Pyecharts】时间线柱状图x轴坐标重复出现并重叠

问题描述 如图右侧显示多的一列坐标 解决方案 降低pyecharts版本&#xff1a;pip install pyecharts2.0.5

ChatGPT与R语言融合技术在生态环境数据统计分析、绘图(回归和混合效应模型、多元统计分析)

自2022年GPT&#xff08;Generative Pre-trained Transformer&#xff09;大语言模型的发布以来&#xff0c;它以其卓越的自然语言处理能力和广泛的应用潜力&#xff0c;在学术界和工业界掀起了一场革命。在短短一年多的时间里&#xff0c;GPT已经在多个领域展现出其独特的价值…

vue2接入高德地图实现折线绘制、起始点标记和轨迹打点的完整功能(提供Gitee源码)

目录 一、申请密钥 二、安装element-ui 三、安装高德地图依赖 四、完整代码 五、运行截图 六、官方文档 七、Gitee源码 一、申请密钥 登录高德开放平台&#xff0c;点击我的应用&#xff0c;先添加新应用&#xff0c;然后再添加Key。 ​ 如图所示填写对应的信息&…

第十九章(自定义类型:结构体)

1. 结构体类型的声明 2. 结构体变量的创建和初始化 3. 结构成员访问操作符 4. 结构体内存对⻬ 5. 结构体传参 一、结构体类型的声明 1.1结构体的声明 struct student {int age;int id[10];char name[10]; };1.2 结构体变量的初始化 struct student {int age;char id[10…

YOLOv1代码复现(论文复现)

YOLOv1代码复现&#xff08;论文复现&#xff09; 本文所涉及所有资源均在传知代码平台可获取 文章目录 YOLOv1代码复现&#xff08;论文复现&#xff09;论文介绍主要内容实验部分卷积网络结构计算损失核心代码 缺点 论文介绍 该论文就是YOLOv1&#xff0c;YOLOv1是YOLO系列目…

大模型部署——NVIDIA NIM 和 LangChain 如何彻底改变 AI 集成和性能

DigiOps与人工智能 人工智能已经从一个未来主义的想法变成了改变全球行业的强大力量。人工智能驱动的解决方案正在改变医疗保健、金融、制造和零售等行业的企业运营方式。它们不仅提高了效率和准确性&#xff0c;还增强了决策能力。人工智能的价值不断增长&#xff0c;这从它处…

Ubuntu网卡配置

一、低阶版本配置网卡步骤:(如Ubuntu 16.04.2 LTS) 编辑配置文件interfaces,添加网卡配置信息 我这边以root用户登录进服务器,就不需要普通用户每次在命令前添加sudo vim /etc/network/interfaces 1.动态获取ip设置: auto ens3 # 网卡设备名称ens3 iface ens3 ine…

Tdesign TreeSelect 树形选择 多选

这里写自定义目录标题 小程序原生开发 Tdesign TreeSelect 树形选择 多选可以选择不同一级分类下的数据 小程序原生开发 Tdesign TreeSelect 树形选择 多选可以选择不同一级分类下的数据 TreeSelect 树形选择 在原demo基础上修改 const chineseNumber 一二三四五六七八九十.…

音视频入门基础:FLV专题(9)——Script Tag简介

一、SCRIPTDATA 根据《video_file_format_spec_v10_1.pdf》第75页到76页&#xff0c;如果某个Tag的Tag header中的TagType值为18&#xff0c;表示该Tag为Script Tag&#xff08;脚本Tag&#xff0c;又称Data Tag、SCRIPTDATA tag&#xff09;。这时如果Filter的值不为1表示未加…

昇思MindSpore进阶教程--使能图算融合

大家好&#xff0c;我是刘明&#xff0c;明志科技创始人&#xff0c;华为昇思MindSpore布道师。 技术上主攻前端开发、鸿蒙开发和AI算法研究。 努力为大家带来持续的技术分享&#xff0c;如果你也喜欢我的文章&#xff0c;就点个关注吧 正文开始 图算融合是MindSpore特有的网络…

十二生肖国庆姓氏专属头像

关注▲洋洋科创星球▲领取十二生肖国庆姓氏专属头像定制&#xff01; 庆祝祖国75周年华诞&#xff0c;在这个举国欢庆的国庆节时刻&#xff0c;我们特别为您准备了一份独特的礼物——十二生肖国庆姓氏专属头像定制。 十二生肖&#xff0c;又称属相&#xff0c;是中国传统文化中…

Linux 安装redis主从模式+哨兵模式3台节点

下载 https://download.redis.io/releases/ 解压 tar -zxvf redis-7.2.4.tar.gz -C /opt chmod 777 -R /opt/redis-7.2.4/安装 # 编译 make # 安装&#xff0c; 一定是大写PREFIX make PREFIX/opt/redis-7.2.4/redis/ install配置为系统服务 cd /etc/systemd/system/主服务…

盒子是什么? -- 第四课

文章目录 前言一、盒子是什么&#xff1f;二、元素介绍1.边框 - border2.内边距 - padding3. 外边距属性 -- margin 三、拓展知识1.块元素垂直外边距的合并2. 嵌套块元素垂直外边距的合并 四、背景属性五、元素的浮动1.浮动2.清除浮动3.元素定位4. 特殊定位 -- 黏性定位5. z-in…

Python selenium库学习使用实操二

系列文章目录 Python selenium库学习使用实操 文章目录 系列文章目录前言一、模拟登录二、表单录入 前言 在上一篇文章中&#xff0c;我们完成Selenium环境的搭建&#xff0c;和简单的自动化。今天继续深入学习。今天的目标是完成模拟登录&#xff0c;和表单录入。 一、模拟登…

什么是网络准入控制系统?2024年有哪些好用的网络准入控制系统?

网络准入控制系统&#xff08;Network Access Control, NAC&#xff09;是一种网络安全解决方案&#xff0c;旨在确保只有符合特定安全策略的设备和用户才能访问网络资源。NAC系统通过在设备连接到网络之前对其进行身份验证、授权和健康状态检查&#xff0c;从而防止未经授权的…

YOLOv11改进 | 注意力篇 | YOLOv11引入GAM注意力机制

1.GAM介绍 摘要&#xff1a;为了提高各种计算机视觉任务的性能&#xff0c;人们研究了各种注意机制。然而&#xff0c;现有的方法忽略了保留通道和空间信息以增强跨维交互的重要性。因此&#xff0c;我们提出了一种通过减少信息减少和放大全球交互表示来提高深度神经网络性能的…

vue3 实现拖拽排序效果 sortablejs

效果图 依赖安装 npm i sortablejs -S <template><div class"warp"><div class"parent-box" v-for"pItem in sortData" :key"pItem.name"><h2 class"parent-name">{{ pItem.name }}</h2>&l…

程序计数器(学习笔记)

程序计数器是一块较小的内存空间&#xff0c;它的作用可以看做是当前线程所执行的字节码的信号指示器&#xff08;偏移地址&#xff09;&#xff0c;Java编译过程中产生的字节码有点类似编译原理的指令&#xff0c;程序计数器的内存空间存储的是当前执行的字节码的偏移地址 因为…

唱响红色志愿,赞歌献给祖国——杭州建德市庆祝中华人民共和国成立75周年联欢盛宴纪实

作者&#xff1a;华夏之音/李望 通讯员&#xff1a;王江平 9月30日上午&#xff0c;金桂的香气与红旗的鲜艳交相辉映&#xff0c;杭州建德市党群服务中心、建德市新时代文明实践中心内洋溢着一股浓厚的节日氛围。在这里&#xff0c;一场名为“唱响红色志愿、赞歌献给祖国”的联…

企业架构系列(15)ArchiMate第13节:战略视角

战略视角提供了对企业高层战略方向和构成的不同视角建模&#xff0c;使建模者能够专注于某些特定方面。 一、战略视角概览 战略视角主要包括&#xff1a; 战略视角&#xff1a;提供企业战略、能力、价值流和资源以及预期成果的高层概述。能力地图视角&#xff1a;提供企业能力…