从函数中返回错误
从函数中返回的错误值除了携带关于错误的描述信息以外,还可以将错误发生的位置、时间、环境等上下文信息一并提供给函数调用者,快速诊断故障。
Go语言处理错误的方式具有显著的优点:
- 函数中发现的错误并不在函数中处理,而是在调用该函数的地方。这为错误处理提供了极大的灵活性,将更多主动权交给函数使用者,而不是由其缔造者自以为是地越俎代庖。
- 以返回错误值而非抛出异常的方式宣告错误,给函数调用者以更大的自由度,毕竟处理不处理错误以及怎样处理错误,应该由函数的用户说了算,一切强加的都是违背人性的。
- 每次调用函数或方法都做错误检查,这种做法看似繁琐,但错误可像其它任何类型的数据一样在函数之间传递,对它们的控制会更加简单而且直接,这意味着代码会更加简洁。
// 从函数中返回错误
// 标准库中的error是一个接口
//
// type error interface {
// Error() string
// }
//
// 通过实现该接口,可以自定义错误信息的内容和格式
//
// func (err 自定义错误类型) Error() string {
// return 错误信息字符串
// }
package main
import (
"fmt"
"runtime"
)
// 自定义错误类型
type detailedError struct {
file string // 记录引发错误的源文件
line int // 记录引发错误的行号
desc string // 给出错误的具体描述
}
// 为自定义错误类型实现error接口中的Error方法
func (err detailedError) Error() string {
return fmt.Sprintf("%v:%v> %v", // 错误创建方法2
err.file, err.line, err.desc)
}
// 返回自定义错误类型的构造函数
func newError(pc uintptr, file string,
line int, ok bool) func(desc string) error {
return func(desc string) error {
return detailedError{file, line, desc}
}
}
// 可能出现问题的函数
func half(n int) (int, error) {
if n%2 != 0 {
return -1, newError(runtime.Caller(0))( // 返回调用点的上下文信息
fmt.Sprintf("Unable to half %v", n))
}
return n / 2, nil
}
func main() {
h, err := half(19)
if err != nil {
fmt.Println(err)
// G:/GoWorkspace/src/error/return/main.go:47> Unable to half 19
return
}
fmt.Println(h)
}
错误与可用性
除了从纯技术角度考虑有关错误的产生和处理方式以外,还需站在用户的立场上做更加精细的设计。毕竟所编写的库或者包是提供给他人使用的,其中错误系统的合理性和有效性,将极大地影响到库或者包的可用性和用户体验
向用户报告错误不是为了推卸责任,而是为了提供真正有价值的帮助,例如:
- 哪里出现了问题?
- 出现了什么问题?
- 为什么会出问题?
- 应如何解决问题?
如果从库或者包中能够获得形式一致且真正有用的错误信息,用户从错误中恢复的可能性就会更高,并因此坚信这样的库或者包不仅有用而且值得信赖。
恐慌
panic是Go语言的内置函数,调用它将立即终止当前运行的程序并引发恐慌。
- panic("Oh no, I can do no more. Goodbye")
panic函数不会返回,在其将参数字符串打印出来以后程序即刻崩溃,系统会打印出更多详细信息。
恐慌通常意味着很严重的问题,调用panic函数实是无奈之举,应被视为一种没有办法的办法,因此千万不要滥用。出现以下情形引发恐慌是合理的:
- 程序已经处于无法恢复的状态,如果继续运行只会带来更多更严重的问题。
- 发生了无法处理的错误,无论做什么都存在风险,最好还是什么都不要做。
// 引发恐慌
// 内置函数panic可引发恐慌并令程序停止运行
2
3 package main
4
5 import "fmt"
6
7 func foo() {
8 panic("Oh no, I can do no more. Goodbye")
9 }
10
11 func main() {
12 fmt.Println("This is executed")
13
14 foo()
15
16 fmt.Println("This is not executed") // 未执行
17 }
// This is executed
// panic: Oh no, I can do no more. Goodbye
// goroutine 1 [running]:
// main.foo(...)
// E:GoWorkSpace/src/error/panic/main.go:8 main.main()
// E:GoWorkSpace/src/error/panic/main.go:14 +0x80
// exit status 2