<p>I'm using quite a few go packages that often provide functions that return (foo, error). However, my code ends up looking like this:</p>
<pre><code>varFoo, err := GetFoo()
if err != nil {
return err
}
sliceBar, err := SliceTheBar(varFoo)
if err != nil {
return err
}
err := CheckBarSlice(sliceBar)
if err != nil {
return err
}
...etc...
</code></pre>
<p>Throw all this above, inside, and below a loop, and it becomes quite... <del>ugly</del> cluttered and hard to read.</p>
<p> </p>
<p>Is there a better way?</p>
<hr/>**评论:**<br/><br/>almostAntiPaladin: <pre><p>The go blog has a good post on error handling. It goes over different ways to get out of the if err != nil repetitiveness. </p>
<p><a href="https://blog.golang.org/errors-are-values">https://blog.golang.org/errors-are-values</a> </p></pre>ggtsu_00: <pre><p>This is the one thing I really appreciate about Go: if something seems really tedious, repetitive or hard in Go, you are likely doing it the wrong way. </p></pre>parenthephobia: <pre><p>It goes over <em>one</em> way, several times: store an error in a shared location. IMO, it doesn't solve anything except where you're calling the same function repeatedly.</p>
<p>If, as I find is more typical, each function you're calling that might return an error is a <em>different</em> function, you then get to write wrapper functions for each of those: and those wrappers will contain the <code>if err != nil</code> logic you were trying to avoid anyway.</p></pre>sh41: <pre><p>I would highly recommend reading this article on this topic. It's very relevant.</p>
<p><a href="https://medium.com/@shazow/code-boilerplate-is-it-always-bad-934827efcfc7">https://medium.com/@shazow/code-boilerplate-is-it-always-bad-934827efcfc7</a></p></pre>DaBeechees: <pre><blockquote>
<p>When a friend was reading through the source code for ssh-chat, he was surprised that I implemented my own Set type. I explained that while Go doesn’t have a built-in Set, it’s just a few lines of boilerplate to make your own on top of a map. In fact, I noticed that my version of Set evolved to be fairly specific to how it was being used. In retrospect, I’m glad that I was tweaking my own implementation iteratively rather than spending time working around whichever limitations a generic library might have had.</p>
</blockquote>
<p>Jesus Christ what is wrong with this guy.</p></pre>metamatic: <pre><p>I wonder how he handled the crypto.</p></pre>mtanski: <pre><p>You need to get this keyboard: <a href="https://pbs.twimg.com/media/DCIF7-2W0AEAv9c.jpg">https://pbs.twimg.com/media/DCIF7-2W0AEAv9c.jpg</a></p></pre>Undreren: <pre><p>Or make a code snippet in atom/VSCode</p></pre>opiniondevnull: <pre><p>It's already there in vscode, type ife+enter and it stubs its out for you.</p></pre>circuitously: <pre><p>In gogland it's "err.nn" if anyone cares</p></pre>comrade-jim: <pre><p>Can be done in vim too. What I do is ignore errors with <code>_</code> and then go back and "expand" the blank identifiers into error checks using a custom key map (because snippets annoy me). </p>
<pre><code>nmap <leader>e xierr<esc>oif err != nil {<enter>
</code></pre>
<p>With this you just put your cursor over the <code>_</code> and press <code>\e</code> and it generates the code. </p></pre>sh41: <pre><p>It now exists for real, see <a href="https://www.reddit.com/r/golang/comments/6v63c2/a_solution_to_if_err_nil/" rel="nofollow">https://www.reddit.com/r/golang/comments/6v63c2/a_solution_to_if_err_nil/</a>.</p></pre>natefinch: <pre><p>I actually made that button specifically because of mtanski's suggestion. :) </p></pre>elagergren: <pre><p>Short answer: no. </p>
<p>Also, good practice is to "wrap" your errors with added context, like so: <code>return fmt.Errorf("xyz failed: %v", err)</code></p>
<p>In some instances it might make sense to use a wrapper function to cut down on the "if err != nil" boilerplate—and some people on here advocate for it—but I feel comfortable saying that, in practice, it's frowned upon.</p></pre>jmank88: <pre><p><a href="https://github.com/pkg/errors">https://github.com/pkg/errors</a></p></pre>cube2222: <pre><p>I think it's great to make yourself a parameterized snipped.
Currently in Gogland I created a live template called "wrap":</p>
<p><code>
if err != nil {
return errors.Wrap(err, "$VAR1$")
}
$END$
</code></p></pre>poetic_waffle: <pre><p>To rephrase: Use this package if you are too lazy to do proper error handling.</p></pre>natefinch: <pre><p>I use it to add a stack trace to my errors, and then to be able to annotate them without losing that stack trace.</p></pre>poetic_waffle: <pre><p>Yeah that's my point. Stack traces are the lazy man's error handling.</p>
<p>To quote <a href="/u/themerovius" rel="nofollow">/u/themerovius</a>:</p>
<blockquote>
<p>I really dislike that package. It's born out of an unreasonable rejection of debug logging, it encourages useless error messages (It equates "helps telling the user what went wrong" with "barfing out a stack trace") and with the language as it is, it just fails if not everyone is using it (and thus violates the whole "errors are just values" idea).</p>
<p>I think it's a perfect example of something that seems reasonable and helpful at first glance, but just encourages bad design and bad code when looked at with some distance.</p>
</blockquote>
<p><a href="https://www.reddit.com/r/golang/comments/6t9puo/which_external_packages_would_you_like_to_become/dljwh8d/?st=j6mqjden&sh=7eed5b0f" rel="nofollow">source</a></p></pre>poetic_waffle: <pre><p>Use this package if you don't care about error handling.</p></pre>cube2222: <pre><p>What alternative do you think is appropriate and better?</p></pre>poetic_waffle: <pre><p><a href="https://blog.golang.org/error-handling-and-go" rel="nofollow">https://blog.golang.org/error-handling-and-go</a></p></pre>dchapes: <pre><blockquote>
<p>return fmt.Errorf("xyz failed: %v", err)</p>
</blockquote>
<p>As <a href="/u/DenzelM">/u/DenzelM</a> says, this is a very bad idea. Among other things it removes any chance for the caller to investigate the error in any sane way. (e.g. <code>if err == io.Err</code>, <code>if os.IsNotExist(err)</code>, <code>if ne, ok := err.(net.Error); ok && ne.Temporary()</code>, etc, etc).</p></pre>elagergren: <pre><p>Sure, if you're returning an err from <code>io.Reader.Read</code> then you'll want people to be able to check the error's type. </p>
<p>A fair amount of libraries don't offer exported types, though, so it depends on the situation. </p></pre>syrupmaple: <pre><p>Very useful wrapper. Haven't come across before. Thank you!</p></pre>goomba_gibbon: <pre><p>This is a convention I've seen used quite a bit. Generally always begin with a lowercase letter and a colon before the wrapped error.</p>
<p>Francesc Campoy does this in his excellent justforfunc YouTube series.</p></pre>DenzelM: <pre><p>Please, just no: <a href="https://www.reddit.com/r/golang/comments/649o0c/comment/dg0neoo" rel="nofollow">https://www.reddit.com/r/golang/comments/649o0c/comment/dg0neoo</a>. Same discussion on <code>errors.New</code> applies to <code>fmt.Errorf</code>. Only use them if you want to torture the people that use your code!</p></pre>rberenguel: <pre><p>Also, fmt.Errorf is 4 times slower than errors.New !!</p>
<p>(tongue-in-cheek, but more seriously, the former is a wrapper of the latter, which then adds an unneeded extra call and fmt as a dependency)</p></pre>Abyxus: <pre><p>Ugh, no.<br/>
There is <code>stackerr</code> for that - <a href="https://github.com/facebookgo/stackerr" rel="nofollow">https://github.com/facebookgo/stackerr</a></p></pre>: <pre><p>[deleted]</p></pre>natefinch: <pre><p>One of my ten commandments of coding is that line returns aren't evil. optimizing for compactness almost always makes your code less readable, not more readable.</p></pre>jeffrallen: <pre><p>I agree with most of the replies here, but I'd like to add this idea: If you use automated ways to put this snippet into your code, you are losing the chance to really think about those lines. Is this really an error that only the upper layer can decide about? Is there some extra cleanup needed when this place gets an error that other places don't need?</p>
<p>Error handling code needs to be a first class citizen in your consciousness. Writing code like it can never fail is occasionally useful during beginning programming exercises, but professional code is not written that way, and if you hope to earn a salary (not just "get" it, but <em>earn</em> it) you will be in the habit of making robust code that responds to the errors that will arrive in a constructive way.</p></pre>carsncode: <pre><p>Plus, not thinking about each instance means you aren't adding comtext-specific details to the errors when you return them. Just <code>return err</code> should almost never appear in code; at the least it should be <code>return fmt.Errorf("failed to foo the bar: %s", err)</code>. Otherwise when those errors finally trigger and bubble up, you'll have no idea what actually caused the problem and you'll make troubleshooting that much harder on yourself.</p></pre>kaeshiwaza: <pre><p>I can understand immediately the flow of your code and where an error can occur. If I need to debug it I know where I will just wrap your error with more context, and that's all.</p>
<p>Keep it this way, when you'll read it in some years you'll be happy to find so readable code !</p></pre>nemith: <pre><p>This is funny. In other langues i freak out now because I am not checking my errors explicitly.</p></pre>Sythe2o0: <pre><p>If you are doing these operations a lot, you can define a struct wrapper around these functions, and have each of those check that the error is not nil before proceeding, something like this: <a href="https://play.golang.org/p/YbL-UOjRuS">https://play.golang.org/p/YbL-UOjRuS</a></p>
<p>This is the wrapper style that elagergen is referring to. </p></pre>villiger2: <pre><p>Seems like an anti-pattern and against go's simplicity first ideal.</p></pre>Sythe2o0: <pre><p>What isn't simple about it?</p></pre>lonahex: <pre><p>As a consumer, the function signatures don't tell your that they might raise an error. It might not be obvious to you unless you carefully read the documentation. If you want to stop operation after a single method call, you end up with the same thing.</p>
<p>As a maintainer, all methods must check error on the reference before doing anything. Compiler and other tools won't help if you forget to do it. </p>
<p>In general, you loose most if not all help from tooling/compiler you'd get by dealing with errors in the normal way. You also make the consumers and contributors of your code/lib learn a new pattern that they must remember only when dealing with this struct you provide. I'd say this has potential to lead to a human errors.</p></pre>Sythe2o0: <pre><p>Fair points, but this isn't a new pattern.</p>
<p>Edit: You could also have each function still return an error, but note in the documentation the additional functionality.</p></pre>miyakohouou: <pre><p>I think Rob Pike's article <a href="https://blog.golang.org/errors-are-values" rel="nofollow">Errors Are Values</a> is probably the best compromise between something that helps with the problem while keeping the code fairly idiomatic, inter-operable with external libraries, and readable by everday go developers.</p>
<p>There are more exotic exotic approaches like <a href="https://speakerdeck.com/rebeccaskinner/monadic-error-handling-in-go" rel="nofollow">using monads for error handling</a> and even some <a href="https://github.com/asteris-llc/gofpher" rel="nofollow">libraries to support this</a> (disclaimer: I wrote that presentation and library, but there are others out there) but these approaches typically fail because of the lack of parameterized types as well as the general lack of support across external libraries.</p>
<p>I've still used monadic pipelining for some sections of production code, but doing so is a much heavier decision than it would be in languages with better support for the constructs.</p></pre>travisby: <pre><p>You can't improve all of it, but you can do a block-level assignment with conditionals:</p>
<pre><code>if err := CheckBarSlice(sliceBar); err != nil {
return err
}
</code></pre>
<p>This will also work with variables you only need inside the conditional blocks (also works in the else statements).</p></pre>knome: <pre><p>Ha. This looks like a first step towards perl's old <code>CheckBarSlice(sliceBar) or die ;</code>, automatically assigning to and using <code>$_</code> of course</p></pre>earthboundkid: <pre><p>I've written a function called <code>die</code> many times that prints and exits if an error is not nil. It's not good in medium/large programs but it's fine for a small one. </p></pre>ariacode: <pre><p>using a snippet helps.</p></pre>natefinch: <pre><p>Problem solved: <a href="https://twitter.com/NateTheFinch/status/899730215957561344" rel="nofollow">https://twitter.com/NateTheFinch/status/899730215957561344</a></p></pre>Abyxus: <pre><p>No, there is not better way. </p>
<p>Go should have use tagged unions like <code>Result<T, error></code> but its authors ignored all the modern knowledge on language design and now we have to use all of this error-prone boilerplate code.</p></pre>asyncrep: <pre><p>Be gone, blasphemer!</p></pre>int32_t: <pre><p>TL;DR: the elimination of the boilerplate error handling code is not particularly interesting when programming in Go, and that's fine.</p>
<p>I think the Go way doesn't incentivize hiding complexity by syntactic abstractions.</p>
<p>There are differences between semantic abstraction and syntactic abstraction (terms invented just now for convenience, sorry if they are mistaken ones).</p>
<p>To clarify what I mean:</p>
<ul>
<li><p>Semantic abstractions: state machines, callbacks, trampolines, exceptions, coroutines, message passing, threads, garbage collection, reference counting, scheduling, and so on. They are the things that could be implemented in the chip or OS level and could occur runtime costs.</p></li>
<li><p>Syntactic abstractions: preprocessors, templates, macros, operator/function overloading, generics, ADT, tuples, and so on. They are the things that typically are only interesting in the PL or compiler level. They could be zero-cost or not depending on the cleverness of the compiler.</p></li>
</ul></pre>dalastboss: <pre><p>Go lacks either semantic or syntactic abstraction for error handling</p></pre>ThaChippa: <pre><p>I don't joke about that. That's not funny.</p></pre>int32_t: <pre><p>Can you elaborate? I spoke of that wholeheartedly and in good faith. ;)</p></pre>kalekold: <pre><p>I use something similar to this to make these kinds of check a one-liner:</p>
<pre><code>func OnError(err error, text string) {
if err != nil {
fmt.Fprintln(stderr, text+": %s", err.Error())
os.Exit(1) // or log
}
}
func Error(text string) {
fmt.Fprintln(stderr, text)
os.Exit(1) // or log
}
</code></pre></pre>Nhoya: <pre><p>you should use one function to handling the errors, you code is ugly because you should pass the error to a single function lile</p>
<pre><code>func checkErrors(err error){
if err != nil {
do stuff
}
}
</code></pre>
<p>and call it with </p>
<pre><code>err := CheckBarSlice(sliceBar)
checkErrors(err)
</code></pre></pre>lonahex: <pre><p>This promotes generalization of errors which makes them less useful. It could be useful in certain situations but most of the time, you'd want to augment errors with extra info which would make function call sites more verbose, almost to the point of no gain.</p></pre>Nhoya: <pre><p>in the case the user proposed he make the same stuff over and over, and if you make the same thing more than 1 time you need a function, if he need to handle the errors in particular case he can avoid using it</p></pre>
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传