Go study notes - errors and exceptions

Go exception handling syntax of the language is absolutely unique, in many high-level language I've ever seen in the form of error handling Go language is a wonderful work.

On the one hand it encourages you to use the C language in the form of an error in the form of return value to be passed, on the other hand it also provides a high-level language generally have exceptions thrown and caught, but do not encourage you to use this form.

Later we will return the value of unified form called "error" will be thrown capture form called "abnormal."

Go language error handling in the industry has been criticized, but since we have into this pit, that was a good squatting it.

Error Interface

Go language stipulates that those who object that implements the interface is error object error, this error interface defines only one method.

type error interface {
  Error() string
}

Note that the name of the interface, it is lowercase, is a built-in global interface. If a name is usually begin with a lowercase letter, so it is not visible outside the package, but the error is a special built-in name, it is globally visible.

Write a very simple error object, write a structure, then hung in Error () method on it.

package main

import "fmt"

type SomeError struct {
    Reason string
}

func (s SomeError) Error() string {
    return s.Reason
}

func main() {
    var err error = SomeError{"something happened"}
    fmt.Println(err)
}

---------------
something happened

For the error code in the form of the above objects is very common, it has a built-in Go generic type of error, errors in the bag. This package also offers a New () function allows us to easily create a generic error.

package errors

func New(text string) error {
    return &errorString{text}
}

type errorString struct {
    s string
}

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

Note that this structure is errorString first letter lowercase, it means that we can not directly use this type of name to construct the wrong object, you must use the New () function.

var err = errors.New("something happened")

If you need to customize error string parameters, use the fmt package provides functions Errorf

var thing = "something"
var err = fmt.Errorf("%s happened", thing)

The first error handling experience

In the Java language, if you encounter a problem usually throws IOException IO type of exception, the Go language which it does not throw an exception, but in the return value of the form to notify the upper layer logic to handle errors. Here we read through the file to try error handling Go language, read documents using the built-os package.

package main

import "os"
import "fmt"

func main() {
    // 打开文件
    var f, err = os.Open("main.go")
    if err != nil {
        // 文件不存在、权限等原因
        fmt.Println("open file failed reason:" + err.Error())
        return
    }
    // 推迟到函数尾部调用,确保文件会关闭
    defer f.Close()
    // 存储文件内容
    var content = []byte{}
    // 临时的缓冲,按块读取,一次最多读取 100 字节
    var buf = make([]byte, 100)
    for {
        // 读文件,将读到的内容填充到缓冲
        n, err := f.Read(buf)
        if n > 0 {
            // 将读到的内容聚合起来
            content = append(content, buf[:n]...)
        }
        if err != nil {
            // 遇到流结束或者其它错误
            break
        }
    }
    // 输出文件内容
    fmt.Println(string(content))
}

-------
package main

import "os"
import "fmt"
.....

There are several points to note in this code. The first should be noted that os.Open (), f.Read () function returns two values, the Go language not only allows the function returns two values, three values ​​of the four values ​​are possible, but the Go language no widespread use of multiple return values ​​of habit, just at the wrong time will need to return need to return two values. In addition to the errors, there is a need to place two return values, that is, the dictionary, by the second return value to inform the result of the reading is zero value or does not exist.

var score, ok := scores["apple"]

The second caveat is that defer keyword, it will close the call file postponed to the end of the current function of the execution, even if the code behind an exception is thrown, the file will be closed to ensure the implementation of, the equivalent of the Java language finally block . defer is a very important characteristic of the Go language in everyday application development, we often use to it.

The third place to note that append function parameters appear in the ... symbols. In the slice chapter, we know append function can be appended to a single element sections, in fact append a one-time function can append multiple elements, it's the number of parameters is variable.

var s = []int{1,2,3,4,5}
s = append(s,6,7,8,9)

However, the code needs to be read file is added to the contents of an entire slice of another slice, this time you need ... operator, its role is the transfer function to append all the elements in the slice parameter expansion. You may be concerned if there are hundreds of sliced ​​elements, and then expand into elemental transfer would not be very cost performance. This does not have to worry, launched only formal start, in fact, did not begin in the realization, passing the parameters or slice of nature in the past.

The fourth place to note that read file operations f.Read (), which puts the contents of the file where to slice filling, filling the amount does not exceed the length of the slice (note that not capacity). If this buffer into the following form, it will infinite loop!

var buf = make([]byte, 0, 100)

Also, if the end of the file, the slice will not fill. So the return value of n need to clear in the end how many bytes read.

Experience Redis error handling

The above example reads the file and did not let the reader feel unhappy error handling, here we want to introduce customers Go language Redis end package, to experience the real error handling Go language how unpleasant.

The use of third-party packages, you need go get instructions to download the package, the directive will be put under third-party packages GOPATH directory.

go get github.com/go-redis/redis

Now I want to implement a small function to get Redis two integer values, and then multiplied again stored in Redis

package main

import "fmt"
import "strconv"
import "github.com/go-redis/redis"

func main() {
 // 定义客户端对象,内部包含一个连接池
    var client = redis.NewClient(&redis.Options {
        Addr: "localhost:6379",
    })

    // 定义三个重要的整数变量值,默认都是零
    var val1, val2, val3 int

    // 获取第一个值
    valstr1, err := client.Get("value1").Result()
    if err == nil {
        val1, err = strconv.Atoi(valstr1)
        if err != nil {
            fmt.Println("value1 not a valid integer")
            return
        }
    } else if err != redis.Nil {
        fmt.Println("redis access error reason:" + err.Error())
        return
    }

    // 获取第二个值
    valstr2, err := client.Get("value2").Result()
    if err == nil {
        val2, err = strconv.Atoi(valstr2)
        if err != nil {
            fmt.Println("value1 not a valid integer")
            return
        }
    } else if err != redis.Nil {
        fmt.Println("redis access error reason:" + err.Error())
        return
    }

    // 保存第三个值
    val3 = val1 * val2
    ok, err := client.Set("value3",val3, 0).Result()
    if err != nil {
        fmt.Println("set value error reason:" + err.Error())
        return
    }
    fmt.Println(ok)
}

------
OK

Go languages ​​is not easy because the use of the exception clause, so what could be wrong for any need to determine the value of returns an error message. In addition to the above access code is necessary to determine Redis addition, string to integer need determination.

There is also a need for special attention because a zero value of the string is empty string not nil, is determined from the string you are not the content itself, the existence of this Redis key corresponding to the key value is an empty string, the return value need the error information to determine. Code is wrong redis.Nil target client specifically for this case there is no key defined.

Compared to the habit of writing a Python and Java programming friends, this type of erroneous judgment is simply too cumbersome hell. But then again, I get used to it.

Abnormal capture

Go language provides a global panic and recover function so that we can throw exceptions, catch exceptions. It is similar to other high-level language in common throw try catch statement, but it is very different, such as panic function can throw out any objects. Let's look at an example of using the panic

package main

import "fmt"

var negErr = fmt.Errorf("non positive number")

func main() {
    fmt.Println(fact(10))
    fmt.Println(fact(5))
    fmt.Println(fact(-5))
    fmt.Println(fact(15))
}

// 让阶乘函数返回错误太不雅观了
// 使用 panic 会合适一些
func fact(a int) int{
    if a <= 0 {
        panic(negErr)
    }
    var r = 1
    for i :=1;i<=a;i++ {
        r *= i
    }
    return r
}

-------
3628800
120
panic: non positive number

goroutine 1 [running]:
main.fact(0xfffffffffffffffb, 0x1)
    /Users/qianwp/go/src/github.com/pyloque/practice/main.go:16 +0x75
main.main()
    /Users/qianwp/go/src/github.com/pyloque/practice/main.go:10 +0x122
exit status 2

The code above throws negErr, a direct result of the crashes, the program prints the last exception stack information. Here we use the recover function to protect it, recover function requires a combination of defer statements used together, ensuring that recover () logic program exceptions when you can get called.

package main

import "fmt"

var negErr = fmt.Errorf("non positive number")

func main() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("error catched", err)
        }
    }()
    fmt.Println(fact(10))
    fmt.Println(fact(5))
    fmt.Println(fact(-5))
    fmt.Println(fact(15))
}

func fact(a int) int{
    if a <= 0 {
        panic(negErr)
    }
    var r = 1
    for i :=1;i<=a;i++ {
        r *= i
    }
    return r
}

-------
3628800
120
error catched non positive number

Exception stack information output results did not, indicating the success of the capture, but even if the program no longer crashes, the logic behind the outliers will no longer continue to be executed. The above code should be noted that we use the anonymous function func () {...}

defer func() {
  if err := recover(); err != nil {
   fmt.Println("error catched", err)
  }
}()

There is a tail parentheses how it is, why do we need the parentheses it? It represents the anonymous function was called. Compare documents written in front of the tail closed parenthesis will be able to understand

defer f.Close()

There are places worthy of note when, panic thrown object may not be the wrong target, and recover () object returned is the panic to throw objects, so it is not necessarily wrong object.

func panic(v interface{})
func recover() interface{}

We often need to recover () returns the result of the judgment, in order to pick out the type of the exception object we are willing to deal, for those who do not want to deal with, you can choose to throw again, so that the upper layer to deal with.

defer func() {
    if err := recover(); err != nil {
        if err == negErr {
            fmt.Println("error catched", err)
        } else {
            panic(err)  // rethrow
        }
    }
}()

Unusual real-world applications

Go Do not use language official statement panic recover, unless you really can not expect the middle of error may occur, or it can very dramatically simplify your code. Simply put, unless forced to, do not use it.

In a common Web applications, not because individual URL handler throws an exception caused by the collapse of the entire program, including the one you need to recover the outside of each URL processor () to restore the exception.

6943526-99e4173e40515094.png

In json serialization process, recursively processing all types of internal logic json, each type of inner container may experience the type can not be serialized. If you use each function returns the wrong way to write code that will become very complicated. So in the built-json package also used the panic, and then call in the outermost layer of wrapped recover function to recover and eventually return to a unified error type.

6943526-4a9c008248a0c821.png

You can imagine the built-json package developers in the design and development of this package should also tangled when bruised and battered, and ultimately the use of panic and recover to make their own code becomes look good.

More than defer statements

Sometimes we need to use multiple defer statement in a function. For example, copy files, you need to open the source and target files, then you need to call twice defer f.Close ().

package main

import "fmt"
import "os"

func main() {
    fsrc, err := os.Open("source.txt")
    if err != nil {
        fmt.Println("open source file failed")
        return
    }
    defer fsrc.Close()
    fdes, err := os.Open("target.txt")
    if err != nil {
        fmt.Println("open target file failed")
        return
    }
    defer fdes.Close()
    fmt.Println("do something here")
}

Note that the execution order of defer statements and the preparation of the code is, in turn, that the last sentence of the first to defer implementation of the rules in order to verify this, we have to look at rewriting the code above

package main

import "fmt"
import "os"

func main() {
    fsrc, err := os.Open("source.txt")
    if err != nil {
        fmt.Println("open source file failed")
        return
    }
    defer func() {
        fmt.Println("close source file")
        fsrc.Close()
    }()

    fdes, err := os.Open("target.txt")
    if err != nil {
        fmt.Println("open target file failed")
        return
    }
    defer func() {
        fmt.Println("close target file")
        fdes.Close()
    }()
    fmt.Println("do something here")
}

--------
do something here
close target file
close source file

Reproduced in: https: //www.jianshu.com/p/373ebafc6a80

Guess you like

Origin blog.csdn.net/weixin_34291004/article/details/91051956