-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherrmgmt.slide
More file actions
250 lines (132 loc) · 5.95 KB
/
errmgmt.slide
File metadata and controls
250 lines (132 loc) · 5.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
Effective Error Management
Brian Ketelsen
me@brianketelsen.com
@bketelsen
* Effective Error Management
In this section you’ll learn how to create and handle errors effectively.
Topics:
- Creating Errors
- Handling Errors
- Differences between libraries and applications
- Guidelines
You’ll end up with a clear guideline for error handling, in both your library code and your applications.
* Philosophy: Go and Errors
Proverb:
Errors are just values.
.caption _Rob_Pike_ at [[https://www.youtube.com/watch?v=PAAkCSZUG1c][Gopherfest SV, 2015]]
.link https://go-proverbs.github.io/
* Philosophy: Go and Errors
What does that mean?
Functionally, it means that *error* is an interface in Go. Any type that implements the function
Error() string
can be used as an *error* type.
* Philosophy: Go and Errors
Conceptually, it means that you have a lot of freedom and flexibility to do error handling.
* Creating Errors - errors.New()
- You can use the `errors` package to create a descriptive error on the fly
return errors.New("Login Failed")
- Simple
- Least amount flexibility
* Creating Errors - Wrapping an Error
- You can use fmt.Errorf() to wrap an error with more context
return fmt.Errorf("Login failed:", err)
- Adds some context
- Harder to determine original error type
* Creating Errors - Exporting Named Errors
- You can create known error types and return them
return ErrInvalidLogin
.code errmgmt/includes/errs/errtype.go /START OMIT/,/END OMIT/
- Easy to compare and test
- Requires discipline to remember to define these when you find new error conditions
* Creating Errors - Exporting Custom Types
- You can create a custom error type
Code next slide
- Great flexibility
- Good when you might want additional error state stored in the error (time)
- I've used these in web applications/API's where I want to send a very consistent message back to the user, serialized as JSON.
* Creating Errors - Exporting Custom Types
.code errmgmt/includes/errs/errstruct.go /START OMIT/,/END OMIT/
* Creating Errors - Your Own Types
- You can implement the `error` interface on your domain types
.code errmgmt/includes/errs/owntype.go /START OMIT/,/END OMIT/
- Creative
- Requires setting your type's ErrMsg equivalent when an error occurs
- Great for a state machine
* Handling Errors
There are really three ways to test error conditions
- Compare with known values
- Assert error type
- Ignore and pass up the call stack
* Handling Errors - Sentinel Errors
If an error is your own package, or exported from an external package, you can test the error in your code.
Dave Cheney calls these "Sentinel errors" *[1]
if err == io.EOF
[1] http://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully
* Handling Errors - Sentinel Errors
.code errmgmt/includes/errs/knowntype.go /START OMIT/,/END OMIT/
* Handling Errors - Sentinel Errors
- No context available to the caller, just a known error condition
- High coupling between caller and called packages
- Defeated if you add extra context by wrapping the error:
return fmt.Errorf("DB Error:", ErrDBUnavailable)
// NOT == ErrDBUnavailable now
- Many packages export error types, especially in network, OS, and database operations
* Handling Errors - Assert with Custom Type
Using custom types provides you with more flexibility in your error handling logic.
.code errmgmt/includes/errs/structcheck.go /START OMIT/,/END OMIT/
* Handling Errors - Assert with Custom Types
- Testing for error type lets you embed useful context in your error
- Error handling can be very specific
- Still highly coupled, sometimes this is OK and preferred
* Handling Errors - Ignore and Pass Up The Stack
return db.Login(u,p)
- Not My Problem(tm)
- Do this deep internally, not in exported Functions/Methods
* Libraries are different
- internal error handling vs external library API
Libraries should export known error types for conditions that are static
sql.ErrNoRows
io.EOF
bytes.ErrTooLarge
- Return underlying error if there's nothing the library can do to fix things
Network calls fail - you can't fix this, return the error
ex: log/syslog#Dial()
- Wrap error
NO.
return fmt.Errorf("reading from database:", err)
// can't test for known error without strings.Contains()
// Destroyed original context
* Handling Errors - General Advice
- Only handle errors once - NO
.code errmgmt/includes/errs/handleonce.go /START OMIT/,/END OMIT/
If the caller logs the error too, you've got possibly two log lines with confusingly similar messages.
* Handling Errors - General Advice
- Only handle errors once - BETTER
.code errmgmt/includes/errs/handleonce2.go /START OMIT/,/END OMIT/
Since you're not doing anything to resolve the error in your function, return it unchanged.
* Wrapping Errors For Clarity
In early 2016, Dave Cheney released *github.com/pkg/errors*
This package allows you to have the best of all worlds, by letting you wrap errors in contextual information while preserving the original error type.
// open config file
_, err := ioutil.ReadAll(r)
if err != nil {
return errors.Wrap(err, "read failed")
}
Callers that print the error will get the wrapped error with your prefix:
read failed: file not found
* Wrapping Errors For Clarity
Every function in the stack can wrap the error with their own context:
return errors.Wrap(err, "opening configuration file")
// opening configuration file: read failed: file not found
* Wrapping Errors For Clarity
You can retrieve the original cause of the error by using the errors.Cause() function:
originalError := errors.Cause(err)
* Wrapping Errors For Clarity - example
.code errmgmt/includes/errs/openfile.go /START OMIT/,/END OMIT/
* Wrapping Errors For Clarity
github.com/pkg/errors is under consideration for inclusion in the standard library. (which is rare!)
Use it.
* Exercise
Discuss pros and cons of error handling.
- Do you have examples of projects that do it well?
- Do you have examples of confusing projects?