Merging other languages
This commit is contained in:
413
en/11.1.md
413
en/11.1.md
@@ -1,200 +1,213 @@
|
||||
# 11.1 错误处理
|
||||
Go语言主要的设计准则是:简洁、明白,简洁是指语法和C类似,相当的简单,明白是指任何语句都是很明显的,不含有任何隐含的东西,在错误处理方案的设计中也贯彻了这一思想。我们知道在C语言里面是通过返回-1或者NULL之类的信息来表示错误,但是对于使用者来说,不查看相应的API说明文档,根本搞不清楚这个返回值究竟代表什么意思,比如:返回0是成功,还是失败,而Go定义了一个叫做error的类型,来显式表达错误。在使用时,通过把返回的error变量与nil的比较,来判定操作是否成功。例如`os.Open`函数在打开文件失败时将返回一个不为nil的error变量
|
||||
|
||||
func Open(name string) (file *File, err error)
|
||||
|
||||
下面这个例子通过调用`os.Open`打开一个文件,如果出现错误,那么就会调用`log.Fatal`来输出错误信息:
|
||||
|
||||
f, err := os.Open("filename.ext")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
类似于`os.Open`函数,标准包中所有可能出错的API都会返回一个error变量,以方便错误处理,这个小节将详细地介绍error类型的设计,和讨论开发Web应用中如何更好地处理error。
|
||||
## Error类型
|
||||
error类型是一个接口类型,这是它的定义:
|
||||
|
||||
type error interface {
|
||||
Error() string
|
||||
}
|
||||
|
||||
error是一个内置的接口类型,我们可以在/builtin/包下面找到相应的定义。而我们在很多内部包里面用到的 error是errors包下面的实现的私有结构errorString
|
||||
|
||||
// errorString is a trivial implementation of error.
|
||||
type errorString struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (e *errorString) Error() string {
|
||||
return e.s
|
||||
}
|
||||
|
||||
你可以通过`errors.New`把一个字符串转化为errorString,以得到一个满足接口error的对象,其内部实现如下:
|
||||
|
||||
// New returns an error that formats as the given text.
|
||||
func New(text string) error {
|
||||
return &errorString{text}
|
||||
}
|
||||
|
||||
下面这个例子演示了如何使用`errors.New`:
|
||||
|
||||
func Sqrt(f float64) (float64, error) {
|
||||
if f < 0 {
|
||||
return 0, errors.New("math: square root of negative number")
|
||||
}
|
||||
// implementation
|
||||
}
|
||||
|
||||
在下面的例子中,我们在调用Sqrt的时候传递的一个负数,然后就得到了non-nil的error对象,将此对象与nil比较,结果为true,所以fmt.Println(fmt包在处理error时会调用Error方法)被调用,以输出错误,请看下面调用的示例代码:
|
||||
|
||||
f, err := Sqrt(-1)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
## 自定义Error
|
||||
通过上面的介绍我们知道error是一个interface,所以在实现自己的包的时候,通过定义实现此接口的结构,我们就可以实现自己的错误定义,请看来自Json包的示例:
|
||||
|
||||
type SyntaxError struct {
|
||||
msg string // 错误描述
|
||||
Offset int64 // 错误发生的位置
|
||||
}
|
||||
|
||||
func (e *SyntaxError) Error() string { return e.msg }
|
||||
|
||||
Offset字段在调用Error的时候不会被打印,但是我们可以通过类型断言获取错误类型,然后可以打印相应的错误信息,请看下面的例子:
|
||||
|
||||
if err := dec.Decode(&val); err != nil {
|
||||
if serr, ok := err.(*json.SyntaxError); ok {
|
||||
line, col := findLine(f, serr.Offset)
|
||||
return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
需要注意的是,函数返回自定义错误时,返回值推荐设置为error类型,而非自定义错误类型,特别需要注意的是不应预声明自定义错误类型的变量。例如:
|
||||
|
||||
func Decode() *SyntaxError { // 错误,将可能导致上层调用者err!=nil的判断永远为true。
|
||||
var err *SyntaxError // 预声明错误变量
|
||||
if 出错条件 {
|
||||
err = &SyntaxError{}
|
||||
}
|
||||
return err // 错误,err永远等于非nil,导致上层调用者err!=nil的判断始终为true
|
||||
}
|
||||
|
||||
原因见 http://golang.org/doc/faq#nil_error
|
||||
|
||||
上面例子简单的演示了如何自定义Error类型。但是如果我们还需要更复杂的错误处理呢?此时,我们来参考一下net包采用的方法:
|
||||
|
||||
package net
|
||||
|
||||
type Error interface {
|
||||
error
|
||||
Timeout() bool // Is the error a timeout?
|
||||
Temporary() bool // Is the error temporary?
|
||||
}
|
||||
|
||||
在调用的地方,通过类型断言err是不是net.Error,来细化错误的处理,例如下面的例子,如果一个网络发生临时性错误,那么将会sleep 1秒之后重试:
|
||||
|
||||
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
|
||||
time.Sleep(1e9)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
## 错误处理
|
||||
Go在错误处理上采用了与C类似的检查返回值的方式,而不是其他多数主流语言采用的异常方式,这造成了代码编写上的一个很大的缺点:错误处理代码的冗余,对于这种情况是我们通过复用检测函数来减少类似的代码。
|
||||
|
||||
请看下面这个例子代码:
|
||||
|
||||
func init() {
|
||||
http.HandleFunc("/view", viewRecord)
|
||||
}
|
||||
|
||||
func viewRecord(w http.ResponseWriter, r *http.Request) {
|
||||
c := appengine.NewContext(r)
|
||||
key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
|
||||
record := new(Record)
|
||||
if err := datastore.Get(c, key, record); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
if err := viewTemplate.Execute(w, record); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
}
|
||||
}
|
||||
|
||||
上面的例子中获取数据和模板展示调用时都有检测错误,当有错误发生时,调用了统一的处理函数`http.Error`,返回给客户端500错误码,并显示相应的错误数据。但是当越来越多的HandleFunc加入之后,这样的错误处理逻辑代码就会越来越多,其实我们可以通过自定义路由器来缩减代码(实现的思路可以参考第三章的HTTP详解)。
|
||||
|
||||
type appHandler func(http.ResponseWriter, *http.Request) error
|
||||
|
||||
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if err := fn(w, r); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
}
|
||||
}
|
||||
|
||||
上面我们定义了自定义的路由器,然后我们可以通过如下方式来注册函数:
|
||||
|
||||
func init() {
|
||||
http.Handle("/view", appHandler(viewRecord))
|
||||
}
|
||||
|
||||
当请求/view的时候我们的逻辑处理可以变成如下代码,和第一种实现方式相比较已经简单了很多。
|
||||
|
||||
func viewRecord(w http.ResponseWriter, r *http.Request) error {
|
||||
c := appengine.NewContext(r)
|
||||
key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
|
||||
record := new(Record)
|
||||
if err := datastore.Get(c, key, record); err != nil {
|
||||
return err
|
||||
}
|
||||
return viewTemplate.Execute(w, record)
|
||||
}
|
||||
|
||||
上面的例子错误处理的时候所有的错误返回给用户的都是500错误码,然后打印出来相应的错误代码,其实我们可以把这个错误信息定义的更加友好,调试的时候也方便定位问题,我们可以自定义返回的错误类型:
|
||||
|
||||
type appError struct {
|
||||
Error error
|
||||
Message string
|
||||
Code int
|
||||
}
|
||||
|
||||
这样我们的自定义路由器可以改成如下方式:
|
||||
|
||||
type appHandler func(http.ResponseWriter, *http.Request) *appError
|
||||
|
||||
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if e := fn(w, r); e != nil { // e is *appError, not os.Error.
|
||||
c := appengine.NewContext(r)
|
||||
c.Errorf("%v", e.Error)
|
||||
http.Error(w, e.Message, e.Code)
|
||||
}
|
||||
}
|
||||
|
||||
这样修改完自定义错误之后,我们的逻辑处理可以改成如下方式:
|
||||
|
||||
func viewRecord(w http.ResponseWriter, r *http.Request) *appError {
|
||||
c := appengine.NewContext(r)
|
||||
key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
|
||||
record := new(Record)
|
||||
if err := datastore.Get(c, key, record); err != nil {
|
||||
return &appError{err, "Record not found", 404}
|
||||
}
|
||||
if err := viewTemplate.Execute(w, record); err != nil {
|
||||
return &appError{err, "Can't display record", 500}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
如上所示,在我们访问view的时候可以根据不同的情况获取不同的错误码和错误信息,虽然这个和第一个版本的代码量差不多,但是这个显示的错误更加明显,提示的错误信息更加友好,扩展性也比第一个更好。
|
||||
|
||||
## 总结
|
||||
在程序设计中,容错是相当重要的一部分工作,在Go中它是通过错误处理来实现的,error虽然只是一个接口,但是其变化却可以有很多,我们可以根据自己的需求来实现不同的处理,最后介绍的错误处理方案,希望能给大家在如何设计更好Web错误处理方案上带来一点思路。
|
||||
|
||||
## links
|
||||
* [目录](<preface.md>)
|
||||
* 上一节: [错误处理,调试和测试](<11.0.md>)
|
||||
* 下一节: [使用GDB调试](<11.2.md>)
|
||||
# 11.1 Error handling
|
||||
|
||||
Go's major design considerations are rooted in the following ideas: a simple, clear, and concise syntax (similar to C), statements are explicit and don't contain hidden any hidden or unexpected things. Go's error handling scheme reflects all of these principles in the way that it's implemented. If you're familiar with the C language, you'll know that it's common to return -1 or NULL values to indicate that an error has occurred. However users who are not familiar with C's API will not know exactly what these return values mean. In C, it's not explicit whether a value of `0` indicates success of failure. On the other hand, Go explicitly defines a type called `error` for the sole purpose of expressing errors. Whenever a function returns, we check to see whether the error variable is `nil` or not to determine if the operation was successful. For example, the `os.Open` function fails, it will return a non-nil error variable.
|
||||
|
||||
func Open(name string) (file * File, err error)
|
||||
|
||||
Here's an example of how we'd handle an error in `os.Open`. First, we attempt to open a file. When the function returns, we check to see whether it succeeded or not by comparing the error return value with nil, calling `log.Fatal` to output an error message if it's a non-nil value:
|
||||
|
||||
f, err := os.Open("filename.ext")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
Similar to the `os.Open` function, the functions in Go's standard packages all return error variables to facilitate error handling. This section will go into detail about the design of error types and discuss how to properly handle errors in web applications.
|
||||
|
||||
## Error type
|
||||
|
||||
`error` is an interface type with the following definition:
|
||||
|
||||
type error interface {
|
||||
Error() string
|
||||
}
|
||||
|
||||
`error` is a built-in interface type, we can / builtin / pack below to find the appropriate definition. And we have a lot of internal error is used inside the package packet errors following implementation structure errorString private
|
||||
|
||||
|
||||
|
||||
`error` is a built-in interface type. We can find the corresponding definition in the builtin package below. We also have a lot of internal packages using the error in a private structure called `errorString`, which implements the error interface:
|
||||
|
||||
// errorString is a trivial implementation of error.
|
||||
type errorString struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (e *errorString) Error() string {
|
||||
return e.s
|
||||
}
|
||||
|
||||
You can convert a regular string to an `errorString` through `errors.New` in order to get an object that satisfies the error interface. Its internal implementation is as follows:
|
||||
|
||||
// New returns an error that formats as the given text.
|
||||
func New(text string) error {
|
||||
return &errorString{text}
|
||||
}
|
||||
|
||||
The following example demonstrates how to use `errors.New`:
|
||||
|
||||
func Sqrt(f float64) (float64, error) {
|
||||
if f < 0 {
|
||||
return 0, errors.New("math: square root of negative number")
|
||||
}
|
||||
// implementation
|
||||
}
|
||||
|
||||
In the following example, we pass a negative number to our `Sqrt` function. Checking the `err` variable, we check whether the error object is non-nil using a simple nil comparison. The result of the comparison is true, so `fmt.Println` (the `fmt` package calls the error method when dealing with error calls) is called to output an error.
|
||||
|
||||
f, err := Sqrt(-1)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
## Custom Errors
|
||||
|
||||
Through the above description, we know that a go Error is an interface. By defining a struct that implements this interface, we can implement their error definitions. Here's an example from the JSON package:
|
||||
|
||||
type SyntaxError struct {
|
||||
msg string // error description
|
||||
Offset int64 // where the error occurred
|
||||
}
|
||||
|
||||
func (e * SyntaxError) Error() string {return e.msg}
|
||||
|
||||
The error's `Offset` field will not be printed at runtime when syntax errors occur, but using a type assertion error type, you can print the desired error message:
|
||||
|
||||
if err := dec.Decode(&val); err != nil {
|
||||
if serr, ok := err.(*json.SyntaxError); ok {
|
||||
line, col := findLine(f, serr.Offset)
|
||||
return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
It should be noted that when the function returns a custom error, the return value is set to the recommend type of error rather than a custom error type. Be careful not to pre-declare variables of custom error types. For example:
|
||||
|
||||
func Decode() *SyntaxError {
|
||||
// error, which may lead to the caller's err != nil comparison to always be true.
|
||||
var err * SyntaxError // pre-declare error variable
|
||||
if an error condition {
|
||||
err = &SyntaxError{}
|
||||
}
|
||||
return err // error, err always equal non-nil, causes caller's err != nil comparison to always be true
|
||||
}
|
||||
|
||||
See http://golang.org/doc/faq#nil_error for an in depth explanation
|
||||
|
||||
The above example shows how to implement a simple custom Error type. But what if we need more sophisticated error handling? In this case, we have to refer to the `net` package approach:
|
||||
|
||||
package net
|
||||
|
||||
type Error interface {
|
||||
error
|
||||
Timeout() bool // Is the error a timeout?
|
||||
Temporary() bool // Is the error temporary?
|
||||
}
|
||||
|
||||
|
||||
Using type assertion, we can check whether or not our error is of type net.Error, as shown in the following example. This allows us to refine our error handling -if a temporary error occurs on the network, it will sleep for 1 second, then retry the operation.
|
||||
|
||||
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
|
||||
time.Sleep(1e9)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
## Error handling
|
||||
|
||||
Go handles errors and checks the return values of functions in a C-like fashion, which is different than what most of the other major languages do. This makes the code more explicit and predictable, but also more verbose. To reduce the redundancy of our error-handling code, we can use abstract error handling functions that allow us to implement similar error handling behaviour:
|
||||
|
||||
func init() {
|
||||
http.HandleFunc("/view", viewRecord)
|
||||
}
|
||||
|
||||
func viewRecord(w http.ResponseWriter, r *http.Request) {
|
||||
c := appengine.NewContext(r)
|
||||
key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
|
||||
record := new(Record)
|
||||
if err := datastore.Get(c, key, record); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
if err := viewTemplate.Execute(w, record); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
}
|
||||
}
|
||||
|
||||
The above example demonstrate access to data and template call has detected error when an error occurs , call a unified handler http.Error, returns a 500 error code to the client , and display the corresponding error data. But when more and more HandleFunc join, so error-handling logic code will be more and more, in fact, we can customize the router to reduce code ( refer to realize the idea of the third chapter of HTTP Detailed) .
|
||||
|
||||
The above function is an example of getting data and handling an error when it occurs by calling a unified error processing function called `http.Error`. In this case, it will return an Internal Error 500 code to the client, and display the corresponding error data. Even using this method however, when more and more `HandleFunc`'s are needed, the error-handling logic can still become quite bloated. An alternative approach would be to customize our router to handle errors by default:
|
||||
|
||||
type appHandler func(http.ResponseWriter, *http.Request) error
|
||||
|
||||
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if err := fn(w, r); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
}
|
||||
}
|
||||
|
||||
Above we've defined a custom router. We can then register our handler as usual:
|
||||
|
||||
func init() {
|
||||
http.Handle("/view", appHandler(viewRecord))
|
||||
}
|
||||
|
||||
The `/view` handler can then be handled by the following code; it is a lot simpler than our original implementation isn't it?
|
||||
|
||||
func viewRecord(w http.ResponseWriter, r *http.Request) error {
|
||||
c := appengine.NewContext(r)
|
||||
key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
|
||||
record := new(Record)
|
||||
if err := datastore.Get(c, key, record); err != nil {
|
||||
return err
|
||||
}
|
||||
return viewTemplate.Execute(w, record)
|
||||
}
|
||||
|
||||
The error handler example above will return the 500 Internal Error code to users when any errors occur, in addition to printing out the corresponding error code. In fact, we can customize the type of error returned to output a more developer friendly error message with information that is useful for debugging like so:
|
||||
|
||||
type appError struct {
|
||||
Error error
|
||||
Message string
|
||||
Code int
|
||||
}
|
||||
|
||||
Our custom router can be changed accordingly:
|
||||
|
||||
type appHandler func(http.ResponseWriter, *http.Request) *appError
|
||||
|
||||
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if e := fn(w, r); e != nil { // e is *appError, not os.Error.
|
||||
c := appengine.NewContext(r)
|
||||
c.Errorf("%v", e.Error)
|
||||
http.Error(w, e.Message, e.Code)
|
||||
}
|
||||
}
|
||||
|
||||
After we've finished modifying our custom error, our logic can be changed as follows:
|
||||
|
||||
func viewRecord(w http.ResponseWriter, r *http.Request) *appError {
|
||||
c := appengine.NewContext(r)
|
||||
key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
|
||||
record := new(Record)
|
||||
if err := datastore.Get(c, key, record); err != nil {
|
||||
return &appError{err, "Record not found", 404}
|
||||
}
|
||||
if err := viewTemplate.Execute(w, record); err != nil {
|
||||
return &appError{err, "Can't display record", 500}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
As shown above, we can return different error codes and error messages in our views, depending on the situation. Although this version of our code functions similarly to the previous version, it's more explicit, and its error message prompts are more comprehensible. All of these factors can help to make your application more scalable as complexity increases.
|
||||
|
||||
## Summary
|
||||
|
||||
Fault tolerance is a very important aspect of any programming language. In Go, it is achieved through error handling. Although `Error` is only one interface, it can have many variations in the way that it's implemented, and we can customize it according to our needs on a case by case basis. By introducing these various error handling concepts, we hope that you will have gained some insight on how to implement better error handling schemes in your own web applications.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [Error handling, debugging and testing](11.0.md)
|
||||
- Next section: [Debugging by using GDB](11.2.md)
|
||||
|
||||
Reference in New Issue
Block a user