这是我参与8月更文挑战的第17天,活动详情查看:8月更文挑战
什么是错误
错误指出程序中的异常情况。假设我们正在尝试打开一个文件,文件系统中不存在这个文件。这是一个异常情况,它表示为一个错误。
Go中的错误也是一种类型。错误用内置的error 类型表示。就像其他类型的,如int,float64,。错误值可以存储在变量中,从函数中返回,等等。
如何使用错误
让我们从一个示例程序开始,这个程序尝试打开一个不存在的文件。
示例代码:
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")
}
在os包中有打开文件的功能函数:
func Open(name string) (file *File, err error)
如果文件已经成功打开,那么Open函数将返回文件处理。如果在打开文件时出现错误,将返回一个非nil错误。
如果一个函数或方法返回一个错误,那么按照惯例,它必须是函数返回的最后一个值。因此,Open 函数返回的值是最后一个值。
处理错误的惯用方法是将返回的错误与nil进行比较。nil值表示没有发生错误,而非nil值表示出现错误。在我们的例子中,我们检查错误是否为nil。如果它不是nil,我们只需打印错误并从主函数返回。
运行结果:
open /test.txt: No such file or directory
我们得到一个错误,说明该文件不存在。
示例代码:
package main
import (
"fmt"
"errors"
)
func checkAge(age int) error {
if age < 0 {
//return errors.New("年龄不能为负数")
// fmt包的Errorf函数的用武之地。这个函数根据一个格式说明器格式化错误,并返回一个字符串作为值来满足错误。
return fmt.Errorf("age是:%d,年龄不能为负数", age)
}
fmt.Println("年龄无误,", age)
return nil
}
func test()(int, int, error) {
return 1, 2, errors.New("啦啦啦")
}
func main() {
/*
在go中并不是像Java语言那样,有直接的异常处理机制。而是使用error.
go中的error也是一种类型,内置接口类型的错误是表示错误条件的常规接口,nil值表示没有错误。
*/
var e1 error // <nil>
fmt.Println(e1)
fmt.Printf("%T\n", e1) // <nil>
//创建一个错误对象
e2 := errors.New("错误了")
fmt.Println(e2) // 错误了
fmt.Printf("%T\n", e2) // *errors.errorString
err:=checkAge(-30)
if err == nil{
fmt.Println("ok。。。")
}else{
fmt.Println("失败,,", err)
}
a, b, err:= test()
fmt.Println(a, b, err)
p1:= new(int)
fmt.Printf("%T,\n",p1)
fmt.Println(*p1)
p2:=new(string)
fmt.Printf("%T\n",p2)
fmt.Println(*p2)
}
运行结果:
<nil>
<nil>
错误了
*errors.errorString
失败,, age是:-30,年龄不能为负数
1 2 啦啦啦
*int,
0
*string
错误类型表示
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
让我们再深入一点,看看如何定义错误类型的构建。错误是一个带有以下定义的接口类型,
type error interface {
Error() string
}
它包含一个带有Error()字符串的方法。任何实现这个接口的类型都可以作为一个错误使用。这个方法提供了对错误的描述。
当打印错误时,fmt.Println函数在内部调用Error() 方法来获取错误的描述。这就是错误描述是如何在一行中打印出来的。
不要忽略错误
永远不要忽略一个错误。重新编写一个示例,该示例列出了与模式匹配的所有文件的名称,而忽略了错误处理代码。
package main
import (
"fmt"
"path/filepath"
)
func main() {
files, _ := filepath.Glob("[")
fmt.Println("matched files", files)
}
我们从前面的例子中已经知道模式是无效的。我忽略了Glob函数返回的错误,方法是使用行号中的空白标识符。
matched files []
由于我们忽略了这个错误,输出看起来好像没有文件匹配这个模式,但是实际上这个模式本身是畸形的。所以不要忽略错误。
自定义错误
创建自定义错误的最简单方法是使用错误包的新功能。
在使用新函数创建自定义错误之前,让我们了解它是如何实现的。下面提供了错误包中的新功能的实现。
// Package errors implements functions to manipulate errors.
package errors
// New returns an error that formats as the given text.
func New(text string) error {
return &errorString{text}
}
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
既然我们知道了新函数是如何工作的,那么就让我们在自己的程序中使用它来创建一个自定义错误。
我们将创建一个简单的程序,计算一个圆的面积,如果半径为负,将返回一个错误。
我们通过fmt包的Errorf函数。这个函数根据一个格式返回一个字符串作为值来满足错误。
使用Errorf函数:
package main
import (
"fmt"
"math"
)
func circleArea(radius float64) (float64, error) {
if radius < 0 {
return 0, fmt.Errorf("Area calculation failed, radius %0.2f is less than zero", radius)
}
return math.Pi * radius * radius, nil
}
func main() {
radius := -20.0
area, err := circleArea(radius)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("Area of circle %0.2f", area)
}
运行结果:
Area calculation failed, radius -20.00 is less than zero
Panic和Recover
Go语言追求简洁优雅,所以,Go语言不支持传统的 try…catch…finally 这种异常,因为Go语言的设计者们认为,
将异常与控制结构混在一起会很容易使得代码变得混乱。因为开发者很容易滥用异常,甚至一个小小的错误都抛出一个异常。
在Go语言中,使用多值返回来返回错误。不要用异常代替错误,更不要用来控制流程。
在极个别的情况下,也就是说,遇到真正的异常的情况下(比如除数为0了)。
才使用Go中引入的Exception处理:defer, panic, recover。
Go没有像Java那样的异常机制,它不能抛出异常,而是使用了panic和recover机制。一定要记住,你应当把它作为最后的手段来使用,也就是说,你的代码中应当没有,或者很少有panic的东西。
Panic是一个内建函数,可以中断原有的控制流程,进入一个令人恐慌的流程中。当函数F调用panic,函数F的执行被中断,但是F中的延迟函数(defer)会正常执行,然后F返回到调用它的地方。在调用的地方,F的行为就像调用了panic。这一过程继续向上,直到发生panic的goroutine中所有调用的函数返回,此时程序退出。异常错误可以直接调用panic产生。也可以由运行时错误产生,例如访问越界的数组。
Recover是一个内建的函数,可以让进入令人恐慌的流程中的goroutine恢复过来。recover仅在延迟函数中有效。在正常的执行过程中,调用recover会返回nil,并且没有其它任何效果。如果当前的goroutine陷入恐慌,调用recover可以捕获到panic的输入值,并且恢复正常的执行。
下面这个函数演示了如何在过程中使用panic
var user = os.Getenv("USER")
func init() {
if user == "" {
panic("no value for $USER")
}
}
下面这个函数检查作为其参数的函数在执行时是否会产生panic:
func throwsPanic(f func()) (b bool) {
defer func() {
if x := recover(); x != nil {
b = true
}
}()
f() //执行函数f,如果f中出现了panic,那么就可以恢复回来
return
}
示例代码:
package main
import "fmt"
func main() {
/*
panic:词意"恐慌"
内建函数,可以中断原有的控制流程。进入一个恐慌中。
recover:词意"恢复"
通过恢复正常的执行并检索传递给panic的调用的错误值来停止恐慌序列
注意:panic可以在任何地方引发,但是recover只能在defer函数中有效
*/
testA()
testB()
testC()
}
func testA() {
fmt.Println("\t函数A。。。")
}
func testB() {
defer func() { // 必须是要先声明defer,否则不能捕获到panic异常
if r:= recover(); r != nil{ // 这里的err其实就是panic传入的内容
fmt.Println(r,"revocer....")
}
}()
for i:=0;i<10;i++{
fmt.Println("函数B。。。", i)
if i == 5{
panic("panic....")
}
}
}
func testC() {
fmt.Println("\t函数C...")
}
运行结果:
函数A。。。
函数B。。。 0
函数B。。。 1
函数B。。。 2
函数B。。。 3
函数B。。。 4
函数B。。。 5
panic.... revocer....
函数C...