Error
Go error就是一个普通的接口,普通的值。
http://golang.org/pkg/builtin/#error
type error interface {
Error() string
}
我们常用一个errors.New()来返回一个error对象。
http://golang.org/src/pkg/errors/errors.go
type errorString struct {
s string
}
http://golang.org/src/pkg/errors/errors.go
func (e *errorString) Error() string {
return e.s
}
基础库中大量自定义的error:
http://golang.org/src/pkg/bufio/bufio.go
var (
ErrInvalidUnreadByte = errors.New("Bufio: invalid use of UnreadByte")
...
)
errors.New()返回的是内部errorString对象的指针
http://golang.org/src/pkg/errors/errors.go
// 每次返回一个新的对象,取其地址,防止两次error new同样text时两次对象相等
func New(text string) error {
return &errorString{text}
}
例子:
package main
import (
"errors"
"fmt"
)
// create a named type for our new type.
type errorString string
// implement the error interface.
func (e errorString) Error() string {
return string(e)
}
// new creates interface values of type error.
func New(text string) error {
return errorString{text}
}
var ErrNamedType = New("EOF") // 自己构建的一种errorString的方法
var ErrStructType = errors.New("EOF") // 标准库的方法返回的指针
func main() {
// 自定义返回的对象
if ErrNamedType == New("EOF") {
fmt.Println("Named type Error")
}
// 重新用标准库返回的对象,在栈上new了两个指针,指向的内存地址肯定是不一样的
if ErrStructType == errors.New("EOF") {
fmt.Println("Struct type Error")
}
}
Output:
Named Type Error
例子:
package main
import "fmt"
// create a named type for our new type.
type errorString struct {
s string
}
// implement the error interface.
func (e errorString) Error() string {
return e.s
}
func NewError(text string) error {
return errorString{text} // 与标准库区别的是此处返回的是值类型,不是指针类型
}
var ErrType = NewError("EOF")
func main() {
if ErrType == NewError("EOF") {
fmt.Println("Error:", ErrType)
}
}
Output:
Error: EOF
因为在返回值不取地址的时候,会展开返回值的s字段做一个等值对比
Error vs Exception
各个语言的演进历史:
-
C
单返回值,一般通过传递指针作为入参,返回值为int表示成功还是失败。
-
C++
引入了exception,但是还无法知道被调用方会抛出什么异常。
-
Java
引入了checked exception,方法的所有者必须申明,调用者必须处理。在启动时抛出大量的异常时很常见的,并在他们的调用堆栈中尽职地记录下来。Java异常不再是异常,而变成了司空见惯,他们从良性到灾难性都有使用,异常的严重性由函数调用者来区分。
Go异常
Go的处理异常逻辑是不引入exception,支持多参数返回,所以使用者很容易在函数签名中带伤实现了error interface
的对象,交由调用者来判定。
如果一个函数返回了(value, error),你不能对这个value做任何假设,必须先判定error。唯一可以忽略error的是,若你连value也不关心。
Go也有panic机制,和其他语言的exception却不是相同的。当使用者抛出异常时,相当于使用者把exception扔给了调用者来处理。
比如在C++中,把string转换为int,如果转换失败,会抛出异常。
Go panic意味着fatal error(就是挂了)。不能假设调用者来解决panic,意味着代码不能继续运行了。
使用多个返回值和一个简单的约定,Go解决了让程序员知道什么时候除了问题,并为真正的异常情况保留了panic。
package main
import "fmt"
func handle() (int, error) {
return 1, nil
}
func main() {
i, err := handle()
if err != nil {
return
}
fmt.Println(i)
}
例子:
package main
import "fmt"
func Positive(n int) (bool, bool) {
if n == 0 {
return false, false
}
return n > -1, true
}
// 打印正数还是负数
func Check(n int) {
pos, ok := Positive(n)
if !ok {
fmt.Println(n, "is neither")
return
}
if pos {
fmt.Println(n, "is positive")
} else {
fmt.Println(n, "is negative")
}
}
Output:
1 is positive
0 is neither
-1 is negative
演进版本:
package main
import (
"errors"
"fmt"
)
func Positive(n int) (bool, error) {
if n == 0 {
return false, errors.New("undefined")
}
return n > -1, nil
}
// 打印正数还是负数
func Check(n int) {
pos, err := Positive(n)
if err != nil {
fmt.Println(n, err)
return
}
if pos {
fmt.Println(n, "is positive")
} else {
fmt.Println(n, "is negative")
}
}
func main() {
Check(1)
Check(0) // 0是既不是正数也不是负数
Check(-1)
}
Output:
1 is positive
0 undefined
-1 is negative
不建议的写法:
package main
import (
"fmt"
)
// 使用panic和recover来模拟类似try-catch这种写法
// In the case that n is 0, Positive will panic
func Positive(n int) bool {
if n == 0 {
panic("indefined")
}
return n > -1
}
// 打印正数还是负数
func Check(n int) {
defer func() {
if recover() != nil {
fmt.Println("is neither")
}
}()
if Positive(n) {
fmt.Println(n, "is positive")
} else {
fmt.Println(n, "is negative")
}
}
func main() {
Check(1)
Check(0) // 0是既不是正数也不是负数
Check(-1)
}
Go的panic处理出现了fatal error情况下使用,比如除以0值、数组越界、不可恢复的环境问题、栈溢出。而对于其他的错误情况,我们应该是期望使用error来进行判定。
you onle need to check the error value if you care about the result. --Dave
- 简单
- 考虑失败,而不是成功(plan for failure, not success)
- 没有隐藏的控制流
- 完全交给使用者控制error
- Error are values