2
2
Go语言主要的设计准则是:简洁、明白,简洁是指语法和C类似,相当的简单,明白是指任何语句都是很明显的,不含有任何隐含的东西,在错误处理方案的设计中也贯彻了这一思想。我们知道在C语言里面是通过返回-1或者NULL之类的信息来表示错误,但是对于使用者来说,不查看相应的API说明文档,根本搞不清楚这个返回值究竟代表什么意思,比如:返回0是成功,还是失败,而Go定义了一个叫做error的类型,来显式表达错误。在使用时,通过把返回的error变量与nil的比较,来判定操作是否成功。例如` os.Open ` 函数在打开文件失败时将返回一个不为nil的error变量
3
3
``` Go
4
4
5
- func Open (name string ) (file *File , err error )
5
+ func Open (name string ) (file *File , err error )
6
6
```
7
7
下面这个例子通过调用`os.Open`打开一个文件,如果出现错误,那么就会调用`log.Fatal`来输出错误信息:
8
8
```Go
9
9
10
- f, err := os.Open("filename.ext")
11
- if err != nil {
12
- log.Fatal (err)
13
- }
10
+ f, err := os.Open("filename.ext")
11
+ if err != nil {
12
+ log.Fatal (err)
13
+ }
14
14
```
15
15
类似于` os.Open ` 函数,标准包中所有可能出错的API都会返回一个error变量,以方便错误处理,这个小节将详细地介绍error类型的设计,和讨论开发Web应用中如何更好地处理error。
16
16
## Error类型
17
17
error类型是一个接口类型,这是它的定义:
18
18
``` Go
19
19
20
- type error interface {
21
- Error () string
22
- }
20
+ type error interface {
21
+ Error () string
22
+ }
23
23
```
24
24
error是一个内置的接口类型,我们可以在/builtin/包下面找到相应的定义。而我们在很多内部包里面用到的 error是errors包下面的实现的私有结构errorString
25
25
``` Go
26
26
27
- // errorString is a trivial implementation of error.
28
- type errorString struct {
29
- s string
30
- }
27
+ // errorString is a trivial implementation of error.
28
+ type errorString struct {
29
+ s string
30
+ }
31
31
32
- func (e *errorString ) Error () string {
33
- return e.s
34
- }
32
+ func (e *errorString ) Error () string {
33
+ return e.s
34
+ }
35
35
```
36
36
你可以通过` errors.New ` 把一个字符串转化为errorString,以得到一个满足接口error的对象,其内部实现如下:
37
37
``` Go
38
38
39
- // New returns an error that formats as the given text.
40
- func New (text string ) error {
41
- return &errorString{text}
42
- }
39
+ // New returns an error that formats as the given text.
40
+ func New (text string ) error {
41
+ return &errorString{text}
42
+ }
43
43
```
44
44
下面这个例子演示了如何使用` errors.New ` :
45
45
``` Go
46
46
47
- func Sqrt (f float64 ) (float64 , error ) {
48
- if f < 0 {
49
- return 0 , errors.New (" math: square root of negative number" )
50
- }
51
- // implementation
47
+ func Sqrt (f float64 ) (float64 , error ) {
48
+ if f < 0 {
49
+ return 0 , errors.New (" math: square root of negative number" )
52
50
}
51
+ // implementation
52
+ }
53
53
```
54
54
在下面的例子中,我们在调用Sqrt的时候传递的一个负数,然后就得到了non-nil的error对象,将此对象与nil比较,结果为true,所以fmt.Println(fmt包在处理error时会调用Error方法)被调用,以输出错误,请看下面调用的示例代码:
55
55
``` Go
56
56
57
- f , err := Sqrt (-1 )
57
+ f , err := Sqrt (-1 )
58
58
if err != nil {
59
59
fmt.Println (err)
60
60
}
@@ -63,28 +63,28 @@ error是一个内置的接口类型,我们可以在/builtin/包下面找到相
63
63
通过上面的介绍我们知道error是一个interface,所以在实现自己的包的时候,通过定义实现此接口的结构,我们就可以实现自己的错误定义,请看来自Json包的示例:
64
64
``` Go
65
65
66
- type SyntaxError struct {
67
- msg string // 错误描述
68
- Offset int64 // 错误发生的位置
69
- }
66
+ type SyntaxError struct {
67
+ msg string // 错误描述
68
+ Offset int64 // 错误发生的位置
69
+ }
70
70
71
- func (e *SyntaxError ) Error () string { return e.msg }
71
+ func (e *SyntaxError ) Error () string { return e.msg }
72
72
```
73
73
Offset字段在调用Error的时候不会被打印,但是我们可以通过类型断言获取错误类型,然后可以打印相应的错误信息,请看下面的例子:
74
74
``` Go
75
75
76
- if err := dec.Decode (&val); err != nil {
77
- if serr , ok := err.(*json.SyntaxError ); ok {
78
- line , col := findLine (f, serr.Offset )
79
- return fmt.Errorf (" %s :%d :%d : %v " , f.Name (), line, col, err)
80
- }
81
- return err
76
+ if err := dec.Decode (&val); err != nil {
77
+ if serr , ok := err.(*json.SyntaxError ); ok {
78
+ line , col := findLine (f, serr.Offset )
79
+ return fmt.Errorf (" %s :%d :%d : %v " , f.Name (), line, col, err)
82
80
}
81
+ return err
82
+ }
83
83
```
84
84
需要注意的是,函数返回自定义错误时,返回值推荐设置为error类型,而非自定义错误类型,特别需要注意的是不应预声明自定义错误类型的变量。例如:
85
85
``` Go
86
86
87
- func Decode () *SyntaxError { // 错误,将可能导致上层调用者err!=nil的判断永远为true。
87
+ func Decode () *SyntaxError { // 错误,将可能导致上层调用者err!=nil的判断永远为true。
88
88
var err *SyntaxError // 预声明错误变量
89
89
if 出错条件 {
90
90
err = &SyntaxError{}
@@ -97,117 +97,117 @@ Offset字段在调用Error的时候不会被打印,但是我们可以通过类
97
97
上面例子简单的演示了如何自定义Error类型。但是如果我们还需要更复杂的错误处理呢?此时,我们来参考一下net包采用的方法:
98
98
``` Go
99
99
100
- package net
100
+ package net
101
101
102
- type Error interface {
103
- error
104
- Timeout () bool // Is the error a timeout?
105
- Temporary () bool // Is the error temporary?
106
- }
102
+ type Error interface {
103
+ error
104
+ Timeout () bool // Is the error a timeout?
105
+ Temporary () bool // Is the error temporary?
106
+ }
107
107
108
108
```
109
109
在调用的地方,通过类型断言err是不是net.Error,来细化错误的处理,例如下面的例子,如果一个网络发生临时性错误,那么将会sleep 1秒之后重试:
110
110
``` Go
111
111
112
- if nerr , ok := err.(net.Error ); ok && nerr.Temporary () {
113
- time.Sleep (1e9 )
114
- continue
115
- }
116
- if err != nil {
117
- log.Fatal (err)
118
- }
112
+ if nerr , ok := err.(net.Error ); ok && nerr.Temporary () {
113
+ time.Sleep (1e9 )
114
+ continue
115
+ }
116
+ if err != nil {
117
+ log.Fatal (err)
118
+ }
119
119
```
120
120
## 错误处理
121
121
Go在错误处理上采用了与C类似的检查返回值的方式,而不是其他多数主流语言采用的异常方式,这造成了代码编写上的一个很大的缺点:错误处理代码的冗余,对于这种情况是我们通过复用检测函数来减少类似的代码。
122
122
123
123
请看下面这个例子代码:
124
124
``` Go
125
125
126
- func init () {
127
- http.HandleFunc (" /view" , viewRecord)
128
- }
126
+ func init () {
127
+ http.HandleFunc (" /view" , viewRecord)
128
+ }
129
129
130
- func viewRecord (w http .ResponseWriter , r *http .Request ) {
131
- c := appengine.NewContext (r)
132
- key := datastore.NewKey (c, " Record" , r.FormValue (" id" ), 0 , nil )
133
- record := new (Record)
134
- if err := datastore.Get (c, key, record); err != nil {
135
- http.Error (w, err.Error (), 500 )
136
- return
137
- }
138
- if err := viewTemplate.Execute (w, record); err != nil {
139
- http.Error (w, err.Error (), 500 )
140
- }
130
+ func viewRecord (w http .ResponseWriter , r *http .Request ) {
131
+ c := appengine.NewContext (r)
132
+ key := datastore.NewKey (c, " Record" , r.FormValue (" id" ), 0 , nil )
133
+ record := new (Record)
134
+ if err := datastore.Get (c, key, record); err != nil {
135
+ http.Error (w, err.Error (), 500 )
136
+ return
137
+ }
138
+ if err := viewTemplate.Execute (w, record); err != nil {
139
+ http.Error (w, err.Error (), 500 )
141
140
}
141
+ }
142
142
```
143
143
上面的例子中获取数据和模板展示调用时都有检测错误,当有错误发生时,调用了统一的处理函数` http.Error ` ,返回给客户端500错误码,并显示相应的错误数据。但是当越来越多的HandleFunc加入之后,这样的错误处理逻辑代码就会越来越多,其实我们可以通过自定义路由器来缩减代码(实现的思路可以参考第三章的HTTP详解)。
144
144
``` Go
145
145
146
- type appHandler func (http.ResponseWriter , *http.Request ) error
146
+ type appHandler func (http.ResponseWriter , *http.Request ) error
147
147
148
- func (fn appHandler ) ServeHTTP (w http.ResponseWriter, r *http.Request) {
149
- if err := fn (w, r); err != nil {
150
- http.Error (w, err.Error (), 500 )
151
- }
148
+ func (fn appHandler ) ServeHTTP (w http .ResponseWriter , r *http .Request ) {
149
+ if err := fn (w, r); err != nil {
150
+ http.Error (w, err.Error (), 500 )
152
151
}
152
+ }
153
153
```
154
154
上面我们定义了自定义的路由器,然后我们可以通过如下方式来注册函数:
155
155
``` Go
156
156
157
- func init () {
158
- http.Handle (" /view" , appHandler (viewRecord))
159
- }
157
+ func init () {
158
+ http.Handle (" /view" , appHandler (viewRecord))
159
+ }
160
160
```
161
161
当请求/view的时候我们的逻辑处理可以变成如下代码,和第一种实现方式相比较已经简单了很多。
162
162
``` Go
163
163
164
- func viewRecord (w http .ResponseWriter , r *http .Request ) error {
165
- c := appengine.NewContext (r)
166
- key := datastore.NewKey (c, " Record" , r.FormValue (" id" ), 0 , nil )
167
- record := new (Record)
168
- if err := datastore.Get (c, key, record); err != nil {
169
- return err
170
- }
171
- return viewTemplate.Execute (w, record)
164
+ func viewRecord (w http .ResponseWriter , r *http .Request ) error {
165
+ c := appengine.NewContext (r)
166
+ key := datastore.NewKey (c, " Record" , r.FormValue (" id" ), 0 , nil )
167
+ record := new (Record)
168
+ if err := datastore.Get (c, key, record); err != nil {
169
+ return err
172
170
}
171
+ return viewTemplate.Execute (w, record)
172
+ }
173
173
```
174
174
上面的例子错误处理的时候所有的错误返回给用户的都是500错误码,然后打印出来相应的错误代码,其实我们可以把这个错误信息定义的更加友好,调试的时候也方便定位问题,我们可以自定义返回的错误类型:
175
175
``` Go
176
176
177
- type appError struct {
178
- Error error
179
- Message string
180
- Code int
181
- }
177
+ type appError struct {
178
+ Error error
179
+ Message string
180
+ Code int
181
+ }
182
182
```
183
183
这样我们的自定义路由器可以改成如下方式:
184
184
``` Go
185
185
186
- type appHandler func (http.ResponseWriter , *http.Request ) *appError
186
+ type appHandler func (http.ResponseWriter , *http.Request ) *appError
187
187
188
- func (fn appHandler ) ServeHTTP (w http.ResponseWriter, r *http.Request) {
189
- if e := fn (w, r); e != nil { // e is *appError, not os.Error.
190
- c := appengine.NewContext (r)
191
- c.Errorf (" %v " , e.Error )
192
- http.Error (w, e.Message , e.Code )
193
- }
188
+ func (fn appHandler ) ServeHTTP (w http .ResponseWriter , r *http .Request ) {
189
+ if e := fn (w, r); e != nil { // e is *appError, not os.Error.
190
+ c := appengine.NewContext (r)
191
+ c.Errorf (" %v " , e.Error )
192
+ http.Error (w, e.Message , e.Code )
194
193
}
194
+ }
195
195
```
196
196
这样修改完自定义错误之后,我们的逻辑处理可以改成如下方式:
197
197
``` Go
198
198
199
- func viewRecord (w http .ResponseWriter , r *http .Request ) *appError {
200
- c := appengine.NewContext (r)
201
- key := datastore.NewKey (c, " Record" , r.FormValue (" id" ), 0 , nil )
202
- record := new (Record)
203
- if err := datastore.Get (c, key, record); err != nil {
204
- return &appError{err, " Record not found" , 404 }
205
- }
206
- if err := viewTemplate.Execute (w, record); err != nil {
207
- return &appError{err, " Can't display record" , 500 }
208
- }
209
- return nil
199
+ func viewRecord (w http .ResponseWriter , r *http .Request ) *appError {
200
+ c := appengine.NewContext (r)
201
+ key := datastore.NewKey (c, " Record" , r.FormValue (" id" ), 0 , nil )
202
+ record := new (Record)
203
+ if err := datastore.Get (c, key, record); err != nil {
204
+ return &appError{err, " Record not found" , 404 }
205
+ }
206
+ if err := viewTemplate.Execute (w, record); err != nil {
207
+ return &appError{err, " Can't display record" , 500 }
210
208
}
209
+ return nil
210
+ }
211
211
```
212
212
如上所示,在我们访问view的时候可以根据不同的情况获取不同的错误码和错误信息,虽然这个和第一个版本的代码量差不多,但是这个显示的错误更加明显,提示的错误信息更加友好,扩展性也比第一个更好。
213
213
0 commit comments