Something that has bothered me about Go errors - how do I resolve them in a loosely coupled way?

xuanbao · · 467 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>This is true for practically every programming language, but particularly in Go, where the programmer is encouraged to return up the stack.</p> <p>Calling functions should eventually handle the error - how do I eliminate the tight coupling?</p> <p>In another language I might use a derived exception object that contains its own methods. Interfaces could potentially do this in Go but it probably would be very messy and not idiomatic.</p> <p>The closest I found is <a href="https://blog.golang.org/error-handling-and-go" rel="nofollow">https://blog.golang.org/error-handling-and-go</a> but he says not to return the type of an error in a function signature for reasons I do not entirely understand.</p> <hr/>**评论:**<br/><br/>Femaref: <pre><p>Using the <code>error</code> interface does not mean you lose type information. In the end, an error is any type that implements the <code>error</code> interface (implements <code>Error() string</code> method). </p> <p>Using an example from the link:</p> <pre><code>if err := dec.Decode(&amp;val); err != nil { if serr, ok := err.(*json.SyntaxError); ok { line, col := findLine(f, serr.Offset) return fmt.Errorf(&#34;%s:%d:%d: %v&#34;, f.Name(), line, col, err) } return err } </code></pre> <p>The type assertion in line two allows you to handle errors in a specific way. The same is done in <code>os</code> with <code>*os.PathError</code>, <code>net</code>with <code>*net.OpError</code>. I do it the same way for errors that need to carry more information. If there are very specific errors, you can use a package variable to compare against. <code>database/sql</code> does this with <code>sql.ErrNoRows</code>. Just create the error with <code>errors.New</code> to produce a descriptive error.</p> <p>Why not have the error in the function signature? You make it harder for yourself and the caller. For yourself, because you are restricted to that one error type. You can&#39;t just pass through an error that is out of your control, e.g. trying to open a file that doesn&#39;t exist. You have to shoe-horn every error case into your new error type.</p> <p>You make it harder for the caller as you can&#39;t:</p> <ul> <li>reuse your error variable, if the error doesn&#39;t implement <code>error</code></li> <li>easily log the error as it doesn&#39;t implement <code>error</code></li> <li>type assert as the value is not an interface, you lose the actual type of the error and associated info</li> </ul> <p>in short: it makes everything harder. The <code>error</code> interface is the sane default case, and as it&#39;s an interface, you can still access the actual error by type asserting.</p></pre>chewxy: <pre><p>Other redditors have commented, widely on a variety of other things. But something in particular jumped out at me:</p> <blockquote> <p>particularly in Go, where the programmer is encouraged to return up the stack.</p> </blockquote> <p>In Go, the programmer is encouraged to handle errors. Returning up the stack isn&#39;t particularly great for a huge number of usecases.</p></pre>TheMerovius: <pre><blockquote> <p>Calling functions should eventually handle the error - how do I eliminate the tight coupling?</p> </blockquote> <p>In my opinion: You can&#39;t. All you may be able to do, is to remove compiler checks verifying that you are coupling correctly, but the coupling itself is intrinsic.</p> <p>Say, for example, you are using a package <code>foo</code>, that stores some state in a file. Any errors encountered are passed on to you (potentially wrapped in some way). At some point, that package gets a feature to read the state from the network instead, say by making an HTTP request to etcd. Suddenly, all the possible error classes completely change - previously, you would get e.g. an <code>*os.PathError</code>, now you suddenly get a <code>*something.HTTPError</code>. When switching to the new feature, your code breaks, because you type-switched on <code>*os.PathError</code>, which will no longer be returned, but the compiler was happy (because it got an <code>error</code> and <code>*os.PathError</code> satisfies the <code>error</code> interface).</p> <p>From this POV, errors and their types are inherently API surface. Any concrete type that <code>foo</code> returns and that you rely on becomes part of <code>foo</code>s API. But the underlying errors (in this example <code>*os.PathError</code> and <code>*something.HTTPError</code>) that <code>foo</code> encounters are <em>implementation details</em> and shouldn&#39;t have been exposed in the API without careful consideration.</p> <p>As a consequence, if you rely on any feature of an <code>error</code> except it&#39;s <code>Error</code> method, you are either a) binding yourself to implementation details, which are bound to change and won&#39;t be compiler checked or b) are relying on documented, stable APIs, in which case <em>it is fine to couple strongly</em>. Just as it&#39;s fine to couple yourself strongly to the signature of some exported function of a different package.</p> <p>A corollary of this is, that packages should spend just as much time thinking about their error API, as they should spend on all the other APIs. And find the right tradeoff between hiding implementation details and providing the flexibility and useful information to handle the errors they return. Personally, I tend to start with &#34;expose no information but the error interface&#34; and then refine by adding types, once the need to distinguish errors arises.</p></pre>ui7_uy8: <pre><blockquote> <p>but he says not to return the type of an error in a function signature for reasons I do not entirely understand.</p> </blockquote> <p>I guess he is just saying return error interface instead of whatever concrete error type you have defined? </p> <p>Remember that &#34;go errors&#34; are not a special features of the language, it&#39;s just a basic interface. It doesn&#39;t do anything special, it doesn&#39;t give you a stack or anything, it&#39;s like returning error codes like in C. An integer is nothing special.</p> <p>It&#39;s a convention, not a &#34;feature&#34;.</p></pre>tv64738: <pre><p>Here&#39;s an example of a looser coupling between the thing generating the error and the thing handling it: <a href="https://golang.org/pkg/net/#Error" rel="nofollow">https://golang.org/pkg/net/#Error</a></p> <p>The caller doesn&#39;t have to care what the exact error was, but if <code>err, ok := err.(net.Error); ok &amp;&amp; err.Temporary()</code> then retrying is a good idea.</p></pre>SeerUD: <pre><p>Using interfaces is fine, because you can test for behaviour instead of specific types. To eliminate the tight coupling, you can present new errors, or at least bubble up errors with some specific behaviour (i.e. not a type) and handle it later by testing the behaviour.</p></pre>

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

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