go语言的魔幻旅程13-错误处理

266 阅读4分钟

更喜岷山千里雪,三军过后尽开颜

初学编程时总认为编程工作就是一堆杂乱无章的英文字符,既无生命力,也无成就感,这样的时间大概延续了一年左右的时间,中途好多次都曾经放弃,不过看着周围的其他人都在坚持学习,最终还是硬着头皮选择坚持了下来。其实从过往者的经历来讲,对于绝大部分的普通编程学习者而言,一年的时间确实是一个长久的考验期,坚持的下来就可以步入编程的大门,坚持不下来可能最终会功亏一篑。无论是单纯的控制台的输出还是简单Demo的搭建,最重要的是不断的坚持,并从一个个问题的解决中获取内心的成就感, 唯有坚持方能领略:“更喜岷山千里雪,三军过后尽开颜”喜悦。

go语言的错误处理

1、什么是错误?

所谓错误是指程序的运行的过程中出现了异常, 例如程序打开一个文件时,如果文件不存在,那么程序就会出现错误。

//举个栗子
func main() {  
    f, err := os.Open("/test.txt")
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(f.Name(), "opened successfully")
}

2、使用New函数自定义错误

//举个栗子
func circleArea(radius float64) (float64, error) {  
    if radius < 0 {
        return 0, errors.New("Area calculation failed, radius is less than zero")
    }
    return math.Pi * radius * radius, nil
}

3、使用结构体定义错误

//举个栗子
type areaError struct {  
    err    string //error description
    length float64 //length which caused the error
    width  float64 //width which caused the error
}


func (e *areaError) Error() string {  
    return e.err
}

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) {  
    err := ""
    if length < 0 {
        err += "length is less than zero"
    }
    if width < 0 {
        if err == "" {
            err = "width is less than zero"
        } else {
            err += ", width is less than zero"
        }
    }
    if err != "" {
        return 0, &areaError{err, length, width}
    }
    return length * width, nil
}

func main() {  
    length, width := -5.0, -9.0
    area, err := rectArea(length, width)
    if err != nil {
        if err, ok := err.(*areaError); ok {
            if err.lengthNegative() {
                fmt.Printf("error: length %0.2f is less than zero\n", err.length)

            }
            if err.widthNegative() {
                fmt.Printf("error: width %0.2f is less than zero\n", err.width)

            }
            return
        }
        fmt.Println(err)
        return
    }
    fmt.Println("area of rect", area)
}

panic

1、什么是panic?

panic是go语言的一种错误的处理机制,通常用于程序发生异常,无法正常处理的情形下我们会使用panic来终止程序的执行。

常用的使用场景:发生了一个不能恢复的错误,此时程序不能正常的执行;发生了编程上的错误(比如应该用指针接收的变量,调用的时候却使用nil进行调用)

//举个栗子
func fullName(firstName *string, lastName *string) {  
    if firstName == nil {
        panic("runtime error: first name cannot be nil")
    }
    if lastName == nil {
        panic("runtime error: last name cannot be nil")
    }
    fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}

func main() {  
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}

2、panic执行的过程中含有defer语句如何执行

执行的顺序是:按照栈的执行顺序先执行完defer语句,之后才执行panic语句

func fullName(firstName *string, lastName *string) {  
    defer fmt.Println("deferred call in fullName")
    if firstName == nil {
        panic("runtime error: first name cannot be nil")
    }
    if lastName == nil {
        panic("runtime error: last name cannot be nil")
    }
    fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}

func main() {  
    defer fmt.Println("deferred call in main")
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}


//执行结果
deferred call in fullName  
deferred call in main  
panic: runtime error: last name cannot be nil

goroutine 1 [running]:  
main.fullName(0x1042bf90, 0x0)  
    /tmp/sandbox060731990/main.go:13 +0x280
main.main()  
    /tmp/sandbox060731990/main.go:22 +0xc0

recover

1、什么是recover?

recover是一个内建的函数,用于重新获取对panic的控制,只有在延迟函数的内调用才有用。在延迟函数内调用 recover,可以取到 panic 的错误信息,并且停止 panic 续发事件(Panicking Sequence),程序运行恢复正常。如果在延迟函数的外部调用 recover,就不能停止 panic 续发事件。

//举个栗子
func recoverName() {  
    if r := recover(); r!= nil {
        fmt.Println("recovered from ", r)
    }
}

func fullName(firstName *string, lastName *string) {  
    defer recoverName()
    if firstName == nil {
        panic("runtime error: first name cannot be nil")
    }
    if lastName == nil {
        panic("runtime error: last name cannot be nil")
    }
    fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}

func main() {  
    defer fmt.Println("deferred call in main")
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}

2、panic、recover和go协程

当panic和recover和go协程结合使用的时候,只有在相同的协程中调用recover才管用,recover不能恢复一个不同协程的panic

//举个栗子
func recovery() {  
    if r := recover(); r != nil {
        fmt.Println("recovered:", r)
    }
}

func a() {  
    defer recovery()
    fmt.Println("Inside A")
    go b()
    time.Sleep(1 * time.Second)
}

func b() {  
    fmt.Println("Inside B")
    panic("oh! B panicked")
}

func main() {  
    a()
    fmt.Println("normally returned from main")
}

总结

不同编程语言处理异常的方式有所区别,但是核心的思想都大同小异,好的程序必然少不了对错误的预防处理,借用某位大佬的经典名言:程序员日常的工作不是在写bug,就是在解决bug的路上。