Is "err != nil" the best idiom for error handling?

blov · · 544 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>I&#39;ve been using Go for a few months now (coming from Python), and I really do enjoy it. The <em>one</em> bugbear I have is the standard error idiom: </p> <pre><code>thing, err := MakeThing() if err != nil { // handle error } </code></pre> <p>I&#39;ll admit that most of my &#34;handling&#34; is just panicking on non-nil errors, but this pattern shows up so much, both in my own code and others. In fact, I don&#39;t think I&#39;ve ever <em>not</em> seen it in a library.</p> <p>Is there a better/cleaner way to handle errors or am I a petulant ex-Pythonista who&#39;s missing something crucial? Thanks for any and all advice.</p> <hr/>**评论:**<br/><br/>pwforgetter: <pre><p>As a reader of code, I find it a delight that I don&#39;t have to worry about what exceptions might get thrown in function-calls, the flow you see is the flow that actually happens.</p></pre>BadMoonRosin: <pre><p>True, but I do wish that Go&#39;s <code>error</code> type contained a bit more metadata or logging capability. </p> <p>When working with Go instead of Java or C#, one thing that I miss dearly is having detailed stacktrace information available. If I call a Java method, and it throws an <code>Exception</code> from somewhere within... the caller can inspect it, apply different handling based on its type, write it to the log file and automatically have a detailed stacktrace with code line numbers, etc.</p> <p>None of that power exists in Go. There are no sub-categories of errors, there&#39;s only the single <code>error</code> type. And <code>error</code> has no idea what line it occurred on, or reflection-inserted stacktrace knowledge of any kind. It&#39;s just a dumb wrapper for a <code>string</code>.</p> <p>Yeah, you CAN work around this by conventions. Always <code>log.Printf(err)</code> an error with detailed context info at the spot of occurrance, before returning it to the caller, etc. However, that&#39;s REALLY clunky, leads to a lot of double-logging, and even in the best-case scenario is still just lame compared to most over modern languages. Inexplicably, Go&#39;s <code>log</code> package doesn&#39;t support levels (e.g. debug, info, error) like every other logging framework out there today. It&#39;s nothing but <code>fmt</code> with a timestamp prefix.</p> <p>I like the fact that Go encourages you to deal with errors when the occur, rather than &#34;swallowing exceptions&#34; or &#34;always throwing exceptions&#34; like other languages. However, let&#39;s not delude ourselves... Go&#39;s overall error-handling model is extremely primitive, and not in a good way.</p></pre>Sphagnum_affine: <pre><p>You can use annotated errors. </p> <ul> <li><a href="https://github.com/spacemonkeygo/errors">spacemonkeygo/errors</a></li> <li><a href="https://github.com/juju/errors">juju/errors</a></li> </ul> <p>They allow attach position and/or message.</p></pre>mcvoid1: <pre><p>That whole &#34;no sub-categories of errors&#34; thing simply isn&#39;t true. Since errors are interfaces, you can have errors that include that information. Now it may not be built-in, but there&#39;s libraries out there which do just that.</p></pre>weberc2: <pre><p>More importantly, you can actually type assert or reflect to get that information.</p></pre>bkeroack: <pre><p>Every time you recommend somebody use reflect, a baby kitten dies.</p></pre>dilap: <pre><p>Definitely for errors you should just be doing type assertions. I don&#39;t think many kittens have died over the use of reflect in e.g. the standard json module, tho. :)</p></pre>weberc2: <pre><p>I don&#39;t know of another way to reliably get type information from an <code>error</code>. Do you?</p></pre>bkeroack: <pre><p>A type switch (to be safe against panics) or, if you&#39;re sure what concrete error type is returned, a bare type assertion.</p></pre>weberc2: <pre><p>A type assertion won&#39;t panic if you check the returned Boolean. </p></pre>hahainternet: <pre><blockquote> <p>the caller can inspect it, apply different handling based on its type</p> </blockquote> <p>You should feel free to do this in Go too. Type switching can help easily distinguish errors imho.</p></pre>weberc2: <pre><p>This does break down when you have jackasses like me who use fmt.Errorf() for everything because creating my own error class is too verbose.</p></pre>hahainternet: <pre><p>I&#39;m also guilty of that I must say.</p></pre>robertmeta: <pre><p>Now that you have identified the problem, step two is fixing it. :) </p></pre>weberc2: <pre><p>I don&#39;t know of any less-verbose solutions, but I&#39;m open to suggestions.</p></pre>idevxy: <pre><p>There aren&#39;t any less-verbose solutions, but come on, it&#39;s 4 lines of code, and that&#39;s counting the closing brace.</p> <pre><code>type MyError struct{} func (MyError e) Error() string { return &#34;This is my error.&#34; } </code></pre> <p>Then you can just <code>return MyError{}</code></p> <p>Is it a big deal if you aren&#39;t writing a library? No. But it&#39;s very little effort for a nice boost in code cleanliness. That&#39;s what Go is all about: doing more with less.</p> <p>Edit: mistakes</p></pre>Akkifokkusu: <pre><p>Your receiver syntax is wrong. Should be <code>e *MyError</code>. But why use pointers for an error type anyway? <code>e MyError</code> and <code>return MyError{}</code> are much more idiomatic, I think. The function should still be returning <code>error</code>, and the consumer can decide whether or not to use type assertion to dig into it.</p></pre>idevxy: <pre><p>Thanks for the correction. I haven&#39;t had the luxury of writing Go much lately. Stuck in Java Land. But why should it return <code>error</code> over <code>MyError</code> in this case, assuming it never returns a nested error that may be of different type? That seems like deferring information to runtime for no reason.</p></pre>dilap: <pre><p>It&#39;s a weird corner of the Go language.</p> <p>So there are three possibilities</p> <pre><code>func Foo() MyError func Foo() *MyError func Foo() error </code></pre> <p>Obviously the first doesn&#39;t work, since you can&#39;t return nil (i.e., there&#39;s no way to say &#34;no error here&#34;).</p> <p>You might think (2) would be good, but that&#39;s where you run into trouble -- assigning a nil MyError pointer to an error, does <em>not</em> give you a nil error!</p> <pre><code>package main import &#34;fmt&#34; type GhostError struct{} func (err *GhostError) Error() string { return &#34;ghost error!&#34; } func Foo() *GhostError { // a-ok... return nil } func main() { var err error err = Foo() if err != nil { fmt.Printf(&#34;%s %#v\n&#34;, err.Error(), err) } else { fmt.Println(&#34;it&#39;s alright&#34;) } } </code></pre> <p>The above code prints &#34;ghost error! (*main.GhostError)(nil)&#34;.</p> <p>What&#39;s going on is that an interface value is two pointers: (value, class). For the interface to be nil, both the value and the class must be nil. But when you assign from a concrete pointer type to an interface value, the type is not nil.</p> <p>To put it another way, a nil pointer can still be a perfectly good interface satisfier (as demonstrated by GhostError above).</p> <p>This is really confusing the first time you encounter it, but is consistent and makes sense once you understand what&#39;s going on and how Go works.</p> <p>It&#39;s definitely a sharp edge of the language though. (I wonder if Go linter&#39;s warn for this?)</p></pre>weberc2: <pre><p>Usually I&#39;m embedding some context into the struct, so it&#39;s more like:</p> <pre><code>type MyError struct { Context string } func (MyError e) Error() string { format := &#34;An error happened. Here&#39;s the context: %s&#34; return fmt.Errorf(format, context) } </code></pre> <p>I didn&#39;t say I disagree with your premise, and I often do it. But I really don&#39;t like breaking my train of thought to crank out that boilerplate, and it&#39;s easier to just <code>fmt.Errorf()</code> it and &#34;come back to it later&#34; (which rarely happens, unless I happen to be hitting that error and need more debugging context). Ideally something like this would be added to <code>go vet</code> or some other linter (maybe it already is?).</p> <blockquote> <p>That&#39;s what Go is all about: doing more with less.</p> </blockquote> <p>I don&#39;t much care about &#34;what Go is all about&#34;; I care about getting shit done quickly. Usually &#34;doing more with less&#34; serves that goal, but here it doesn&#39;t. Oh well.</p></pre>: <pre><p>[deleted]</p></pre>weberc2: <pre><p>How is that better than <code>fmt.Errorf()</code>?</p></pre>bkeroack: <pre><p>Errors can contain whatever data you want. It&#39;s just a single-method interface after all.</p> <p>You (as the caller) <em>do</em> need to know what more-specific-error-type the method returns, but that&#39;s hardly unreasonable if you plan on inspecting the added data.</p></pre>idevxy: <pre><p>Further, there&#39;s no reason the func needs to return just <code>error</code>. You can have a signature like <code>func Foo() FooError</code> and effortlessly assign the return value to an <code>error</code> variable as long as it satisfies the interface.</p> <p>That requires no more effort than knowing what the return value of the func is in the first place. This is Go&#39;s whole approach to error handling: Errors are just data, and error handling is just code. It&#39;s not special.</p></pre>dchapes: <pre><p>Don&#39;t do this. There are reasons (I wish I had the links handy) of why this is a bad idea. It&#39;s for those unlinked reasons that in <code>os</code> there are many functions documented as returning either <code>nil</code> or a <code>*os.PathError</code> but the signature is purposefully <code>error</code> and <strong>not</strong> <code>*os.PathError</code>.</p> <p>Edit: from the <a href="https://golang.org/doc/faq#nil_error" rel="nofollow">FAQ</a>:</p> <blockquote> <p>It&#39;s a good idea for functions that return errors always to use the <code>error</code> type in their signature (as we did above) rather than a concrete type such as <code>*MyError</code>, to help guarantee the error is created correctly. As an example, <a href="https://golang.org/pkg/os/#Open" rel="nofollow"><code>os.Open</code></a> returns an <code>error</code> even though, if not <code>nil</code>, it&#39;s always of concrete type <a href="https://golang.org/pkg/os/#PathError" rel="nofollow"><code>*os.PathError</code></a>. </p> </blockquote></pre>earthboundkid: <pre><p>Yeah, if I were designing Go 2.0, I&#39;d change <code>error</code> to be an interface of <code>Error() string</code> <em>and</em> <code>Source() error</code> so that you can chain errors (the last error in the chain would just return <code>nil</code>).</p></pre>pure_x01: <pre><p>The problem is DRY though </p></pre>blogetry: <pre><p>This is true. Python&#39;s exceptions are a little too ephemeral, and Go&#39;s method of making errors actually part of the code makes things clearer.</p></pre>weberc2: <pre><p>Although in Go you still don&#39;t know if you&#39;re handling all errors correctly, since you don&#39;t know the set of all things that could implement the error interface. Sometimes I wonder if the idiom should have been <code>func() (OSError, IOError, ParseError)</code> instead of <code>func() error</code>.</p></pre>010a: <pre><p>Yes, instead we just get generic <code>error</code> strings with no identification as to what kind of errors a function might throw.</p> <p><code> public User getUser(String id) throws UserNotFoundException, ConnectionExcetion { } </code></p> <p>Versus</p> <p><code> func GetUser(id string) (User, error) { } </code></p> <p>As a reader of code, I find it a delight that I don&#39;t have to worry about what exceptions might get thrown in function calls. Except when the function&#39;s signature literally defines what exceptions will be thrown and the IDE takes care of the rest.</p> <p>As a writer of code, I find it a delight that I have to read the actual source code of a Go function to determine the types of errors it is capable of returning so I know how to handle them. No, wait, that&#39;s not a delight; that&#39;s abhorrent. </p> <p>I&#39;m actually amazed at your opinion: its completely wrong in the case of any exception throwing language which requires (or at least allows) throwable exception types to be declared in the method signature, yet simultaneously ironically points out a glaring deficiency in Go. </p></pre>pwforgetter: <pre><p>I have more experience with python than with e.g. java, and the function doesn&#39;t list the possible exceptions. </p> <p>If you need an IDE to tell you that:</p> <pre><code>u = GetUser() use(u) </code></pre> <p>might actually raise UserNotFoundException or ConnectionException and enver execute use(u), you have only won that you rely on a good ide instead of code that is readable on its own?</p> <p>If I have to hover over every function call to see its signature, that&#39;s a lot slower than my eyes skipping over the error-cases but mentally understanding that it might abort there. (And I think it might force better error-messages, but that still requires discipline)</p></pre>010a: <pre><p>That&#39;s true. Python is the one exception (pun intended).</p> <p>What&#39;s wrong with relying on tools to help you navigate code? Java is famous for it, but we do it with Go as well. What do you think Godoc is? The only difference is the magnitude to which the tools help you. </p> <p>My opinion is that if I have to browse a library&#39;s source code to settle a deficiency in their API documentation or godoc, something is not right. There are ways of writing Go code so this is unnecessary, but its not idiomatic and very few libraries do this. </p> <p>For example, I like writing functions like this:</p> <pre><code>func GetUser(id string) (User, NotFoundError, CnctError) {} </code></pre> <p>That way it is explicit exactly which errors a function returns and errors <em>in</em> my error handling code can be caught at compile time. But quite often libraries design their APIs like this...</p> <pre><code>// GetUser returns the user object and a UserNotFound // error if the id doesn&#39;t exist or a CnctError if // there is a problem with the database connection. func GetUser(id string) (User, error) {} </code></pre> <p>Ok, we&#39;ve got more information, but its in a doc string. If I&#39;m using tooling like an IDE, it can&#39;t read that. And I&#39;ll probably need a type switch on the caller side to actually do something with that error, so there goes compile time safety. But at least I don&#39;t have to go digging through their source code. Until we get this:</p> <pre><code>func GetUser(id string) (User, error) {} </code></pre> <p>Which is sadly the majority of libraries in Go. No information. Time to dig into this function on Github to figure out what it can return.</p></pre>dilap: <pre><p>A downside of listing all the possible error cases in your GetUser signature is if you decide to add another error case, you have to update all your function caller signatures. (Or, to put it another way, you break API compatability.)</p> <p>Personally, I think something like this works well:</p> <pre><code>// GetUser gets a user by id. If no user with that id exists, returns NotFoundError. func GetUser(id UserId) error </code></pre> <p>And then you&#39;d use it like this:</p> <pre><code>user, err := GetUser(userId) if err != nil { if _, notFound := err.(NotFoundError); notFound { // do some special stuff here } else { // generic case } } </code></pre> <p>Seems reasonable to me, but it&#39;s true that you&#39;re relying on the comments to tell you what&#39;s going on, rather than the type system.</p></pre>010a: <pre><p><a href="https://www.youtube.com/watch?v=ilcRS5eUpwk" rel="nofollow">Adding another error case <em>should</em> break API compatibility.</a> By adding another error case you are saying &#34;something about the purpose of this function has changed so fundamentally that its capable of failing in a whole new way.&#34; By changing the function signature you can guarantee that every caller is now capable of handling the new failure case.</p> <p>This is powerful and a huge upside to this method, not a downside. </p></pre>dilap: <pre><p>Doesn&#39;t make sense to me. The kinds of ways a function can fail is implementation detail; as a caller I don&#39;t really care why it failed, just that it did, most of the time.</p></pre>dericofilho: <pre><p><a href="https://blog.golang.org/errors-are-values">https://blog.golang.org/errors-are-values</a></p></pre>dasacc22: <pre><p>As of go1.6beta2</p> <p>8029 lines of <code>err != nil</code></p> <p>1792 of those lines are of <code>if err := [x]; err != nil {</code></p> <p>5745 of those lines are of <code>if err != nil {</code></p> <p>The remainder are either of form <code>if x.err != nil ...</code> or <code>if [string] != &#34;[foo]&#34; || err != nil {</code> or one of the above with a different name for err.</p> <p>Basically you either start thinking about the implication of errors or you suffer the turmoil of typing out something you refuse to think about over and over. I don&#39;t recommend the latter.</p></pre>demizer: <pre><blockquote> <p>Basically you either start thinking about the implication of errors or you suffer the turmoil of typing out something you refuse to think about over and over. I don&#39;t recommend the latter.</p> </blockquote> <p>When prototyping code, I used to do</p> <pre><code>val, _ := func() </code></pre> <p>Eventually an error would occur and I would have to do fmt.Println() to find the problem only to discover I was ignoring the error and forgot.</p> <p>Kinda of annoying that I have to think about all the errors that could ever happen, but it definitely does make for more well defined code.</p></pre>unitedcreatures: <pre><p>Use gometalinter to be sure, it emits warnings on ignored errors. </p></pre>mcvoid1: <pre><p>In some ways, Go&#39;s idiom is a bit more sophisticated than other languages in that it differentiates between everyday, run-of-the-mill errors and more exceptional ones, and only has the more exceptional ones break your control flow and have the potential to leave things a mess. Not being able to open a file is so common there&#39;s no reason to ditch your whole stack frame like when exceptions are thrown - just handle it up front and leave no questions as to whether or not something else still needs to be cleaned up.</p> <p>If you&#39;re eating at a table and drop something on the table, you just clean it up right then and there - you don&#39;t need to flip the whole table over and then get a mop to clean up the floor.</p></pre>mixedCase_: <pre><p>That&#39;s correct form. Go was designed to create robust software, this way you become more aware of failure points and forces you to handle them or pass them along.</p> <p>Exceptions (unless they&#39;re checked ones) make you automatically pass them along unless explicitly handled. Which makes it more likely to miss errors you&#39;d like to handle in a specific way. Hell, most of the time we don&#39;t even know if what we&#39;re calling is even able to blow up much less how they could!</p> <p>Also, you can also save a line by doing:</p> <pre><code>if thing, err := MakeThing(); err != nil { // handle error } </code></pre> <p>I try to avoid it unless the situation is very simple since it makes for slightly harder to read code and that&#39;s what Go fiercely tries to avoid.</p> <p>Also, PLEASE do not use panic for recoverable errors. Just return an error.</p></pre>random_testaccount: <pre><blockquote> <p>Go ... forces you to handle them or pass them along. </p> </blockquote> <p>You&#39;re not forced to handle errors: </p> <pre><code>func foo(i int) error { if i == 0 { return errors.New(&#34;I like errors&#34;) } fmt.Printf(&#34;Hello %d\n&#34;, i) return nil } func main() { foo(1) foo(0) // Oh no, I forgot foo can return an error! foo(2) } </code></pre> <p>When you need to perform foo3, for which you need data from foo2, for which you need data from foo1, every step of which may return a value or an error, and in the case of an error with each step, you need to do bar and then exit, don&#39;t you sometimes wish you had a try .. catch .. finally type construct? </p> <p>Yes, I know the go way is perfectly workable, but is it always the most elegant? Apple decided to move to a try .. catch syntax with swift 2.0 after starting with a go-like approach. </p></pre>carbocation: <pre><p>I do wish that every return value had to be handled, even with just <code>_</code> to make it explicit that it was being ignored.</p></pre>mixedCase_: <pre><p>You know, that&#39;s a really good argument for the compiler to throw a warning or an error even, when you ignore return values via just calling the function.</p> <p>My personal experience is that I never call a function without assigning the value to something (even if it&#39;s underscore), and if I&#39;m going to do so it&#39;s because I&#39;ve weighted the options and am going to look both ways before crossing the street. It&#39;s like a personal bad practice to do otherwise, but I assume plenty of people don&#39;t do the same.</p></pre>wehavetobesmarter: <pre><p>Would be better in this case to return a (bool,error) since you are interested in the side effect. In some cases, a wrapper that panics on error could be appropriate.</p></pre>arnaldomf: <pre><p>When you do this kind of error checking:</p> <pre><code>if thing, err := Makething(); err != nil { ... } </code></pre> <p><strong>thing</strong> is created in a new scope, so it is not usable outside of the if/else, even if it was declared before.</p></pre>ihsw: <pre><p>You sure about that? I do <code>var thing thingStruct</code> prior to those if statements all the time purely because I noticed they weren&#39;t carrying over to outside the if block&#39;s scope.</p></pre>carbocation: <pre><p>If you want <code>thing</code> available outside of the <code>if/else</code> scope, then you need to declare both <code>thing</code> and <code>err</code> beforehand and use <code>=</code> rather than <code>:=</code>.</p></pre>arnaldomf: <pre><p>Sorry, i meant that if you use the symbol <strong>:=</strong> as in</p> <p>if thing, err := Makething(); err != nil</p> <p>you will be declaring a new variable called thing in the if/else socpe, so the value received from Makething will not be persisted in the <strong>thing</strong> declared before.</p></pre>ihsw: <pre><p>Ah, very true.</p></pre>blogetry: <pre><p>Yes, this is what I&#39;m learning so far. </p> <p>The programs I&#39;m writing are small, run-once affairs (my current one is a blog generator), so they die <em>immediately</em> upon seeing a non-nil error. I&#39;ve not yet had a situation where I&#39;ve actually had to &#34;handle&#34; an error.</p> <p>I thought I knew about the <code>if ...; err != nil</code> trick, but I must have forgotten. And yes, I&#39;m using log functions to print and die now, instead of panicking. Thanks!</p></pre>cathalgarvey: <pre><p>As a fellow Pythonista it&#39;s common at first to think &#34;I&#39;ll just panic and recover!&#34; but the Go order of events is to unwind first and look for recovers later, so it&#39;s actually nowhere near Python&#39;s raise/except system.</p> <p>I pretty much only use panic to kill an application with a stack trace. The defer/recover thing is just not how I reason about code if I can help it.</p></pre>DualRearWheels: <pre><p>I use defer/recover only at top level of each goroutine to collect and report errors before terminating (or restarting routine). Makes program more robust.</p> <p>Compared to Python, D and other exception languages, I find Go&#39;s error approach more simpler and reliable. More than once I forgot try/catch block that caused me a lot of grief. In Go that is impossible, even with error default returning value (zero usually) can cover most cases.</p></pre>blogetry: <pre><p>Is defer useful? It seems strange to defer a statement when I could just put it at the end of a function.</p></pre>DualRearWheels: <pre><p>It is, when you have multiple exit points (returns or even panic) or complex work flow it would be much harder and error prone other way. And it is much more readable to group defer code at function start or grouped with relevant code (open file, deferr close file etc.).</p></pre>blogetry: <pre><p>Hmm, I guess it does make sense to bundle all your open/close statements into one chunk at the beginning of a function, before entering an actual workflow.</p></pre>tmornini: <pre><p>I prefer Ruby&#39;s ensure.</p> <p>I&#39;d be shocked if Go 2 doesn&#39;t implement some form of try/catch/throw.</p></pre>freebullets: <pre><p>AFAIK, deferred statements are always called before exiting the function. So if I put an <code>if x { return }</code> in the middle of the function, my deferred statement will be called, whereas if it was at the end of the block it wouldn&#39;t. I believe it also executes before panics.</p></pre>blogetry: <pre><p>Executing before panics makes sense actually, because then you could use them as a mandatory cleanup action (<code>.Close()</code>, etc) that happens regardless of any panics. Thanks.</p></pre>kurin: <pre><p>Deferred statements aren&#39;t called if another goroutine panics, or if any goroutine calls os.Exit.</p></pre>blogetry: <pre><p>You&#39;re right, the temptation to just panic and let the error bubble up to a more recoverable place is very strong. (As is the temptation to write my variables in <code>snake_case</code>, but alas.)</p></pre>mellowdrifter: <pre><p>Personally I hate negatives. I would much prefer &#39;if err&#39; - handle error. </p></pre>daniu: <pre><blockquote> <p>Personally I hate negatives</p> </blockquote> <p>What&#39;s wrong with</p> <pre><code>if !invalid != false { ... } </code></pre> <p>? ;)</p> <p>Seriously though, I prefer to have my code handle the most common, &#34;normal&#34;, cases first, so the non-error one. Increases readability imo.</p></pre>blogetry: <pre><p>Yeah, <code>if err != nil</code> is quite readable and it states exactly what you&#39;re doing. Besides, it&#39;s literally everywhere in existing Go code. </p> <p>Not saying <code>if err != nil</code> in Go is like not calling the self argument<code>self</code> in Python.</p></pre>rco8786: <pre><p>Both the best and the worst. The language maintainers sticking to their guns as if err != nil being somehow elegant is hogwash.</p> <p>The standard argument is &#34;I don&#39;t have to worry about what exceptions might get thrown&#34; or some such.</p> <p>Go look at any major Go lib(Docker, InfluxDB, Go itself, whatever) you&#39;ll see hundreds or thousands of:</p> <pre><code>if err != nil return err </code></pre> <p>All you&#39;re doing it passing the error back up the stack, just like an exception. And at some point you write an error handler that logs it and shows a message to the user, just like an exception. The only difference is that you have to do it manually, and without error types. Joy.</p></pre>oefig: <pre><blockquote> <p>All you&#39;re doing it passing the error back up the stack, just like an exception. And at some point you write an error handler that logs it and shows a message to the user, just like an exception.</p> </blockquote> <p>The idea of handling errors in-line is so you can try to recover from them when they happen. With exceptions it&#39;s too easy to just handle all exceptions the same way or ignore them completely.</p></pre>rco8786: <pre><p>I know what the <em>idea</em> is. But the <em>reality</em> is that it just doesn&#39;t happen. Like I said, check out error handling in basically any Go library. They&#39;re all smattered with returning the error to the caller up to some main error handling site. </p> <p>How many errors can you really recover from in line? Not many. The vast majority of the time theyre logged and an error message is shown to the user. </p></pre>andreynering: <pre><p>I don&#39;t know if it is the best alternative, but you could write functions to abstract handling the errors:</p> <pre><code>package main func checkErr(err error) { if err != nil { // do something: // panic it or write in the log, etc. } } func main() { foo, err := doSomething() checkErr(err) } </code></pre></pre>favadi: <pre><p>Compare:</p> <pre><code>foo, err := doSomething() checkErr(err) </code></pre> <p>with</p> <pre><code>foo, err := doSomething() if err != nil { } </code></pre> <p>I&#39;ll say the latter one is more readable.</p></pre>d_rudy: <pre><p>The pattern I use in my web app is something like this:</p> <pre><code> func handleError(err error, w http.ResponseWriter) { switch err.(type) { case ErrNotFound: http.Error(w, err.Error(), http.StatusNotFound) default: http.Error(w, err.Error(), http.StatusInternalServerError) } } http.HandleFunc(&#34;/posts&#34;, func(w http.ResponseWriter, r *http.Request) { posts, err := getPosts() if err != nil { handleError(err, w) return } // do something with the posts } </code></pre> <p>The advantage being that I have a few error types that will all respond with the same http error code, and then if I don&#39;t have a special case, then it should probably return a 500. I could do it inline in the http handler, but after a while I would lose consistency of what type of errors were supposed to return what kind of http error codes. Now I can guarantee some sort of consistency, and I think the benefit outweighs the fact that I&#39;m using a type switch.</p></pre>daddyc00l: <pre><p>maybe you should be calling the error handler httpErrorHandler ?</p></pre>blogetry: <pre><p>It&#39;s also better because unless you&#39;re just passing off errors to be printed and panicked, you need the context to actually handle the error. If you pass an error to a separate function, you lose all the context you need to resolve the error.</p></pre>andreynering: <pre><p>You are right, when you <em>can</em> handle the error (And when you can, you also should).</p> <p>In some cases the only thing you can do is log and panic (or return error 500 if it is a web app, etc.).</p></pre>headzoo: <pre><blockquote> <p>In some cases the only thing you can do is log and panic</p> </blockquote> <p>Sure, but at the top level of your app. Panics should only be triggered in a few places in your code. Libraries, functions, methods, etc shouldn&#39;t panic. If they can&#39;t handle the error it should be passed up the stack.</p> <p>The checkErr() function is really only useful in the main() function, and there shouldn&#39;t be so many error checks in main() that a convenience function is even warranted.</p></pre>andreynering: <pre><blockquote> <p>Panics should only be triggered in a few places in your code.</p> </blockquote> <p>Agree.</p> <p>But panicing is not the only thing you can do inside the function. You can have one function that panic, other that return error 500, other that writes a warning in the log, etc. Whatever you want, actually. The ideia is reducing duplication of code.</p> <p>Sure there are cases you have to handle it youself. I also agree it should be used at high level. In low level functions it is better to just return the error to the caller.</p></pre>sriramalka: <pre><p>Have you seen Rob Pike&#39;s &#34;errors are values&#34; talk? There are ways of structuring your code so that every alternate line is not &#34;if err ...&#34;.</p></pre>gbitten: <pre><p>I love the way how Go handles errors for its simplicity. BTW, another related cool feature are the <a href="http://blog.golang.org/defer-panic-and-recover" rel="nofollow">Defer, Panic, and Recover functions</a>. I use them extensively, not only for errors, but as a way to exit quickly of a series of nested functions. The below web application example, I use them to print errors and to redirect to other url. </p> <pre><code>package main import &#34;net/http&#34; func CheckError(err error) { if err != nil { panic(err) } } func Redirect(url string) { panic(url) } func handler(w http.ResponseWriter, r *http.Request) { defer func() { if rec := recover(); rec != nil { switch rec.(type) { case string: url := rec.(string) w.Header().Set(&#34;Location&#34;, url) w.WriteHeader(http.StatusFound) case error: err := rec.(error) http.Error(w, err.Error(), http.StatusInternalServerError) } } }() if user_not_logged() { Redirect(&#34;http://site.com/login&#34;) } err := do_something() CheckError(err) do_more_stuff() } func main() { http.HandleFunc(&#34;/&#34;, handler) http.ListenAndServe(&#34;:8080&#34;, nil) } </code></pre></pre>warmans: <pre><p>That&#39;s the main pattern. One other is to create methods like: </p> <pre><code>func MustMakeThing() </code></pre> <p>which will panic or log.Fatal if they fail. The Must prefix indicates it has to succeed or it will panic (used in the regex package for example).</p> <p>Note that if you use:</p> <pre><code>if thing, err := MakeThing(); err != nil { // handle error } </code></pre> <p>the thing variable is only available within the braces, so it&#39;s mainly useful when the method only returns and error and nothing else.</p></pre>andreynering: <pre><p>I like this pattern, specially for things like a broken regex, that is a must fix.</p></pre>random314: <pre><p>I&#39;m pretty sure half of my code consists of that check... That included unit tests.</p></pre>doki_pen: <pre><p>Passing the error to the caller::</p> <pre><code> func myfunc() (err error) { val, err := errorprone() if err != nil { return } // do something with val } </code></pre></pre>yeah-ok: <pre><p>Hmm, what about this full on simple function:</p> <blockquote> <p>thing, err := MakeThing()</p> <p>PanicOnError(err);</p> </blockquote> <p>Makes 3 lines of code into one and doesn&#39;t obscure the code much as far as I can see.</p></pre>skelterjohn: <pre><p>On the other hand, your program immediately crashes. So it&#39;s got that going against.</p></pre>yeah-ok: <pre><p>Point being that it could be:</p> <blockquote> <p>RecoverOnError(err,functiontorevocerwith())</p> </blockquote> <p>Or whatever you please, all the same no conditional statement need to be written out in the middle of one&#39;s code if it&#39;s not wanted.</p></pre>skelterjohn: <pre><p>You just have all the error handling hidden away, easily forgotten about when you add new potential errors, and if you do it wrong panics that you <em>don&#39;t</em> intend will be suppressed.</p> <p>Panics are not exceptions, and shouldn&#39;t be used as such. There are too many leaks in the abstraction.</p></pre>tucnak: <pre><p>Well, it&#39;s quite disgusting tbh. Since it requires nothing but understanding of conditional statements, which is the most popular PL construct, it&#39;s quite readable. I can&#39;t understand why Go advocates keep on defending it and trying to pull some daft points off... you know, all you need to say is &#34;type system&#39;s weak, the language considered awfully small, so C-style error handling is the best we could get by design. it&#39;s not ideal, but works good for what Go is for&#34;. Go advocates keep on saying that while error is a trivial interface, errors are values and therefore, incredibly powerful, but that&#39;s completely false. Type system of the language and the actual error handling approach absolutely fails when it comes down to actually handling them. All we do is <code>return err</code> all the way through stack and possibly log it somewhere (usually, even without any details — good luck in debugging). Wanna see a stack trace? Context? You just can&#39;t, the language is created for fresh-out-of-school Googlers, not capable of understanding a language with abstractions more sophisticated than trait.</p> <p>Answering your question: No, <code>if err != nil</code> is definitely not the best idiom for error handling. Imo, Either monad does significantly better. In fact, if you think about it, it&#39;s not logical to return errors at all. There must be a distinct layer for error handling, since error has nothing to do with the value returned by some function, it&#39;s just what could possibly happen.</p></pre>daniu: <pre><p>I think you&#39;re entirely right. There seems to be that great feeling of satisfaction that &#34;an error is a value&#34; - when conceptually it just isn&#39;t. An error is a circumstance that invalidates a result.</p> <p>And even within Go, it&#39;s not really consistent - when a function returns &#34;multiple values&#34; (result, error), either the result is valid, or the error is set - so it really just returns one value.</p> <p>That said, it&#39;s not that I&#39;m particularly offended or anything by Go&#39;s error concept - errors have always been problematic to design into a language; C&#39;s global error, Java&#39;s RuntimeExceptions that don&#39;t have to be declared so there&#39;s still ways to run into undeclared problems...</p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

544 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传