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

错误与异常在golang中区分

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

  • 错误是业务的一部分,而不同与异常。例如:开一个文件:文件正在被占用,可知的。

错误error

  • Go中的错误也是一种类型。错误用内置的error类型表示。就像其他类型的,如int, float64。

1. 在built-in 包中error被设计为一个接口

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
	Error() string
}

在我们最常用的函数返回值类型就是这个接口类型
在这里插入图片描述
Go 将 error 设计为一个接口,只需要实现 Error() string 方法,返回有意义、简练的错误描述信息即可。这也使得我们可以以任何的方式来自定义错误

2. golang内部对该接口的 实现1:errors.New(text string)error

源码中定义了一个结构体类型errorString,这个结构体实现了error接口定义的Error()方法,因此业务中直接 errors.New("err msg") 就可以使用了

package errors

// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {
	return &errorString{text} // 这个结构体实现了error接口中的方法,因此返回的结构体也是error类型
}

// errorString is a trivial implementation of error.
type errorString struct {
	s string
}

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

3. golang内部对该接口的 实现2:fmt.Errorf(format string, a …any) error

fmt 包下有一个Errorf,使用也十分简单,返回值也是error类型。

err := fmt.Errorf("this is an err| code: %d, msg: %s", 404, "IER")

源码中,返回的可能是不同实现error接口的结构体类型
比如case 0 返回的就是上面的errorString 结构体类型,case 1 是wrapError结构体类型,该类型也实现了error接口,典型的多态

func Errorf(format string, a ...any) error {
	p := newPrinter()
	p.wrapErrs = true
	p.doPrintf(format, a)
	s := string(p.buf)
	var err error
	switch len(p.wrappedErrs) {
	case 0:
		err = errors.New(s)
	case 1:
		w := &wrapError{msg: s}
		w.err, _ = a[p.wrappedErrs[0]].(error)
		err = w
	default:
		if p.reordered {
			slices.Sort(p.wrappedErrs)
		}
		var errs []error
		for i, argNum := range p.wrappedErrs {
			if i > 0 && p.wrappedErrs[i-1] == argNum {
				continue
			}
			if e, ok := a[argNum].(error); ok {
				errs = append(errs, e)
			}
		}
		err = &wrapErrors{s, errs}
	}
	p.free()
	return err
}

异常

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

panic

  1. panic后面的代码不会被执行,包括之后的defer
  2. 如果panic之前有defer语句,先执行defer语句(LIFO)。panic之后的defer语句不被执行
  3. 如果有panic发生,我们尽可能接收它,并处理

recover

  1. recover:接收panic异常并处理,一般是recover结合defer处理 panic 恐慌
  2. recover必须在defer语句中调用,才能捕获到panic。defer语句会延迟函数的执行,直到包含它的函数即将返回时,才执行defer语句中的函数。
  3. recover会返回panic的参数,直接接收即可
  4. 即便使用了recover,panic后面的代码依然不会被执行
  5. 当前函数的panic被recover之后,表示当前函数直接被执行完毕,正常执行下一个函数
func main() {
    //defer语句绑定的匿名函数
    defer func() {
        r := recover()
        if r != nil {
            fmt.Println("捕获到异常:", r)
        }
    }()
    panic("发生了异常")
    fmt.Println("这行代码不会被执行")
}

尝试下面的输出顺序:

func TestRecovery1(t *testing.T) {
	defer fmt.Println("defer1")
	defer fmt.Println("defer2")

	fmt.Println("main")
	panicAndREVFun()
	normalFunc()
}
func normalFunc() {
	fmt.Println("后面的函数正常执行")
}
func panicAndREVFun() {
	defer fmt.Println("defer func 1")
	defer func() {
		if msg := recover(); msg != nil {
			fmt.Println(msg)
			//recover 之后,在下面完成当前代码的善后处理,函数将执行完毕
		}
	}()
	panic("this is panic msg")
	fmt.Println("无论panic是否被recovery,panic后面的代码不被执行")
}

=== RUN TestRecovery1
main
this is panic msg
defer func 1
后面的函数正常执行
defer2
defer1
— PASS: TestRecovery1 (0.00s)
PASS

Process finished with the exit code 0

defer使用注意事项

生产环境中常出现的问题

  1. 先通过一个简单的示例说明问题:defer遇到变量会怎么样?

    func TestChan(t *testing.T) {
    	x := 5
    	defer fmt.Println(x) // 捕获并推迟输出x的值
    	x = 10
    }
    
    

    实际输出:

    === RUN   TestChan
    5
    --- PASS: TestChan (0.00s)
    PASS
    
  2. channel 实际中经常遇到的错误:

    var ch2 chan struct{}  // 声明
    func TestPrint(t *testing.T) {
    	defer close(ch2)
    	ch2 = make(chan struct{}) // 之后初始化
    	...
    }
    

    实际运行:

    panic: close of nil channel [recovered]
    	panic: close of nil channel
    
  3. 切片与defer相关

    func TestChan(t *testing.T) {
    	x := 5
    	defer fmt.Println(x) // 捕获并推迟输出x的值 5
    	x = 10
    
    	s := make([]int, 2, 3)
    	s[0] = 1
    	defer fmt.Printf("defer中的输出:%v\n", s) // 保持入栈时切片的信息,即len=2因此输出了底层数组的值
    
    	s[1] = 2 // 注意容量为2,s切片底层的len一直为2
    	s = append(s, 3) // 此时len发生改变
    	fmt.Println("非defer中的输出:", s)
    }
    

    实际输出

    === RUN   TestChan
    非defer中的输出: [1 2 3]
    defer中的输出:[1 2]
    5
    --- PASS: TestChan (0.00s)
    PASS
    
  • 分析原因:
    • defer 语句的参数在定义时就已经确定,而不会等到函数执行到 defer 语句时再评估。(defer是在函数结束时调用,但是defer 函数参数确是立即求值的)
    • 原理是:当程序执行到 defer 语句时,会将 defer 后面的函数调用及其参数存储在一个栈中
    • 关键点:看defer语句捕获到栈中的 都是当前值(但是注意这个值可能本身就是一个引用,指向的是底层的数据结构,就比如切片)

对于channel使用defer关闭的总结: 为了避免混淆,通常建议在 defer 语句中直接使用最终的通道,或者将 defer 语句放在通道创建之后,这样可以确保 defer 语句关闭的是期望的通道。

  • 最后看一个最逆天的例子。是的,bug写多了,自己都会出题了:

    func TestDefer(t *testing.T) {
    
    	// exp1
    	x := 5
    	defer fmt.Println(x) // 捕获并推迟输出x的值 5
    	x = 10
    
    	// exp2
    	n := 10
    	defer func() {
    		fmt.Println(n) //n此时是引用!
    	}()
    	n = 20
    
    	// exp3
    	y := 3
    	defer func(num int) {
    		fmt.Println(num) // 闭包,输出3,值拷贝
    	}(y)
    	y = 4
    
    }
    

想不到吧,输出居然是:3、20、5

  • 还是根据之前的分析:
    • 这里的关键是,exp2中放到defer栈中的其实是一个匿名函数,这里 n 是被一个匿名函数捕获的,而不是直接作为 defer 参数传递。匿名函数中的 n 变量会捕获 n 的引用,所以在 defer 执行时,它访问的是最新的 n 值。总结:在匿名函数中,n 不是立即传值,而是被匿名函数捕获
    • 和之前一样,exp1中defer 在注册时会 捕获 当前的参数值,而不是延迟执行时的值。
    • exp3就比较经典了,在使用waitgroup、循环开启goroutine中,和闭包相关的一个好习惯:直接拷贝参数值,以免造成意想不到的意外,做到可控。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大模型 Fine-Tuning 技术解析

引言 在大型语言模型(LLMs, Large Language Models)的发展历程中,预训练模型和微调(Fine-tuning)技术起到了至关重要的作用。这些技术使得模型不仅能够学习到丰富的语言特征,还能根据具体任务进行优化调整…

LabVIEW开发中常见硬件通讯接口快速识别

在 LabVIEW 开发中,与硬件进行通讯是实现数据采集与控制的重要环节。准确判断通讯接口类型和协议,可以提高开发效率,减少调试时间。本文结合 LabVIEW 的实际应用,详细介绍如何识别和判断常见硬件通讯接口的定义,并提供…

抖音短视频矩阵系统源码开发全流程解析

在项目开发过程中,调整配置文件至关重要,这些文件包括数据库连接、API密钥及全局参数等。通过正确配置这些信息,可确保应用程序的稳定性和安全性。灵活调整配置以适应具体需求有助于短视频矩阵系统项目的顺利推进。 在开发环境中&#xff0c…

Unity功能模块一对话系统(4)实现个性文本标签

本期我们将了解如何在TMPro中自定义我们的标签样式&#xff0c;并实现两种有趣的效果。 一.需求描述 1.定义<float>格式的标签&#xff0c;实现标签处延迟打印功能 2.定义<r" "></r>格式的标签&#xff0c;实现标签区间内文本片段的注释显示功能…

深度学习实战自动驾驶目标识别

本文采用YOLOv8作为核心算法框架&#xff0c;结合PyQt5构建用户界面&#xff0c;使用Python3进行开发。YOLOv8以其高效的实时检测能力&#xff0c;在多个目标检测任务中展现出卓越性能。本研究针对BDD100K自动驾驶目标数据集进行训练和优化&#xff0c;该数据集包含丰富的自动驾…

广西大数据局:数聚政府、利企惠民(广西数字政府建设内容、管理机制、应用场景)

2023年数字政府评估大会上&#xff0c;广西大数据局党委书记、主任周飞发表了题为“数聚政府、利企惠民”的主旨演讲。主要介绍了广西壮族自治区“数字政府的建设内容、数字政府的管理机制以及数字政府有哪些应用场景来实现惠企利民”。 篇幅限制&#xff0c;部分内容如下&…

AI 助力游戏开发中的常用算法实现

在当今的游戏开发领域&#xff0c;人工智能&#xff08;AI&#xff09;技术的应用已经成为推动行业发展的关键力量。AI不仅能够提升游戏的智能化水平&#xff0c;还能够增强玩家的沉浸感和游戏体验。随着技术的进步&#xff0c;AI在游戏设计、开发和测试中的应用越来越广泛&…

行业商机信息付费小程序系统开发方案

行业商机信息付费小程序系统&#xff0c;主要是整合优质行业资源&#xff0c;实时更新的商机信息。在当今信息爆炸的时代&#xff0c;精准、高效地获取行业商机信息对于企业和个人创业者而言至关重要。 一、使用场景 日常浏览&#xff1a;用户在工作间隙或闲暇时间&#xff0c…

LabVIEW 中 NI Vision 模块的IMAQ Create VI

IMAQ Create VI 是 LabVIEW 中 NI Vision 模块&#xff08;NI Vision Development Module&#xff09;的一个常用 VI&#xff0c;用于创建一个图像变量。该图像变量可以存储和操作图像数据&#xff0c;是图像处理任务的基础。 ​ 通过以上操作&#xff0c;IMAQ Create VI 是构建…

[AI] 深度学习的“黑箱”探索:从解释性到透明性

目录 1. 深度学习的“黑箱”问题&#xff1a;何为不可解释&#xff1f; 1.1 为什么“黑箱”问题存在&#xff1f; 2. 可解释性研究的现状 2.1 模型解释的方法 2.1.1 后置可解释性方法&#xff08;Post-hoc Explanations&#xff09; 2.1.2 内在可解释性方法&#xff08;I…

UnityRenderStreaming使用记录(四)

测试把UnityRenderStreaming部署在docker&#xff0c;剧透一下&#xff0c;嘎了…… 当然webserver运行的妥妥的 那么打包出的程序运行log Mono path[0] /home/unity/Broadcast/Broadcast_Data/Managed Mono config path /home/unity/Broadcast/Broadcast_Data/MonoBleedingE…

javaEE-文件操作和IO-文件

目录 一.什么是文件 1.文件就是硬盘(磁盘)上的文件。 2.计算机中存储数据的设备&#xff1a; 3.硬盘的物理特征 4.树型结构组织和⽬录 5.文件路径 文件路径有两种表示方式&#xff1a; 6.文件的分类 二、java中文件系统的操作 1.File类中的属性&#xff1a; 2.构造方…

SqlSession的线程安全问题源码分析

&#x1f3ae; 作者主页&#xff1a;点击 &#x1f381; 完整专栏和代码&#xff1a;点击 &#x1f3e1; 博客主页&#xff1a;点击 文章目录 SqlSession 是线程安全的吗&#xff1f;为什么说是线程不安全的&#xff1f;事务管理问题 数据库连接的共享问题 一级缓存线程安全问题…

拆解 Web3:探寻去中心化网络的核心密码

近年来&#xff0c;Web3频繁出现在技术讨论中&#xff0c;被视为互联网发展的下一阶段。那么&#xff0c;Web3究竟是什么&#xff1f;它如何区别于传统互联网&#xff0c;又将为未来的网络带来哪些新的可能&#xff1f;本文将从科普的角度拆解Web3的核心密码&#xff0c;揭开它…

《Vue3实战教程》37:Vue3生产部署

如果您有疑问&#xff0c;请观看视频教程《Vue3实战教程》 生产部署​ 开发环境 vs. 生产环境​ 在开发过程中&#xff0c;Vue 提供了许多功能来提升开发体验&#xff1a; 对常见错误和隐患的警告对组件 props / 自定义事件的校验响应性调试钩子开发工具集成 然而&#xff…

Ruby自动化:用Watir库获取YouTube视频链接

引言 Watir&#xff08;Web Application Testing in Ruby&#xff09;是一个强大的工具&#xff0c;它允许开发者使用Ruby语言来自动化控制浏览器。Watir最初被设计用于自动化Web应用测试&#xff0c;但其功能远不止于此。通过Watir&#xff0c;我们可以模拟用户行为&#xff…

[CTF/网络安全] 攻防世界 warmup 解题详析

查看页面源代码&#xff0c;发现source.php 得到一串代码&#xff0c;进行代码审计&#xff1a; <?phpclass emmm{public static function checkFile(&$page){$whitelist ["source">"source.php","hint">"hint.php"];…

solr9.7 单机安装教程

1.环境要求:jdk11以上 2.下载wget https://dlcdn.apache.org/solr/solr/9.7.0/solr-9.7.0.tgz 3.解压 4.修改solr.in.sh配置 5.启动命令 bin/solr start 6.创建core bin/solr create -c <core名称> 注意:用solr ui界面创建&#xff0c;会提示找不到solrconfig.xml和m…

应用架构模式-总体思路

采用引导式设计方法&#xff1a;以企业级架构为指导&#xff0c;形成较为齐全的规范指引。在实践中总结重要设计形成决策要点&#xff0c;一个决策要点对应一个设计模式。自底向上总结采用该设计模式的必备条件&#xff0c;将之转化通过简单需求分析就能得到的业务特点&#xf…

基于AI大模型的医院SOP优化:架构、实践与展望

一、引言 1.1 研究背景与意义 近年来,人工智能(AI)技术取得了迅猛发展,尤其是大模型的出现,为各个领域带来了革命性的变化。在医疗领域,AI 医疗大模型正逐渐崭露头角,展现出巨大的应用潜力。随着医疗数据的海量积累以及计算能力的大幅提升,AI 医疗大模型能够对复杂的…