1 00:00:06,580 --> 00:00:10,470 - Hi, now the next step in learning design is error handling 2 00:00:10,470 --> 00:00:13,001 We started very early in one of the very first sections, 3 00:00:13,001 --> 00:00:15,560 when I was trying to prepare you for this class, 4 00:00:15,560 --> 00:00:18,150 to tell you that lines of code are important 5 00:00:18,150 --> 00:00:21,401 and to tell you that if you really wanna maintain 6 00:00:21,401 --> 00:00:25,300 mental models and reduce the chances of legacy code, 7 00:00:25,300 --> 00:00:26,900 you've got to worry about failure. 8 00:00:26,900 --> 00:00:29,820 Failure is always what we're coding against 9 00:00:29,820 --> 00:00:31,260 and error handling is everything. 10 00:00:31,260 --> 00:00:32,880 It's not about exception handling, 11 00:00:32,880 --> 00:00:34,570 because exception handling I think 12 00:00:34,570 --> 00:00:36,880 hides context around errors. 13 00:00:36,880 --> 00:00:39,110 This is about looking at an error 14 00:00:39,110 --> 00:00:43,050 when it's happening, right, and Go errors are just values 15 00:00:43,050 --> 00:00:45,670 and they can be anything you need them to be. 16 00:00:45,670 --> 00:00:47,910 But from an API design perspective, 17 00:00:47,910 --> 00:00:49,830 for me error handling is about one thing, 18 00:00:49,830 --> 00:00:54,170 it's about showing respect to the user of your API, 19 00:00:54,170 --> 00:00:58,690 giving your user enough context to make an informed decision 20 00:00:58,690 --> 00:01:00,960 about the state of the application 21 00:01:00,960 --> 00:01:03,820 and giving them enough information to be able to either 22 00:01:03,820 --> 00:01:06,710 recover or make a decision to shut down. 23 00:01:06,710 --> 00:01:09,370 If your application loses integrity, 24 00:01:09,370 --> 00:01:11,880 you have a responsibility to shut it down. 25 00:01:11,880 --> 00:01:14,560 I don't wanna shut down apps, but you have a responsibility, 26 00:01:14,560 --> 00:01:16,250 because integrity, data corruption, 27 00:01:16,250 --> 00:01:17,680 no no no no no no no no no. 28 00:01:17,680 --> 00:01:20,180 And there's two ways to shut down an application in Go. 29 00:01:20,180 --> 00:01:23,390 You can go to OS, the OS package, dot exit, 30 00:01:23,390 --> 00:01:25,770 and you can set a return code on that, 31 00:01:25,770 --> 00:01:27,140 that's the fastest way, 32 00:01:27,140 --> 00:01:29,820 or you can call the built in function panic. 33 00:01:29,820 --> 00:01:31,700 Now you'll choose one over the other depending on if 34 00:01:31,700 --> 00:01:33,490 you need a stack trace or not. 35 00:01:33,490 --> 00:01:35,480 So if you need the stack trace you're gonna call panic, 36 00:01:35,480 --> 00:01:38,510 if you don't you just call OS exit. 37 00:01:38,510 --> 00:01:42,600 But we've gotta design APIs and errors around the idea 38 00:01:42,600 --> 00:01:44,410 of giving the user enough context 39 00:01:44,410 --> 00:01:46,240 to make an informed decision. 40 00:01:46,240 --> 00:01:49,610 So let's start with the basic mechanics 41 00:01:49,610 --> 00:01:51,720 and what the language provides you, 42 00:01:51,720 --> 00:01:53,780 in terms of error handling. 43 00:01:53,780 --> 00:01:58,520 Now what you're gonna see here is the error interface. 44 00:01:58,520 --> 00:02:01,465 Now this error interface is provided to us, 45 00:02:01,465 --> 00:02:03,770 built in to the language. 46 00:02:03,770 --> 00:02:06,170 There really is no package called built in here, 47 00:02:06,170 --> 00:02:08,040 this URL, that name built in 48 00:02:08,040 --> 00:02:10,230 is there purely for documentation. 49 00:02:10,230 --> 00:02:11,350 But look at the error interface, 50 00:02:11,350 --> 00:02:13,560 it looks like it's un-exported, but it really isn't 51 00:02:13,560 --> 00:02:15,650 because again it's built into the language, 52 00:02:15,650 --> 00:02:17,730 and it's got one active behavior, error, 53 00:02:17,730 --> 00:02:19,220 and it returns a string 54 00:02:19,220 --> 00:02:21,810 and this interface is how we work with errors. 55 00:02:21,810 --> 00:02:23,930 If you think about it, error handling in Go 56 00:02:23,930 --> 00:02:25,970 is done from a decoupled state 57 00:02:25,970 --> 00:02:27,820 and that's very very important 58 00:02:27,820 --> 00:02:30,380 because error handling is gonna be all over the place 59 00:02:30,380 --> 00:02:31,930 because it's gonna be wide spread, 60 00:02:31,930 --> 00:02:35,030 we want it decoupled so we can change error handling, 61 00:02:35,030 --> 00:02:37,620 improve and refactor error handling without creating 62 00:02:37,620 --> 00:02:41,390 a large set of cascading changes throughout a code base. 63 00:02:41,390 --> 00:02:43,120 So this interface is critical, 64 00:02:43,120 --> 00:02:46,150 but again error are just values in Go 65 00:02:46,150 --> 00:02:49,690 and the most widely used value for error handling 66 00:02:49,690 --> 00:02:53,680 is what you see on line 15 and that is the error string type 67 00:02:53,680 --> 00:02:55,860 and this comes from the errors package. 68 00:02:55,860 --> 00:02:58,550 Notice a couple things about the error string package, 69 00:02:58,550 --> 00:03:01,970 one it's a struct type that is un-exported 70 00:03:01,970 --> 00:03:04,740 and two it has one field in there 71 00:03:04,740 --> 00:03:08,200 which is an un-exported field named S of type string. 72 00:03:08,200 --> 00:03:11,440 This is the most commonly used error value in Go 73 00:03:11,440 --> 00:03:14,790 and in many many cases gives us enough context 74 00:03:14,790 --> 00:03:16,720 to give the caller enough information 75 00:03:16,720 --> 00:03:19,350 to make an informed decision. 76 00:03:19,350 --> 00:03:21,768 Now the next thing you're gonna see from the errors package, 77 00:03:21,768 --> 00:03:24,650 implementation of the error interface. 78 00:03:24,650 --> 00:03:26,610 I want you to notice a couple things here, 79 00:03:26,610 --> 00:03:28,710 one choosing pointer semantics 80 00:03:28,710 --> 00:03:30,680 and that's very very important, 81 00:03:30,680 --> 00:03:32,180 and we're gonna come back and explain 82 00:03:32,180 --> 00:03:34,620 why pointer semantics are very important here. 83 00:03:34,620 --> 00:03:38,400 Unless you're working with a errored value type 84 00:03:38,400 --> 00:03:40,860 that is gonna require value semantics, 85 00:03:40,860 --> 00:03:43,660 maybe like a reference type or a built in type, 86 00:03:43,660 --> 00:03:45,710 if we're using that struct I'd most definitely want 87 00:03:45,710 --> 00:03:47,780 pointer semantics for the implementation 88 00:03:47,780 --> 00:03:48,830 of the error interface. 89 00:03:48,830 --> 00:03:51,710 And notice that it's really just there for one purpose, 90 00:03:51,710 --> 00:03:54,040 one to make the value compliant with errors, 91 00:03:54,040 --> 00:03:55,750 but two for logging. 92 00:03:55,750 --> 00:03:58,300 Whatever comes out of this method should be 93 00:03:58,300 --> 00:03:59,940 for logging and logging only. 94 00:03:59,940 --> 00:04:02,770 If your user has to parse the string coming out, 95 00:04:02,770 --> 00:04:04,550 you have failed the user. 96 00:04:04,550 --> 00:04:07,750 We can not have them parsing to get context. 97 00:04:07,750 --> 00:04:10,490 Okay, now, now that we have the implementation 98 00:04:10,490 --> 00:04:13,540 of the interface, we now have the factory function, 99 00:04:13,540 --> 00:04:15,760 there it is, again coming from the errors package. 100 00:04:15,760 --> 00:04:17,420 And this is how we create errors. 101 00:04:17,420 --> 00:04:20,280 And remember or, more importantly look at 102 00:04:20,280 --> 00:04:24,170 the return of this factory, it is the error interface. 103 00:04:24,170 --> 00:04:27,160 And we look on line 27 and what we see is that we are 104 00:04:27,160 --> 00:04:31,250 constructing a value of the un-exported type error string, 105 00:04:31,250 --> 00:04:34,160 taking it's address, because we're using pointer semantics 106 00:04:34,160 --> 00:04:37,650 for the implementation, which means that we can only share 107 00:04:37,650 --> 00:04:39,320 and we stick that in the error. 108 00:04:39,320 --> 00:04:41,950 So any time you call new, I wanna show you 109 00:04:41,950 --> 00:04:45,780 on the white board for a second what that error variable 110 00:04:45,780 --> 00:04:48,330 is gonna look like when we call new, 111 00:04:48,330 --> 00:04:50,260 because what's interesting here is we've got 112 00:04:50,260 --> 00:04:52,110 a factory function that's not returning 113 00:04:52,110 --> 00:04:54,360 a concrete value directly, it's returning 114 00:04:54,360 --> 00:04:57,199 an interface value and remember I told you 115 00:04:57,199 --> 00:05:00,770 that factory functions really should be returning 116 00:05:00,770 --> 00:05:03,820 concrete values and pointers. 117 00:05:03,820 --> 00:05:05,990 Except this is that one kind of exception, 118 00:05:05,990 --> 00:05:07,710 remember there's exceptions to everything 119 00:05:07,710 --> 00:05:10,360 and that's new, because you always work with errors 120 00:05:10,360 --> 00:05:12,320 from an interface perspective. 121 00:05:12,320 --> 00:05:15,240 So when I call new, what we're gonna end up 122 00:05:15,240 --> 00:05:19,530 really getting back is the error interface value. 123 00:05:19,530 --> 00:05:22,130 It's gonna say I have a pointer to an error string 124 00:05:22,130 --> 00:05:24,810 and it's gonna have our pointer to the struct 125 00:05:24,810 --> 00:05:26,980 error string type, which is really just a string 126 00:05:26,980 --> 00:05:28,250 at the end of the day. 127 00:05:28,250 --> 00:05:30,090 Well it probably wouldn't say Bill, right? 128 00:05:30,090 --> 00:05:33,470 It would be some sort of error message, whatever that is. 129 00:05:33,470 --> 00:05:34,930 And this is what we're gonna get back, 130 00:05:34,930 --> 00:05:36,770 we're gonna get errors and work with errors 131 00:05:36,770 --> 00:05:39,080 from a decoupled state. 132 00:05:39,080 --> 00:05:42,980 This is now an error interface value with a concrete pointer 133 00:05:42,980 --> 00:05:46,183 inside of it to our error message or string. 134 00:05:47,240 --> 00:05:50,540 And it's very typical in Go to be looking 135 00:05:50,540 --> 00:05:53,360 at this kind of code here when it comes to error handling. 136 00:05:53,360 --> 00:05:55,410 Notice that we're in an if statement, 137 00:05:55,410 --> 00:05:57,370 inside the if statement we make a call 138 00:05:57,370 --> 00:05:59,200 to say in this case webCall, 139 00:05:59,200 --> 00:06:02,010 we check to see if webCall has failed, 140 00:06:02,010 --> 00:06:04,120 we've got the error not equals nil. 141 00:06:04,120 --> 00:06:06,370 Now there's a few things here that I wanna talk about 142 00:06:06,370 --> 00:06:07,570 before we move on. 143 00:06:07,570 --> 00:06:11,930 One is that people liked the idea of exception handling 144 00:06:11,930 --> 00:06:16,260 because the idea was the try code had your happy path 145 00:06:16,260 --> 00:06:19,780 and your catch took care of all the negative path 146 00:06:19,780 --> 00:06:22,460 and you separated this for code readability. 147 00:06:22,460 --> 00:06:24,580 I get it, but I can't tell you how many times 148 00:06:24,580 --> 00:06:27,050 when I landed in the catch, I was lost, 149 00:06:27,050 --> 00:06:30,190 because the context of how I got there is lost. 150 00:06:30,190 --> 00:06:31,837 Was it this call, was it that call? 151 00:06:31,837 --> 00:06:34,720 And I used to spend too much time trying to figure out 152 00:06:34,720 --> 00:06:37,870 how I got inside of a catch, it doesn't work. 153 00:06:37,870 --> 00:06:41,620 But you can still have this concept of happy path 154 00:06:41,620 --> 00:06:44,800 if you follow this basic design principle 155 00:06:44,800 --> 00:06:46,270 when writing code in Go. 156 00:06:46,270 --> 00:06:48,470 And when it is reasonable and practical, 157 00:06:48,470 --> 00:06:52,170 what I want you to do, is use the if statement 158 00:06:52,170 --> 00:06:55,870 to handle your negative path logic and keep your path, 159 00:06:55,870 --> 00:06:59,920 your positive path logic on a straight line of sight. 160 00:06:59,920 --> 00:07:03,221 In other words, if this call to webCall succeeds, 161 00:07:03,221 --> 00:07:08,070 then I just move down the function to the next call. 162 00:07:08,070 --> 00:07:11,130 So this line of sight right here, this tab, 163 00:07:11,130 --> 00:07:14,260 is my happy path, I'm gonna get the same benefits 164 00:07:14,260 --> 00:07:16,100 we were getting from exception handling. 165 00:07:16,100 --> 00:07:19,490 If this call was succeeded, no problem, 166 00:07:19,490 --> 00:07:24,490 we keep our line of sight going and we continue to go down 167 00:07:24,650 --> 00:07:28,120 this line of sight for the happy path 168 00:07:28,120 --> 00:07:30,320 and we use the if statement to deal with 169 00:07:30,320 --> 00:07:32,070 our negative path logic. 170 00:07:32,070 --> 00:07:35,740 My line of sight down this line right here 171 00:07:35,740 --> 00:07:38,770 is our negative path and the idea is 172 00:07:38,770 --> 00:07:41,130 that we return out of the negative path. 173 00:07:41,130 --> 00:07:44,350 So we do something if there's failure then 174 00:07:44,350 --> 00:07:46,460 we're gonna handle that failure, whatever that means, 175 00:07:46,460 --> 00:07:48,710 we'll talk about that and then you will return. 176 00:07:48,710 --> 00:07:50,910 You can have, even though you have a single entry point 177 00:07:50,910 --> 00:07:52,550 for a function, get into your head 178 00:07:52,550 --> 00:07:54,790 that we can have multiple exit points. 179 00:07:54,790 --> 00:07:57,210 And the one thing I really want you to avoid using 180 00:07:57,210 --> 00:08:00,390 is an else clause, else clauses make code 181 00:08:00,390 --> 00:08:04,700 an order of magnitude more complicated to read, 182 00:08:04,700 --> 00:08:07,160 because what else clauses typically try to do 183 00:08:07,160 --> 00:08:10,214 is you're putting positive path logic inside your if, 184 00:08:10,214 --> 00:08:11,970 you're trying to push a function 185 00:08:11,970 --> 00:08:13,900 from the beginning all the way down. 186 00:08:13,900 --> 00:08:16,037 It just means I have to consume, read, 187 00:08:16,037 --> 00:08:19,690 and understand more code as I go through the function. 188 00:08:19,690 --> 00:08:21,980 When you use this pattern I don't have to worry 189 00:08:21,980 --> 00:08:23,920 about reading as much code because I know that 190 00:08:23,920 --> 00:08:26,880 if I've gotten here and I've moved down to line 36, 191 00:08:26,880 --> 00:08:28,530 I know everything above is already done 192 00:08:28,530 --> 00:08:30,140 and fine and we're good. 193 00:08:30,140 --> 00:08:32,330 So I really want you to follow this pattern. 194 00:08:32,330 --> 00:08:35,240 I also want you to stay away from else clauses, 195 00:08:35,240 --> 00:08:37,920 because we're not looking to have a single exit point. 196 00:08:37,920 --> 00:08:42,020 What's really nice about Go too is they have this concept 197 00:08:42,020 --> 00:08:45,260 of a naked switch and on the naked switch, 198 00:08:45,260 --> 00:08:48,170 in your case you could do Boolean logic, 199 00:08:48,170 --> 00:08:50,520 you can write Boolean logic here 200 00:08:50,520 --> 00:08:52,830 and if you do have a situation where you may have 201 00:08:52,830 --> 00:08:57,580 an if else, if else, then use the switch to do that 202 00:08:57,580 --> 00:09:00,240 and again leverage the idea of negative path 203 00:09:00,240 --> 00:09:03,440 on the return, negative path always indented. 204 00:09:03,440 --> 00:09:06,350 Try these lines of, lines of you know scope here, 205 00:09:06,350 --> 00:09:08,530 these lines of path, you'll see that it's gonna 206 00:09:08,530 --> 00:09:11,890 really help you and as we continue to work with code here 207 00:09:11,890 --> 00:09:14,640 I'll show you how this code base is doing it. 208 00:09:14,640 --> 00:09:18,509 Okay great, so, we've got the call to webCall. 209 00:09:18,509 --> 00:09:20,580 WebCall down here is returning an error, 210 00:09:20,580 --> 00:09:23,550 it's returning an error interface value 211 00:09:23,550 --> 00:09:27,980 where the string inside of our error value here 212 00:09:27,980 --> 00:09:31,250 says bad request, right that's the string, 213 00:09:31,250 --> 00:09:33,750 bad request, so when we call webCall 214 00:09:33,750 --> 00:09:36,130 we take that error interface value, 215 00:09:36,130 --> 00:09:38,650 we store it in a local variable to the if called error, 216 00:09:38,650 --> 00:09:40,850 we're using our short variable declaration operatives, 217 00:09:40,850 --> 00:09:43,210 it's a local variable only bound to the if, 218 00:09:43,210 --> 00:09:45,330 this is really great and then we check 219 00:09:45,330 --> 00:09:46,410 error not equal to nil. 220 00:09:46,410 --> 00:09:49,150 Now this is interesting, 'cause nil 221 00:09:49,150 --> 00:09:51,393 is really an interesting concept in Go. 222 00:09:52,500 --> 00:09:57,220 Nil is always the zero value for the two types of 223 00:09:57,220 --> 00:09:59,340 types that can be nil, one is pointers 224 00:09:59,340 --> 00:10:01,600 and one are the reference types. 225 00:10:01,600 --> 00:10:03,410 Alright, so nil always takes on 226 00:10:03,410 --> 00:10:06,880 the type it needs to satisfy an expression. 227 00:10:06,880 --> 00:10:08,880 In this case what is nil? 228 00:10:08,880 --> 00:10:12,470 Well since err is an error interface value, 229 00:10:12,470 --> 00:10:17,126 then what nil represents is a nil error interface value. 230 00:10:17,126 --> 00:10:20,880 You see, it's always gonna take on the type it needs, 231 00:10:20,880 --> 00:10:22,380 so we can do the compare. 232 00:10:22,380 --> 00:10:26,210 But I like English and so for me, error not equals nil, 233 00:10:26,210 --> 00:10:28,810 what it really means, it's asking one question, 234 00:10:28,810 --> 00:10:31,830 it's asking, is there a concrete value stored 235 00:10:31,830 --> 00:10:34,090 inside that error interface. 236 00:10:34,090 --> 00:10:37,090 Here's a situation where there's no concrete value stored, 237 00:10:37,090 --> 00:10:39,100 here's a situation where there is 238 00:10:39,100 --> 00:10:40,950 and in our case we're going to have it. 239 00:10:40,950 --> 00:10:42,790 So anytime I see error not equals nil, 240 00:10:42,790 --> 00:10:45,633 what we're really saying is, is there a value, 241 00:10:45,633 --> 00:10:48,120 whether a piece of data, a value or pointer, 242 00:10:48,120 --> 00:10:50,860 is there something stored concrete inside 243 00:10:50,860 --> 00:10:52,450 the error interface and if there is, 244 00:10:52,450 --> 00:10:54,030 that means we have an error. 245 00:10:54,030 --> 00:10:55,960 Now in this case the fact that we have an error 246 00:10:55,960 --> 00:10:58,040 is all the context we need, 247 00:10:58,040 --> 00:11:00,770 because this function only returns one error 248 00:11:00,770 --> 00:11:03,110 and we know what that error is and we have enough context 249 00:11:03,110 --> 00:11:04,840 so we can make an informed decision 250 00:11:04,840 --> 00:11:06,550 about how to handle the error. 251 00:11:06,550 --> 00:11:09,190 All I'm doing right now is logging it and returning it. 252 00:11:09,190 --> 00:11:11,530 Alright, so when you got a function that returns 253 00:11:11,530 --> 00:11:13,880 just one error, a lot of times it's just the fact 254 00:11:13,880 --> 00:11:16,560 that an error exists, that there's a concrete value 255 00:11:16,560 --> 00:11:19,670 stored inside the error interface, which is enough. 256 00:11:19,670 --> 00:11:24,460 But what if webCall could return multiple errors? 257 00:11:24,460 --> 00:11:28,360 At that point now we have to deal with a different mechanism 258 00:11:28,360 --> 00:11:31,420 to give the user enough context 259 00:11:31,420 --> 00:11:32,913 to make an informed decision.