golang 语言中错误处理机制

与其他主流语言如 Javascript、Java 和 Python 相比,Golang 的错误处理方式可能和这些你熟悉的语言有所不同。所以才有了这个想法根大家聊一聊 golang 的错误处理方式,以及实际开发中应该如何对错误进行处理。因为分享面对 Golang有一个基本的了解 developers, 所以一些简单地方就不做赘述了。

如何定义错误

在 golang 语言中,无论是在类型检查还是编译过程中,都是将错误看做值来对待,和 string 或者 integer 这些类型值并不差别。声明一个 string 类型变量和声明一个 error 类型变量是没什么区别的。

你可以定义接口作为 error 的类型,有关 error 能够提供什么样信息都是由自己决定的,这是 error 在 golang 作为值的好处,不过这样做也自然有其坏处,有关 error 定义好坏就全由其定义开发人员所决定,也就是有关 error 融入过多人为的主观因素。

package main

import (
	"fmt"
	"io/ioutil"
)

func main(){
	dir, err := ioutil.TempDir("","temp")

	if err != nil{
		fmt.Errorf("failed to create temp dir: %v",err)
	}
}

错误在语言中的重点地位

在 Go 语言中错误处理设计一直大家喜欢讨论的内容,错误处理是该语言的核心,但该语言并没有规定如何处理错误。社区已经为改进和规范错误处理做出了努力,但许多人忽略了错误在我们应用程序领域中的核心地位。也就是说,错误与客户和订单类型一样重要。

Golang中的错误

错误表示在应用程序中发生了不需要的情况。比方说,想创建一个临时目录,在那里可以为应用程序存储一些文件,但这个目录的创建失败了。这是一个不期望的情况,就可以用错误来表示。

通过创建自定义错误可以将更丰富错误信息传递给调用者。个返回值返回将错误交给调用函数人来处理错误。Golang 本身允许函数具有多个返回值,所以通常把错误作为函数最后一个参数返回给调用者来处理。

errors 是 I/O

  • 有时候开发人员是 error 的生产者(写 error)
  • 有时候开发人员又是 error 的消费者(读 error)

也就是我们开发程序一部分工作是读取和写入 error

errors 的上下文
什么是 error 的上下文呢? 如何定义 error 需要考虑一些因素,例如在不同程序我们定义 error 和处理 error 方式也不仅相同

  1. CLI 工具
  2. 长时间运行的系统

而且我们需要考虑使用程序的人群,他们是什么方式来使用系统,这些因素都是我们设计也好定义错误信息要考虑的因素。

错误的类型

就错误核心,那么错误可能是我们预料之中的错误,错误也可能是我们没有考虑到,例如无效内存,数组越界,也就是单靠代码自身暂时是解决不了的错误 ,这样的误差往往让代码恐慌,所以 Panic。通常这样错误对于程序是灾难性的失败,无法修复的。

自定义错误

如前所述,错误使用内置的错误接口类型来表示,其定义如下。

type error interface {
    Error() string
}

下面举了 2 例子来定义 error ,分别定义两个 struct 都实现了 Error() 接口即可

type SyntaxError struct {
    Line int
    Col  int
}

func (e *SyntaxError) Error() string {
    return fmt.Sprintf("%d:%d: syntax error", e.Line, e.Col)
}
type InternalError struct {
    Path string
}

func (e *InternalError) Error() string {
    return fmt.Sprintf("parse %v: internal error", e.Path)
}

该接口包含一个方法 Error() ,以字符串形式返回错误信息。每一个实现了错误接口的类型都可以作为一个错误使用。当使用 fmt.Println 等方法打印错误时,Golang 会自动调用 Error() 方法。

在 Golang 中,有多种创建自定义错误信息的方法,每一种都有自己的优点和缺点。

基于字符串的错误

基于字符串的错误可以用 Golang 中两个开箱即用方法来自定义错误,适用哪些仅返回描述错误信息的相对来说比较简单的错误。

err := errors.New("math: divided by zero")

将错误信息传入到 errors.New() 方法可以用来新建一个错误

err2 := fmt.Errorf("math: %g cannot be divided by zero", x)

fmt.Errorf 通过字符串格式方式,可以将错误信息包含你错误信息中。也就是为错误信息添加了一些格式化的功能。

自定义数据结构的错误

可以通过在你的结构上实现 Error 接口中定义的 Error() 函数来创建自定义的错误类型。下面是一个例子。

Defer, panic 和 recover

Go 并不像许多其他编程语言(包括 Java 和 Javascript )那样有异常,但有一个类似的机制,即 "Defer, panic 和 recover"。然而,panic 和 recover 的使用情况与其他编程语言中的异常非常不同,因为代码本身无法应对时候和不可恢复的情况下使用。

Defer

有点类似析构函数,在函数执行完毕后做一些资源释放等收尾工作,好处其执行和其在代码中位置并没有关系,所以可以将其写在你读写资源语句后面,以免随后忘记做一些资源释放的工作。关于 defer 输出也是面试时,面试官喜欢问的一个问题。

package main

import(
	"fmt"
	"os"
)

func main(){
	f := createFile("tmp/machinelearning.txt")
	defer closeFile(f)
	writeFile(f)
}

func createFile(p string) *os.File {
	fmt.Println("creating")
	f, err := os.Create(p)
	if err != nil{
		panic(err)
	}
	return f
}

func closeFile(f *os.File){
	fmt.Println("closing")
	err := f.Close()

	if err != nil{
		fmt.Fprintf(os.Stderr, "error:%v\n",err)
		os.Exit(1)
	}
}

func writeFile(f *os.File){
	fmt.Println("writing")
	fmt.Fprintln(f,"machine leanring")
}

defer 语句会将函数推入到一个栈结构中。同时栈结构中的函数会在 return 语句执行后被调用。

package main

import "fmt"

func main(){
	// defer fmt.Println("word")
	// fmt.Println("hello")

	fmt.Println("hello")
	for i := 0; i <=3; i++ {
		defer fmt.Println(i)
	}
	fmt.Println("world")
}

hello
world
3
2
1
0

可以通过在你的结构上实现 Error 接口中定义的 Error() 函数来实现自定义错误类型,下面是一个例子。

Panic

panic 语句向 Golang 发出信号,这时通常是代码无法解决当前的问题,所以停止代码的正常执行流程。一旦调用了 panic,所有的延迟函数都会被执行,并且程序会崩溃,其日志信息包括 panic 值(通常是错误信息)和堆栈跟踪。

举个例子,当一个数字被除以0时,Golang会出现 panic。

package main

import "fmt"

func main(){
	divide(5)
}

func divide(x int){
	fmt.Printf("divide(%d)\n",x+0/x)
	divide(x-1)
}
divide(5)
divide(4)
divide(3)
divide(2)
divide(1)
panic: runtime error: integer divide by zero

goroutine 1 [running]:
main.divide(0x0)
        /Users/zidea2020/Desktop/mysite/go_tut/main.go:10 +0xdb
main.divide(0x1)
        /Users/zidea2020/Desktop/mysite/go_tut/main.go:11 +0xcc
main.divide(0x2)
        /Users/zidea2020/Desktop/mysite/go_tut/main.go:11 +0xcc
main.divide(0x3)
        /Users/zidea2020/Desktop/mysite/go_tut/main.go:11 +0xcc
main.divide(0x4)
        /Users/zidea2020/Desktop/mysite/go_tut/main.go:11 +0xcc
main.divide(0x5)
        /Users/zidea2020/Desktop/mysite/go_tut/main.go:11 +0xcc
main.main()
        /Users/zidea2020/Desktop/mysite/go_tut/main.go:6 +0x2a
exit status 2

Recover

Go语言提供了recover内置函数,前面提到,一旦panic,逻辑就会走到defer那,那我们就在defer那等着,调用recover函数将会捕获到当前的panic,被捕获到的panic就不会向上传递了。然后,恢复将结束当前的 Panic 状态,并返回 Panic 的错误值。

package main

import "fmt"

func main(){
	accessSlice([]int{1,2,5,6,7,8}, 0)
}

func accessSlice(slice []int, index int) {
	defer func() {
		if p := recover(); p != nil {
			fmt.Printf("internal error: %v", p)
		}
	}()

	fmt.Printf("item %d, value %d \n", index, slice[index])
	defer fmt.Printf("defer %d \n", index)
	accessSlice(slice, index+1)
}

包装错误

Golang 也允许对错误进行包裹,通过错误嵌套,在原有错误信息上添加一个额外信息帮助调用者对问题判断以及后续应该如何处理信息。以通过使用 %w 标志和 fmt.Errorf 函数来对原有的错误进行保存提供一些特定的信息,如下例所示。

package main

import (
	"errors"
	"fmt"
	"os"
)

func main() {
	err := openFile("non-existing")

	if err != nil {
		fmt.Printf("error running program: %s \n", err.Error())
	}
}

func openFile(filename string) error {
	if _, err := os.Open(filename); err != nil {
		return fmt.Errorf("error opening %s: %w", filename, err)
	}

	return nil
}

上面已经通过代码演示如何包装一个错误,程序会打印输出使用 fmt.Errorf 添加文件名的包装过的错误,也打印了传递给 %w 标志的原有错误信息。这里再补充一个 Golang 还提供的功能,通过使用 error.Unwrap 来还原错误信息,从而获得原有的错误信息。

package main

import (
	"errors"
	"fmt"
	"os"
)

func main() {
	err := openFile("non-existing")

	if err != nil {
		fmt.Printf("error running program: %s \n", err.Error())

		// Unwrap error
		unwrappedErr := errors.Unwrap(err)
		fmt.Printf("unwrapped error: %v \n", unwrappedErr)
	}
}

func openFile(filename string) error {
	if _, err := os.Open(filename); err != nil {
		return fmt.Errorf("error opening %s: %w", filename, err)
	}

	return nil
}

错误的类型转换

有时候需要在不同的错误类型之间进行转换,有情况需要通过类型转换来为错误添加信息,或者换一种表达方式,。 errors.As 函数提供了一个简单而安全的方法,通过寻找错误链中匹配错误类型进行转化输出。如果没有找到匹配的,该函数返回 false 。

package main

import (
	"errors"
	"fmt"
	"io/fs"
	"os"
)

func main(){
	// Casting error
	if _, err := os.Open("non-existing"); err != nil {
		var pathError *os.PathError
		if errors.As(err, &pathError) {
			fmt.Println("Failed at path:", pathError.Path)
		} else {
			fmt.Println(err)
		}
	}
}

在这里,试图将通用错误类型转换为 os.PathError ,这样就可以访问该特定的错误信息,这些信息保存在结构体中的 Path 属性上。

错误类型检查

Golang 提供了 errors.Is 函数来用于检查错误类型是否为指定的错误类型,该函数返回一个布尔值值来表示是否为指定错误类型。

package main

import (
	"errors"
	"fmt"
	"io/fs"
	"os"
)

func main(){
	// Check if error is a specific type
	if _, err := os.Open("non-existing"); err != nil {
		if errors.Is(err, fs.ErrNotExist) {
			fmt.Println("file does not exist")
		} else {
			fmt.Println(err)
		}
	}
}

到此这篇关于golang 语言中错误处理机制的文章就介绍到这了,更多相关golang 错误处理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Golang try catch与错误处理的实现

    目录 Golang try catch panic panic主要使用场景: recover error 预定义错误值 自定义错误类型 使用 panic和recover模拟 tyr catch  谨慎! Golang try catch 虽然在使用Golang的时候发现没有try catch这种错误处理机制但是想一想golang作为一门优雅的语言,似乎也是情理之中.因为在java中 throws在函数签名中有一个关键字,旨在使异常流程比较简洁,但是一旦预期的异常数量增加,就很难准确捕捉到具体异常

  • Golang中重复错误处理的优化方法

    Golang 错误处理最让人头疼的问题就是代码里充斥着「if err != nil」,它们破坏了代码的可读性,本文收集了几个例子,让大家明白如何优化此类问题. 让我们看看 Errors are values中提到的一个 io.Writer 例子: _, err = fd.Write(p0[a:b]) if err != nil { return err } _, err = fd.Write(p1[c:d]) if err != nil { return err } _, err = fd.Wr

  • Golang巧用defer进行错误处理的方法

    本文主要跟大家介绍了Golang巧用defer进行错误处理的相关内容,分享出来供大家参考学习,下面来看看详细的介绍: 问题引入 毫无疑问,错误处理是程序的重要组成部分,有效且优雅的处理错误是大多数程序员的追求.很多程序员都有C/C++的编程背景,Golang的程序员也不例外,他们处理错误有意无意的带着C/C++的烙印. 我们看看下面的例子,就有一种似曾相识的赶脚,代码如下: func deferDemo() error { err := createResource1() if err != n

  • golang 语言中错误处理机制

    与其他主流语言如 Javascript.Java 和 Python 相比,Golang 的错误处理方式可能和这些你熟悉的语言有所不同.所以才有了这个想法根大家聊一聊 golang 的错误处理方式,以及实际开发中应该如何对错误进行处理.因为分享面对 Golang有一个基本的了解 developers, 所以一些简单地方就不做赘述了. 如何定义错误 在 golang 语言中,无论是在类型检查还是编译过程中,都是将错误看做值来对待,和 string 或者 integer 这些类型值并不差别.声明一个

  • Go语言中错误处理实例分析

    本文实例讲述了Go语言中错误处理的方法.分享给大家供大家参考.具体分析如下: 错误是可以用字符串描述自己的任何东西. 主要思路是由预定义的内建接口类型 error,和其返回返回字符串窜的方法 Error 构成. type error interface { Error() string } 当用 fmt 包的多种不同的打印函数输出一个 error 时,会自动的调用该方法. 复制代码 代码如下: package main import (     "fmt"     "time

  • C语言中的参数传递机制详解

    C中的参数传递 本文尝试讨论下C中实参与形参的关系,即参数传递的问题. C语言的参数传递 值传递 首先看下列代码: #include <stdio.h> int main(){ int n = 1; printf("实参n的值:%d,地址:%#x\n", n, &n); void change(int i);//函数声明 change(n); printf("函数调用后实参n的值:%d,地址:%#x\n", n, &n); return

  • 深入理解Swift语言中的闭包机制

    在 Swift 中的闭包类似于结构块,并可以在任何地方调用,它就像 C 和 Objective C 语言内置的函数. 函数内部定义的常数和变量引用可被捕获并存储在闭包.函数被视为封闭的特殊情况,它有 3 种形式. 在 Swift 语言闭合表达式,如下优化,重量轻语法风格,其中包括: 推导参数并从上下文菜单返回值的类型 从单封表达的隐性返回 简略参数名称 尾部闭包语法 语法 下面是一个通用的语法定义用于闭包,它接受参数并返回数据的类型: 复制代码 代码如下: {(parameters) -> re

  • 列举java语言中反射的常用方法及实例代码

    Java反射机制 一.什么是反射机制  简单的来说,反射机制指的是程序在运行时能够获取自身的信息.在java中,只要给定类的名字,     那么就可以通过反射机制来获得类的所有信息. 二.哪里用到反射机制  有些时候,我们用过一些知识,但是并不知道它的专业术语是什么,在刚刚学jdbc时用过一行代码,     Class.forName("com.mysql.jdbc.Driver.class").newInstance();但是那时候只知道那行代码是生成     驱动对象实例,并不知道

  • Golang 语言高效使用字符串的方法

    01介绍 在 Golang 语言中,string 类型的值是只读的,不可以被修改.如果需要修改,通常的做法是对原字符串进行截取和拼接操作,从而生成一个新字符串,但是会涉及内存分配和数据拷贝,从而有性能开销.本文我们介绍在 Golang 语言中怎么高效使用字符串. 02字符串的数据结构 在 Golang 语言中,字符串的值存储在一块连续的内存空间,我们可以把存储数据的内存空间看作一个字节数组,字符串在 runtime 中的数据结构是一个结构体 stringStruct,该结构体包含两个字段,分别是

  • Golang 语言控制并发 Goroutine的方法

    goroutine 是 Go语言中的轻量级线程实现,由 Go 运行时(runtime)管理.Go 程序会智能地将 goroutine 中的任务合理地分配给每个 CPU. 01介绍 Golang 语言的优势之一是天生支持并发,我们在 Golang 语言开发中,通常使用的并发控制方式主要有 Channel,WaitGroup 和 Context,本文我们主要介绍一下 Golang 语言中并发控制的这三种方式怎么使用?关于它们各自的详细介绍在之前的文章已经介绍过,感兴趣的读者朋友们可以按需翻阅. 02

  • Golang语言如何高效拼接字符串详解

    目录 01.介绍 02.操作符 + 03.strings.Join 方法 04.fmt.Sprint 方法 05.bytes.Buffer 类型 06.strings.Builder 类型 07.总结 01.介绍 在编程语言中,字符串是一种重要的数据结构.在 Golang 语言中,因为字符串只能被访问,不能被修改,所以,如果我们在 Golang 语言中进行字符串拼接操作,Golang 需要进行内存拷贝. 如果读者朋友们了解过 Golang 语言内存管理的相关知识,就会知道内存拷贝会带来性能消耗.

  • Golang语言的多种变量声明方式与使用场景详解

    目录 01介绍 02变量声明方式 标准声明变量 不显式赋初始值声明变量 省略类型声明变量 短变量声明 显式类型转换 变量列表声明 变量声明块 03使用场景 包级变量 全局变量 局部变量 04注意事项: 05总结 01介绍 在程序设计中,编译器必须将代表数据的变量名称替换成该数据所在的内存地址.变量的名称.类型及内存地址通常会维持固定,但该内存地址所存储的数据在程序执行期间则可能会改变. Golang 语言编译器需要先明确变量的内存边界,才可以使用变量.通过声明变量使用的类型,编译器可以明确变量的

  • Go语言中更优雅的错误处理

    从现状谈起 Go语言受到诟病最多的一项就是其错误处理机制.如果显式地检查和处理每个error,这恐怕的确会让人望而却步.下面我们将给大家介绍Go语言中如何更优雅的错误处理. Golang 中的错误处理原则,开发者曾经之前专门发布了几篇文章( Error handling and Go和 Defer, Panic, and Recover.Errors are values )介绍.分别介绍了 Golang 中处理一般预知到的错误与遇到崩溃时的错误处理机制. 一般情况下,我们还是以官方博客中的错误

随机推荐