TE
科技回声
首页24小时热榜最新最佳问答展示工作
GitHubTwitter
首页

科技回声

基于 Next.js 构建的科技新闻平台,提供全球科技新闻和讨论内容。

GitHubTwitter

首页

首页最新最佳问答展示工作

资源链接

HackerNews API原版 HackerNewsNext.js

© 2025 科技回声. 版权所有。

Errors are values

138 点作者 davecheney超过 10 年前

28 条评论

chrismorgan超过 10 年前
What in Go is a return type of (Anything, error) with typically exactly one of the two being nil is in Rust the Result type, `enum Result&lt;T, E&gt; { Ok(T), Err(E) }`, something which is explicitly exclusively-either an OK T or an error E.<p>The equivalent of `x, err = expr; if err != nil { return err }` is <i>very</i> common in Rust; it would be written `x = match expr { Ok(x) =&gt; x, Err(e) =&gt; return Err(e) }`. It’s such a common pattern that a macro named try! was placed in the standard library for this incantation. Therefore you can write `x = try!(expr);` (or if you’re going to ignore the Ok variant, just `try!(expr)`).<p>Rust also warns you if you do nothing with a Result object; you can ignore the warning or suppress it, but it all makes it very much harder to ignore such error conditions. Go is one step up from C, where integral error-indicating return codes are the norm and are typically ignored, but I feel it’s still a fair way off the confidence that Rust gives me that I haven’t accidentally forgotten to deal with an error case. If you want people to <i>get</i> the message “Whatever you do, always check your errors!”, it helps if you design the language to make it hard for the user to forget.<p>(You people that know Rust, I’m ignoring the `FromError` part for simplicity. That’s only in libstd’s try!, anyway; libcore’s try! doesn’t have it.)
评论 #8878703 未加载
评论 #8877867 未加载
评论 #8877832 未加载
评论 #8877995 未加载
评论 #8879634 未加载
rtpg超过 10 年前
&gt;We recently scanned all the open source projects we could find and discovered that this snippet occurs only once per page or two, less often than some would have you believe.<p>once per page? that&#x27;s once per 30 or so lines. 2 or 3 percent of the code is this same repetitive structure? Also, only takes one time to forget this for everything to fall apart.<p>We&#x27;ve spent so much time trying to deal with uninit&#x27;d pointers and things like that on the language level, and here we have something that&#x27;s much simpler, yet super repetitive and should be easy to catch on compile time.<p>Maybe I&#x27;m just a bad programmer, but I really prefer the Java &quot;force you to catch exception&quot; model much more to this.<p>&gt;It&#x27;s worth stressing that whatever the design, it&#x27;s critical that the program check the errors however they are exposed.<p>If it&#x27;s critical to check the errors, I think that it&#x27;s worth writing language rules in a way to force people to write a &quot;has error &quot; code path (think pattern matching).<p>I get go is trying to be as small of a core as possible, but I think there&#x27;s a lot of value in extra compiler checks (with no runtime cost!) to catch this sort of thing.
评论 #8877910 未加载
评论 #8878547 未加载
评论 #8877713 未加载
评论 #8877823 未加载
评论 #8878355 未加载
overgard超过 10 年前
I get being against exceptions in a language like C++ where it can leave your program in a really weird state and have some ugly performance implications, but I don&#x27;t get the argument in languages where it can work just fine.<p>The argument seems to be &quot;you should be checking for errors anyway&quot;, but I feel like there are basically two types of errors: the obvious kind that you&#x27;re going to check for anyway (or get bug reports), like network disconnects and bad files, and the &quot;how the hell did that even happen&quot; exception. For the obvious exceptions it seems like a wash, other than the try catch block being a bit cleaner, but for the &quot;hell the hell did that happen&quot; error exceptions seem way more useful. I&#x27;d much rather my program explode immediately than risk someone letting the error passing silently because people didn&#x27;t check err because like &quot;that will never happen&quot;. I know, functions can call panic and all that, but it just seems clumsy for no net gain.<p>I do think there&#x27;s one type of error checking that doesn&#x27;t get enough love (I don&#x27;t know if Go has this, but I&#x27;m guessing probably?). &quot;assert&quot; that gets removed in release builds. It&#x27;s kind of silly when you&#x27;re working alone, but in a team of programmers or for libraries it&#x27;s awesome, because it&#x27;s basically like runtime documentation that gets compiled out. So you can warn your fellow devs about things without it bloating your program. The newer C++ has a really great feature called &quot;static_assert&quot;, which is basically a compile time assertion, which can be handy for things like checking in various places that maybe if the count in enum changed, that you accounted for it in another spot of the code, or that certain aspects of a new platform sit right, or things like that.
评论 #8878033 未加载
评论 #8878502 未加载
wyager超过 10 年前
Rather, (in Go) errors are values that violate type safety and destroy any correctness guarantees the compiler makes.<p>Languages like Rust and Haskell do this right. Both make heavy use of optionals for failable functions, and Haskell&#x27;s support for HKTs (and therefore Monads) allows awesome stuff like Maybe or Either chaining.<p>Monads allow you to write code that, syntactically, looks like you&#x27;re not doing any error handling. In reality, the error handling semantics are baked in at the Monad level. This makes sense, because error handling is usually very repetitive (e.g. &quot;Check if the function we just called failed. If it did, return early. Otherwise, keep going.&quot; ad infinitum). As a (pseudcoded) example:<p><pre><code> parseTimeString :: String -&gt; Either Error Time parseTimeString string = parse string $ do hours &lt;- readHours semicolon minutes &lt;- readMinutes semicolon seconds &lt;- readSeconds return (Time hours minutes seconds) </code></pre> If any of those steps fails, the entire computation will stop and the error message will be returned. This is roughly equivalent to checking for &quot;nil&quot; (in Go) after every function call, except you don&#x27;t actually have to do it. This is defined in the code for the Either monad, which is at <a href="https://hackage.haskell.org/package/base-4.7.0.2/docs/src/Data-Either.html#Either" rel="nofollow">https:&#x2F;&#x2F;hackage.haskell.org&#x2F;package&#x2F;base-4.7.0.2&#x2F;docs&#x2F;src&#x2F;Da...</a> . As you can see, it&#x27;s 4 lines of code.<p>(Side note: this is pretty much how you write parsers in Haskell using the Parsec library)
评论 #8880332 未加载
inconshreveable超过 10 年前
I disagree.<p>Of the various approaches I&#x27;ve seen to cope with this problem, Rust&#x27;s try! macro is the most elegant.<p>Given the practical minded nature of Go and the core team&#x27;s willingness to add in special cases to the language where appropriate, I think a Go version of try!() would be welcome and fit with the ethos of Go. It solves the 80% case and it doesn&#x27;t mean the creation of a type-safe macro system.<p>The thesis of the blog post is that errors are values and thus you can program around the repetitive handling of them. But the proposed solution of writing a one-off implementation of Maybe like `errWriter` isn&#x27;t an answer because:<p>1) Most importantly, when you need to call 3 functions which return differently typed values and can fail, there&#x27;s nothing in the language to help you. This is the common case. 2) The implementation is now less obvious. To understand it, I need to read the `errWriter` helper code. 3) The code space you&#x27;ll save by writing a new Maybe type is negligible.
评论 #8877994 未加载
modernserf超过 10 年前
This looks suspiciously like implementing Maybe every time you need to deal with a series of errors.
评论 #8877656 未加载
评论 #8877700 未加载
评论 #8877705 未加载
评论 #8878438 未加载
评论 #8877657 未加载
latch超过 10 年前
I&#x27;m a Go developer, but I don&#x27;t buy this. At best it&#x27;s showing how, with a bit of work (often times by the developer), errors aren&#x27;t that bad. But I don&#x27;t see how it&#x27;s showing that it&#x27;s better?<p>If you log the error when checking if ew.err != nil, which might be many lines or even a different function, you&#x27;ll have a hard time getting a meaningful context (which is hard to do in Go in the best case), like a stack.
评论 #8879033 未加载
评论 #8878430 未加载
sepeth超过 10 年前
I think Peter Norvig&#x27;s quote &quot;Design patterns are bug reports against your programming language&quot; is a perfect fit for this situation.
评论 #8878734 未加载
twotwotwo超过 10 年前
The stdlib&#x27;s compress&#x2F;bzip2 internally does the same sort of thing where you do a bunch of operations and then check for errors: <a href="http://golang.org/src/compress/bzip2/bit_reader.go#L12" rel="nofollow">http:&#x2F;&#x2F;golang.org&#x2F;src&#x2F;compress&#x2F;bzip2&#x2F;bit_reader.go#L12</a><p>And regexp uses panics to propagate parse errors up the stack internally, and that&#x27;s even described in Effective Go: <a href="http://golang.org/doc/effective_go.html#recover" rel="nofollow">http:&#x2F;&#x2F;golang.org&#x2F;doc&#x2F;effective_go.html#recover</a><p>In both cases an error value is returned to the package user.<p>In general, I think you should feel freer do abnormal things that suit your situation <i>within</i> a package as opposed to across API boundaries. The standard ways of doing things should still be preferred when it&#x27;s not too costly: the standards are what they are for a reason (even if the tradeoffs they represent are more appealing in some situations than others), it&#x27;s easier to read code that&#x27;s written in the way everyone&#x27;s used to, and standards help keep you from spending time bikeshedding unimportant details. But the surface area of your package is the first thing users will have to learn, and what you have to keep in your head whenever maintaining code that uses your package--that&#x27;s what it&#x27;s most important to make clean and normal-looking and consistent.<p>I know the post talks about using an unusual error-handling pattern in an API, and you can do that sometimes, too. Just trying to make a separate point--when you&#x27;re thinking about doing unusual stuff, keep in mind there&#x27;s a big difference between an unusual implementation and an unusual API.
评论 #8941927 未加载
nbevans超过 10 年前
This article is bizarre. I&#x27;ve read it twice expecting an actual solution to null checking basically everywhere and it doesn&#x27;t actually present one.<p>I&#x27;ll stick with discriminated unions in F#, thanks. Errors are values, correct. But it seems hardly any languages actually bother to grok the concept. If your language doesn&#x27;t provide type safety for ensuring that you capture all resulting conditions then it has failed its job.
评论 #8879014 未加载
Arnavion超过 10 年前
That second-last example with errWriter destroys safety AFAICT, since it requires the user to remember to check for ew.err to see if the whole sequence of operations actually succeeded or not.<p>Atleast the last example with flush returning the error made sense, since that&#x27;s an explicit &quot;commit&quot; operation that the user must call for correctness. However not every series of operations has a final commit operation, so this technique is unsafe in general.<p>(In fact even flush might be optional? I didn&#x27;t check the docs.)<p>Edit: The docs for flush say nothing, but the docs for the Writer interface itself ( <a href="http://golang.org/pkg/bufio/#Writer" rel="nofollow">http:&#x2F;&#x2F;golang.org&#x2F;pkg&#x2F;bufio&#x2F;#Writer</a> ) do:<p>&gt; After all data has been written, the client should call the Flush method to guarantee all data has been forwarded to the underlying io.Writer.
评论 #8879040 未加载
Animats超过 10 年前
Go lacks a construct comparable to &quot;with&quot; in Python, or WITH-OPEN-FILE in Lisp, to guarantee that things get closed out on scope exit. Go has &quot;defer&quot;, but that&#x27;s a clunky replacement. You have to explicitly write the &quot;defer&quot;, and it&#x27;s always function-scope, not construct-scope.<p>Most of the problems with exceptions in C++ come from the C++ memory model - allocation and deallocation in exceptions are usually painful, and care must be taken to clean up and unlock things. Go doesn&#x27;t have that problem - it&#x27;s garbage collected.<p>Go does have &quot;panic&quot;, which is Go&#x27;s answer to &quot;longjmp&quot;. People keep trying to use that as an exception mechanism, which is not a good thing.<p>Python seems to have the best track record with exceptions. Python&#x27;s usual problem with exceptions comes from library functions which, under some rare circumstance, raise an unexpected exception and take down the whole program. This is mostly a legacy problem due to the poor original design of Python&#x27;s exception hierarchy. You would like, for example, for everything that can possibly go wrong with an HTTP read or a network operation to be a subclass of EnvironmentError, as caused by an external event or data. That wasn&#x27;t the case originally. There are still problems in Python 2.7 with getting a ValueError because some low-level data item was bad UTF8 or something like that.<p>The trouble with the &quot;errors are values&quot; concept is that error details tend to get lost as errors propagate upward. This came up yesterday on HN in connection with network errors for GoGo&#x27;s airborne networking service. I once argued that D&#x27;s error type should have a &quot;why&quot; pointer slot, so that, when you passed an error upward, you could link the lower-level error to the higher level error. The error message printer would then list out the errors, yielding something like<p><pre><code> Unable to complete transaction because database update failed because database connection was lost because of network error &quot;Host down&quot;.</code></pre>
评论 #8878753 未加载
评论 #8878219 未加载
jimjimjim超过 10 年前
&quot;I was wrong about Gopher&#x27;s Law. The probability of mentioning generics in a #golang discussion starts at 1 and raises with time.&quot; - Dave Cheney.<p>Article posted 1 hour ago. Ctrl-F Generics = 3 matches
评论 #8878611 未加载
harryh超过 10 年前
If a function be advertised to return an error code in the event of difficulties, thou shalt check for that code, yea, even though the checks triple the size of thy code and produce aches in thy typing fingers, for if thou thinkest ``it cannot happen to me&#x27;&#x27;, the gods shall surely punish thee for thy arrogance.
评论 #8878459 未加载
chrisfarms超过 10 年前
One thing I love about the repetitive err pattern is that I find my self wrapping&#x2F;appending errors with useful context more frequently (rather than say catching an exception five frames further down and just appending &quot;mylib: oops&quot;).<p>I realise the later was just laziness on my part, but the constant err checking really does force good habits on me, which of course yields higher quality code that&#x27;s easier to reason about when something goes wrong.
评论 #8877818 未加载
thegeomaster超过 10 年前
Lua uses this to handle errors---I don&#x27;t find it really tedious. A common pattern is kind of the same---to return either one return value and nil and then an error message or an error code (Lua, like Go, has multiple returns) in case of errors. This results in code like<p><pre><code> let res, err = some_function() if res == nil then -- you can also check for err ~= nil like in Go -- return from this function with something, err contains more -- information end </code></pre> I think it&#x27;s very neat---you can also return nil, err from this function if you can&#x27;t deal with it and some code up the stack should be able to handle it in a meaningful manner. I don&#x27;t find it particularly tedious; i.e. not less tedious than writing try..catch blocks, and you can clearly see where are the function exit points, whereas with exceptions, any line can theoretically throw an exception. A missed error check can be troublesome, but generally using nil as a value further down in the function will hopefully trigger another error which might be then caught so you can spot the bug.<p>Lisp&#x27;s continuations are also IMHO a great mechanism---you catch the error and then you can tell the function down the stack what it should do: you can abort, or you can try fixing the error and continuing, or you can try again for some number of times and then report a new error if that fails. It&#x27;s more flexible than stuff I&#x27;ve heard of before.
评论 #8878556 未加载
artursapek超过 10 年前
I don&#x27;t know if I&#x27;m just ignorant, but I like that Go forces me to use real estate for dealing with errors. It&#x27;s the first language I&#x27;ve used where I give as much attention and design to the &quot;sad paths&quot; as I do to the &quot;happy path&quot;. And I&#x27;m very comfortable navigating through text files and folding blocks of code, so it doesn&#x27;t bother me if handling an error costs me a few lines.<p>Macros like try! might take up less space, but is that really a bottleneck for your work?
评论 #8877887 未加载
评论 #8878577 未加载
zzzcpan超过 10 年前
How about just using beloved tables instead? Maybe even with function literal to prepare some stuff, if you need to.<p><pre><code> for _, s := []string { p0[a:b], p0[c:d], &#x2F;&#x2F; ... } { _, err = fd.Write(s) if err != nil { return err } } </code></pre> As others already implied it could have been cleaner with exceptions, but I don&#x27;t think this is that one case where exceptions are particularly important. There is no recursion yet or anything like that.
h8liu超过 10 年前
I think explicitly handling error like go is a good thing. Error handling is a tricky thing that needs thought to get right and should be expressed clearly in the code. My only complaint is that `if e != nil { return e }` takes 3 lines of code. Could `gofmt` format that into one line with no line breaking, I am already pretty happy. If they could add a keyword like `ereturn` that only returns when the last parameter (must be error) is not nil, just as an alias, I would be totally satisfied.
tel超过 10 年前
Errors are absolutely values and that&#x27;s a great perspective for any language to take. Essentially, you can see an errorful computation as &quot;returning&quot; either a valid return value as expected or an error. In Go the quotes are eliminated: that is exactly what happens<p><pre><code> ret, err = fd.Write(...) </code></pre> Above we have two (initial) possibilities<p><pre><code> 1. The return was error-free, err = nil, and ret is an interesting return value. 2. The return was errorful, err != nil, err instead details the reason for error as an integer, and ret is nonsense. </code></pre> This is a little challenging because we must be sure to check err in the second case since otherwise ret is silently meaningless. This is a source of bugs called &quot;Boolean Blindness&quot;.<p>In a language with Sum types we&#x27;d represent this by saying that errorful computations adjoin an error type to the return type.<p><pre><code> Int ----&gt; Either Error Int </code></pre> What&#x27;s nice is that this automatically ensures the above invariant of err&#x2F;ret is held: a value of type Either Error Int is either &quot;Right i&quot; for some valid Int i or &quot;Left e&quot; for some error value e. The first thing we check is the handedness of the result and thus know the result of the computation.<p>Sum types like Either <i>solve</i> Boolean Blindness because we literally cannot get access to our return type Int unless it is valid: if we find that the result is &quot;Left e&quot; then all we have is an Error value.<p>(Ultimately what this boils down to is that product types like (ret, err) behave <i>very</i> differently from sum types and cannot replace them.)<p>If Go had generics we would also notice that the error-passing behavior of Either is naturally ignorant of the particular choice of types Error and ain&#x27;t. We could manipulate it meaningfully (and indeed write the functionality used in this article) for any and all values e and r as the type Either e r. Then upon use Either e r would specialize to Either Error Int.<p>It&#x27;s easy to argue that this is silly: we only need one error type and it is not difficult to replicate this code. I&#x27;ll argue for now that only having one error type is at best confusing and at worst a lie. Different processes and functions have different exceptional states. Coercing all of this <i>variation</i> in error meaning into the same error type ensures that misunderstandings and accidental unifications will occur.<p>Honestly, I really love Go for making sure that errors were values: it is an incredible step forward for making exceptional cases more reasonable. Unfortunately, Go does not seem expressive enough to build the machinery which makes this style of error handling all of reasonable, non-repetitive, and non-confusing.
mercurial超过 10 年前
&quot;Error are values&quot;, OK, but as a non-Go programmer, how do you handle idiomatically the most common case of wanting to bubble the error up the stack and turn it into something human-readable? Bonus if you do not break encapsulation. Double bonus if you get a stack trace out of it.
评论 #8878804 未加载
callesgg超过 10 年前
The handling of errors in go is weird if one only returns or breaks on errors. But it works fine if one does something on error, in almost every error check in my go code I have a log.Println with a human readable text like &quot;could not read from the application configuration file.&quot; much nicer to get errors like that than a stack dump and a message like could not read from file $filename.
al2o3cr超过 10 年前
&quot;There is one significant drawback to this approach, at least for some applications: there is no way to know how much of the processing completed before the error occurred. If that information is important, a more fine-grained approach is necessary.&quot;<p>A more fine-grained approach which is conveniently omitted from the article, BECAUSE IT&#x27;S SPAMMING `if err != nil` EVERY OTHER STATEMENT...
评论 #8879724 未加载
Terr_超过 10 年前
If you want to force the consumer to consider an error case and make <i>some</i> decision about it... Well, I actually like Java&#x27;s checked exceptions.<p>You&#x27;ve got to emit an error <i>somehow</i> and static checking ensures the consumer won&#x27;t accidentally ignore the case. (Deliberately, perhaps, but that&#x27;s a people-problem rather than a technology one.)
millstone超过 10 年前
I don&#x27;t get it. How is &quot;errors are values&quot; different from anything? In what language are errors not values?
评论 #8879738 未加载
rvirding超过 10 年前
Being an erlang programmer I am so used to programming for the correct case and not to check errors but let the process crash when you get an error or, rarely, use a try that I find this style of programming completely wrong. It is what we are trying to avoid. You will always miss cases.
评论 #8883367 未加载
lectrick超过 10 年前
Saying it&#x27;s so doesn&#x27;t make it true. You can declare that an error is simply a special value returned and expect people to check for that value all over the place, or you can declare that errors are (literally) &quot;exceptions&quot; and should therefore throw.
评论 #8879848 未加载
lazzlazzlazz超过 10 年前
Pike&#x27;s solution does not help very much, and only exposes how little work the Go compiler is actually doing to ensure your code is safe and sensible.