Built-in error interface is not enough

polaris · · 414 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p><a href="https://milkr.io/tsaikd/Go-lang-error-handling" rel="nofollow">https://milkr.io/tsaikd/Go-lang-error-handling</a> </p> <ul> <li>Use errors/fmt package &gt; hard to catch/track</li> <li>Use customized error struct &gt; annoying</li> </ul> <p>So I write a helper package. </p> <p>Source Code: <a href="https://github.com/tsaikd/KDGoLib/tree/master/errutil" rel="nofollow">https://github.com/tsaikd/KDGoLib/tree/master/errutil</a></p> <hr/>**评论:**<br/><br/>shovelpost: <pre><p>Errors are just values. Stop over-complicating things. Go is not Java.</p></pre>rrr00bb: <pre><p>see how github.com/uber-go/zap handles this. standard Go logging is simplistic, and encourages amazingly crappy logging. the problem with &#34;errors are just values&#34; is that at the very least - errors have <em>parameters</em>, if not also cause errors and origins (file:line or stack trace). if you just dump printfs to stdout, you hand totally unparsable goo to your log collectors; which is why those are often based on document indexing engines - which seems crazy for logs. switching over to zap had many huge benefits, such as: the library makes it easy to use constant values for error names (so that they can be <em>counted</em> when there are millions going through log collectors), they have named parameters so that you can do meaningful work with these counts. from logs, you should be able to compute aggregate performance numbers, reliability rates, etc. you can&#39;t do that if you are just pasting strings together and dumping them to stdout with no more structure than a timestamp. an unambiguously parseable format (json, thrift, csv, etc) also makes it easy to transform logs into the format that is most suitable for collection. I think the standard logging library is as lame as it is because Google uses something totally different for real-world use (related to Borgmon, etc.). You need to be able to correlate messages that relate to a single http session, etc. So you if you don&#39;t have thread-local re-assign of loggers, then you need something like what Uber&#39;s zap does to hand derived loggers around. I can go on. When I import third-party libraries, they dump junk into my stdout, and usually provide no way to override the libraries logger so that I can make its logger conform. You don&#39;t need a complicated library. You just need an interface that can be overridden so that the formatting can be made to conform to what the overall app needs.</p></pre>megadyne: <pre><p>Your problem seems to be with logging not errors...</p> <blockquote> <p>errors have parameters,</p> </blockquote> <p>Than make an errors which have parameters... it doesn&#39;t have to be a plain string.</p></pre>rrr00bb: <pre><p>the two are strongly related. if most of your errors are random non-constant strings, then you get into a habit of simply logging them and not being able to check their values; because you can&#39;t reliably <em>classify</em> them. If there were always part of an error that you can check to classify it (4xx or 5xx decision), or you were always supplied with a function that will properly classify it, then you can always do the right thing. One of the reason that errors are not always constant is the cause parameter. If another library hands you &#34;didnt get ciphertext foo.txt: file not found&#34;, it makes you grep for stuff. What it should really be is that there is a constant &#34;didnt get ciphertext&#34; with parameters &#34;filename&#34; and &#34;cause&#34;. And when a third-party library gets an error, since the standard only supplies a few bad options like fmt.Printf or log.Printf, these apps call these functions directly and just dump them into your stdout. What is much better is to have a var Logger=NewRootLogger() that when you use <em>their</em> package, you can override the logger their.Logger=my.Logger ... so that it&#39;s uniform. Maybe your stdin/stdout have a well defined grammar (a debugger), where you really expect stderr to be all logs (the way it should be done). Logs should basically be a list of timestamped and leveled errors, where you do not commit to what the actual output format is. The way that the logger is configured determines what is actually printed, and what format it is on. It&#39;s the top level program using all the libraries that determines this. Otherwise, you will get a simple requirement like &#34;I want to see all logs that happened during my http session&#34;, and its somewhere from painful to impossible to do this right, especially in the presence of third party libraries.</p></pre>tsaikd: <pre><p>Write a new error struct is annoying. You have to implement Error() interface for each error struct. My package is a helper to do this, you just need one line code for each error type.</p> <p>And as rrr00bb said, logging and errors are strongly related. So my package provide the trace and formatter interface, too.</p></pre>tsaikd: <pre><p>The main problem is how to handle some expected errors, and bypass unexpected errors? The easiest way to handle error is just panic(), but it&#39;s irresponsible and should not happen in stable applications.</p></pre>shovelpost: <pre><blockquote> <p>The main problem is how to handle some expected errors, and bypass unexpected errors?</p> </blockquote> <p>Read <a href="https://blog.golang.org/errors-are-values" rel="nofollow">Errors are Values</a> and <a href="https://blog.golang.org/error-handling-and-go" rel="nofollow">Error handling and Go</a>. Have your read those? Read them again.</p> <blockquote> <p>The easiest way to handle error is just panic()</p> </blockquote> <p>What?</p></pre>tsaikd: <pre><p>So you think the errors package is perfect, but I think it could be improved in some cases.</p></pre>megadyne: <pre><p>He never said that. He pointed out that you don&#39;t understand how to handle errors. For example you wrote:</p> <blockquote> <p><strong>How to identify errors if their error messages are the same?</strong></p> <p>It&#39;s impossible</p> </blockquote> <p>You could just define specific errors for that case, for examples how it is done in the <a href="https://golang.org/src/io/io.go?s=777:1279#L21" rel="nofollow">io package</a></p> <p>You can just check </p> <pre><code>if err == io.EOF { ... stuff here } </code></pre> <p>If an error with the same &#34;EOF&#34; message exists, it wouldn&#39;t be the same error.</p> <p>Edit: <a href="https://play.golang.org/p/PN30mBMMxm" rel="nofollow">Example</a></p></pre>tsaikd: <pre><p>OK...I got the point, and removed from my document. thx</p></pre>rrr00bb: <pre><p>this is a perfect example of what is wrong with the normal way of handling errors in Go. because errors don&#39;t keep their message (ie: their printf mask - a compile-time constant) separate from their parameters (ie: filename, previous cause error, etc), you end up doing things like strings contains or regexes because so many libraries just paste strings together. you need to be able to classify errors that come back up to you to know that you need a 4xx class error versus a 5xx error. If there were part of the error that you could always do an equality check on, and know that you can ignore the parameters, it would go a long way towards cleaning this up. As it is, you can write your own code to do nicer errors, but third party libraries log unparseable stuff to your stdout/stderr and return you errors with strings that are not constants, because there is no standard place to put parameters. It is very important to be able to overwrite a packages logger so that you can make its format fit into your application. Logs are like a long sequence of return values; a data structure that needs to be parsed by tools.</p></pre>Crast: <pre><p>Errors are actually simple to classify.</p> <p>Let&#39;s say you&#39;re writing an API that has many levels, and eventually you want to communicate to the caller via HTTP the &#39;class&#39; as you say of error. If you really think about it, you&#39;ve got very few things:</p> <ul> <li>Not Found (404)</li> <li>Forbidden (403)</li> <li>Invalid input (400)</li> </ul> <p>And then, everything that&#39;s not one of those, is a 500 server error.</p> <p>You have many ways you can approach this to communicate this information:</p> <ol> <li>you can define values for generic errors for these values, and compare with these values to select your response code... similar to comparing<code>io.EOF</code> as mentioned earlier.</li> <li>You can define concrete types for each of these, so you can provide custom messages but still know which &#39;kind&#39; of error it is with type asserts.</li> <li>You can define a single concrete type for communicating these values, and use a field on there, a constant maybe, to determine the &#39;class&#39; of error. GRPC does this quite well: <a href="https://godoc.org/google.golang.org/grpc/codes" rel="nofollow">https://godoc.org/google.golang.org/grpc/codes</a> and <a href="https://godoc.org/google.golang.org/grpc#Code" rel="nofollow">https://godoc.org/google.golang.org/grpc#Code</a></li> </ol> <p>After doing that, in your error handling, you can assert the interface to the given type in the response handler:</p> <pre><code>if e, ok := err.(*MyErrType); ok { switch e.Code { case NotFound: // return HTTP 404 case Forbidden: // return HTTP 403 // etc } } else { // anything else, return HTTP 500 } </code></pre> <p>Basically, when you&#39;re looking to communicate anything, it&#39;s likely you know the context in which you are creating the error, so you can communicate that by coming up with an error value you know, and pass it up the stack until somewhere you know what to do with it (often at your HTTP handler)</p> <p>Any error you didn&#39;t explicitly create yourself is likely an unexpected error from some library, or some eventuality. In server applications it&#39;s typically things like socket disconnects, DNS failures, timeouts, and such, there are so many types of these that there&#39;s no real reason you should have to explicitly know all the underlying types or deal with it, just use the <code>else</code> case and return HTTP 500, optionally including the value of <code>err.Error()</code>. &#34;Errors are values&#34; is not just a mantra people repeat to be annoying, what it&#39;s saying is... stop getting concerned about the underlying type of things and just use the value.</p></pre>tv64738: <pre><pre><code>if err.Error() == &#34;An error occur&#34; { blahblah... } </code></pre> <p>If that&#39;s your understanding of how it should be done, please do not try to make replacements; instead, strive to understand Go better.</p></pre>tsaikd: <pre><p>This is an example of error generated by fmt package in <a href="https://github.com/docker/docker/blob/v1.11.2/daemon/graphdriver/plugin.go#L24" rel="nofollow">docker source code</a>.</p> <p>Could you tell me how to catch the error? Do not say lookupPlugin() is not exported method, it&#39;s just an example in popular go project</p></pre>megadyne: <pre><p><a href="https://play.golang.org/p/ic2106pJFX" rel="nofollow">https://play.golang.org/p/ic2106pJFX</a></p> <p>(BTW: I&#39;m not a go programmer, just looked into it a little bit.)</p></pre>tsaikd: <pre><p>You show the most general way to generate error: create an error struct.</p> <p>But it&#39;s annoying, you have to write a log of codes for the error.</p></pre>megadyne: <pre><p>You don&#39;t have to create a struct, I just wanted to reuse the fmt.Sprintf for <em>Error() string</em> :)</p> <p>In general you only need a unique interface (i.e. <em>DriverError</em> with the unique <em>DriverError()</em> function)</p> <p>For example here is a new type which is basically a string, which just implements the <em>error</em> Interface and <em>DriverError</em> Interface.</p> <p><a href="https://play.golang.org/p/xtk4ouBjpn" rel="nofollow">https://play.golang.org/p/xtk4ouBjpn</a></p> <p>And basically you only have to write it once. So it isn&#39;t a major hassle. And you don&#39;t need an extra package import for basic stuff.</p></pre>tsaikd: <pre><p>OK, replace my previous reply, s/struct/interface/</p> <p>In your second example, you have to declare interfaces for each type of errors, I think it&#39;s also annoying :(</p> <p><a href="https://play.golang.org/p/IFRrpPhDak" rel="nofollow">https://play.golang.org/p/IFRrpPhDak</a></p></pre>tv64738: <pre><p>No, I can&#39;t. But you are blaming Go for Docker&#39;s mistakes.</p></pre>HectorJ: <pre><p>This idea of error factories is interesting.</p> <p>You&#39;re missing some kind of error wrapping though.</p> <p>The helper I use now is Dave Cheney&#39;s <a href="https://github.com/pkg/errors" rel="nofollow">https://github.com/pkg/errors</a></p></pre>tsaikd: <pre><p>the error factory instance could wrap other error by calling New() e.g.</p> <pre><code>var ErrorChild = errutil.NewFactory(&#34;child&#34;) var ErrorParent = errutil.NewFactory(&#34;parent&#34;) func demoChild() error { return ErrorChild.New(nil) } func demoParent() error { return ErrorParent.New(demoChild()) } </code></pre></pre>

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

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