【Go】错误和异常处理

Go语言中没有try catch语句,而是通过函数返回值逐层往上抛。

错误指的是可能出现问题的地方出现了问题。例如:打开一个文件时失败了,这种情况在人们的意料之中。
异常指的是不应该出现问题的地方出现了问题。例如:引用了空指针,这种情况是在人们的意料之外。
错误是业务过程中的一部分,而异常不是。

Go语言引入了内置的error类型表示错误,错误值可以存储在变量中,从函数中返回,error也可以看成一种数据类型。

package main

import (
	"fmt"
	"os"
)

func main(){
	/*
	如果一个函数或方法返回一个错误,那么按照惯例,它必须是函数返回的最后一个值
	 */
	f, err := os.Open("test.txt")
	if err != nil{
		fmt.Println(err)
		return
	}
	// 根据f进行文件的读或写
	fmt.Println(f.Name(), "opened successfully")
}

error是一种内置数据类型,内置的接口:

type error interface {
	Error() string
}

其中定义了一个Error的方法。使用Go语言提供的包构建一个error:

  • errors包下的函数:New(), 创建一个error对象
  • fmt包下的Errorf()函数:func Errorf(format string, a …interface{}) error
    创建实例如下:
// 1. 创建一个error数据
	err1  := errors.New("自己创建的一个错误")
	fmt.Println(err1)
	fmt.Printf("%T\n", err1)
	// 2.另一个创建error的方法
	err2 := fmt.Errorf("错误的信息码%d", 404)
	fmt.Println(err2)
	fmt.Printf("%T\n", err2)

例如:

// 设计一个函数:验证年龄是否合法,如果为负数,就返回一个error
func checkAge(age int) error{
	if age < 0{
		// 返回error
		// err := errors.New("年龄不合法")
		err := fmt.Errorf("给定的年龄是:%d, 是不合法的", age)
		return err
	}
	fmt.Println("年龄是:", age)
	return nil
}

根据断言查看错误类型相关数据:

package main

import (
	"fmt"
	"os"
)

func main(){
	/*
	如果一个函数或方法返回一个错误,那么按照惯例,它必须是函数返回的最后一个值
	 */
	f, err := os.Open("test.txt")
	if err != nil{
		fmt.Println(err)
		// 使用断言 查看错误信息
		if ins, ok := err.(*os.PathError); ok{
			fmt.Println("1.Op:", ins.Op)
			fmt.Println("2.Path:", ins.Path)
			fmt.Println("3.Err:", ins.Err)
		}
		return
	}
	// 根据f进行文件的读或写
	fmt.Println(f.Name(), "opened successfully")
}

还有可以这个例子:


package main

import (
	"fmt"
	"net"
)

func main() {
	// 正常访问一个网址
	addr, err := net.LookupHost("www.baidu.com")
	fmt.Println(err)
	fmt.Println(addr)
	// 不能访问一个网址
	addr2, err2 := net.LookupHost("www.baidu2.com")
	fmt.Println(err2)
	fmt.Println(addr2)
	// 使用断言查看断言相关错误信息
	if ins, ok := err.(*net.DNSError);ok{
		if ins.Timeout(){
			fmt.Println("操作超时")
		}else if ins.Temporary(){
			fmt.Println("临时性错误")
		}else{
			fmt.Println("通常错误")
		}
	}

}

如果返回的错误是我们关心的错误,我们可以直接比较,然后具体情况具体分析。例如:


package main

import (
	"fmt"
	"path/filepath"
)

func main() {
	// filepath包的Glob函数用于返回与模式匹配的所有文件的名称,当模式出现错误时,该函数返回一个错误ErrBadPattern
	files, err := filepath.Glob("[")
	if err != nil && err == filepath.ErrBadPattern{
		fmt.Println(err)
		return
	}
	fmt.Println("matched files", files)
}

不要忽略一个错误,忽视错误会招致麻烦。

自定义error。 error是一个接口,实现接口方法即可。例如:

package main

import (
	"fmt"
	"math"
)

/**
自定义错误
 */

func main()  {
	radius := 4.0
	area1, err1 := circleArea(radius)
	if err1 != nil{
		fmt.Println(err1)
		// 断言判断错误类型为areaError,获取错误类型中的属性
		if err, ok:=err1.(*areaError);ok{
			fmt.Printf("半径是:%.2f", err.radius)
		}
		return
	}
	fmt.Println("圆形的面积:", area1)
	radius = - 4.0
	area2, err2 := circleArea(radius)
	if err2 != nil{
		fmt.Println(err2)
		// 断言判断错误类型为areaError,获取错误类型中的属性
		if err, ok:=err2.(*areaError);ok{
			fmt.Printf("半径是:%.2f", err.radius)
		}
		return
	}
	fmt.Println("圆形的面积:", area2)
}
// 定义一个结构体
type areaError struct {
	msg string
	radius float64
}
// 实现error接口
func (e *areaError) Error() string{
	return fmt.Sprintf("error:半径,%.2f,%s", e.radius, e.msg)
}
func circleArea(radius float64)(float64, error)  {
	if radius < 0{
		return 0, &areaError{msg:"半径非法", radius:radius}
	}
	return math.Pi*radius*radius, nil

}

进阶:自定义错误以及对错误的处理方法。

package main

import "fmt"

func main()  {
	length, width := 5.6, 10.2
	area, err := rectArea(length, width)
	if err!=nil {
		fmt.Println(err)
		return
	}
	fmt.Println("矩形的面积为:", area)
	length, width = -5.6, -10.2  // 这是赋值,不是定义
	area, err = rectArea(length, width)
	if err!=nil {
		fmt.Println(err)
		// 使用断言判断error类型
		if err, ok:= err.(*areaError); ok{
			if err.lengthNegative(){
				fmt.Printf("error:长度,%.2f小于0", err.length)
			}
			if err.widthNegative(){
				fmt.Printf("error:宽度,%.2f小于0", err.width)
			}
		}
		return
	}
	fmt.Println("矩形的面积为:", area)
}
// 结构体
type areaError struct {
	msg string  // 错误的描述
	length float64  // 发生错误的时候,矩形的长度
	width float64  // 发生错误的时候,矩形的宽度
}

func (e *areaError)Error() string  {
  return e.msg
}
func (e *areaError) lengthNegative() bool  {
	return e.length < 0
}
func (e *areaError) widthNegative() bool {
	return e.width < 0
}
// 计算矩形的面积
func rectArea(length, width float64) (float64, error)  {
	msg := ""
	if length < 0{
		msg = "矩形长度小于零"
	}
	if width < 0{
		if msg == ""{
			msg = "矩形宽度小于零"
		}else{
			msg += ", 矩形宽度小于零"
		}
	}
	if msg != ""{
		// 有错误,创建对应的错误结构体(对象)
		return 0, &areaError{msg:msg, length:length,width:width}
	}
	return length*width, nil
}

panic()和recover()
golang(go)中引入两个内置函数panic和recover来触发和终止异常处理流程,同时引入关键字defer来延迟defer后面的内容,一直等到defer语句的函数执行完毕是,延迟函数defer候命的内容才会被执行,而不管defer语句是通过return正常结束,还是由于panic导致已成结束。

package main

import "fmt"

func main()  {
	defer func() {
		// recover()  返回panic中的参数类型
		// 这时说明恐慌了
		if msg:= recover() ;msg != nil{
			fmt.Println(msg, "程序恢复了") // 执行recover后,程序就可以恢复执行(不报错),类似于catch
			// 但是异常后面的程序是不会再运行了
		}
	}()
	funA()
	defer myPrint("defer main 3...")
	funB()
	defer myPrint("defer main 4...")
	fmt.Println("main over")

}
func myPrint(s string)  {
	fmt.Println(s)
}

func funA()  {
	fmt.Println("一个很普通的函数A")
	
}
func funB()  { // defer 为外围函数

	fmt.Println("一个很普通的函数B")
	defer myPrint("defer funcB 1...")
	for i:=1; i<=10;i++{
		fmt.Println("i:", i)
		if i == 5{
			// 让程序中断
			panic("funB恐慌了")  // 参数为一个接口,可以接受任意值
			// 这时程序后面的内容就不能再执行了
			// 注已经被defer的函数还会执行
		}
	}
	defer myPrint("defer funcB 2...")

}

什么时候使用错误,什么时候使用异常需要根据实际情况而定。例如:

  • 空指针引用
  • 下标越界
  • 除数为0
  • 不应该出现的分支,比如default
  • 输入不应该引起函数错误

等,可使用异常处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

智绘山河

你的鼓励可能解决你下一个问题

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值