8 ms·
I've never really understood the "no easy way to handle errors" complaint. I'm not saying it is easy, nor that it isn't verbose (hell, it is super verbose!) but
by inkel 12y ago
I've never really understood the "no easy way to handle errors" complaint. I'm not saying it is easy, nor that it isn't verbose (hell, it is super verbose!) but I'm seriously asking: what would be an easy way to handle errors? I'm not asking about how to do it in Go, just how to do it in any programming language.
To me errors are very special constructs, is not a typical return value, as for example a method returning true or false whether the arguments are valid or not, so I expect errors to be something similar (at least semantically) as exceptions in other languages, and in that case, that is, return values that should be deal with in a certain special manner, so I don't expect to deal with them in an "real easy way"™, but in a very conscious and deliberated way.
- MCRed 12y agoIt's the verbosity that I'm talking about. In go I end up with %70 of my code being error handling or error related... with Elixir it's close to %0.01. I'm giving estimates here, of course, but they aren't off by much. With elixir, using try catch is rare, because it should never crash, and when it does, you lose a process and debug it. With go, every line that calls a function needs to check to see if there was an error. And handling that error takes at least another line or two. Unless there's a better way that I'm missing.
- inkel 12y agoOk, verbosity I agree. That doesn't have anything to do with being easy or not, I think. I might be wrong. Verbosity can be a pain. Even after reading Rob Pike's article on "Errors are values" [1], I agree with you that the solution is still verbose. Those numbers you gave, even when you said they're estimates, still seems like made up numbers. I've never used Elixir, so if you could point me to an example so I can be more informed, I'll be grateful :) but also I've read some Go programs (not many, I'm not an expert) and it didn't seem to me that 70% of the code is dedicated to error handling. Note that I'm not defending Go's position on error handling, I'm asking about error handling in general, what does people mean with "easy way to do it". [1]: http://blog.golang.org/errors-are-values http://blog.golang.org/errors-are-values
- engineerDave 12y agohere is a podcast where they cite error handling stats of C++ vs Erlang. http://mostlyerlang.com/2014/11/19/049-supervisors/ http://mostlyerlang.com/2014/11/19/049-supervisors/ starting at 28:45 although you might want to listen to the whole podcast
- muraiki 12y agoThe Erlang/Elixir way can be hard to appreciate because it's not just a very different way of handling errors, it's a completely different way to architect your application. The following tutorial only takes about 45 min to go through, but you'll get a taste for how error handling (across distributed process, at that) works in Elixir: https://howistart.org/posts/elixir/1 https://howistart.org/posts/elixir/1
- codygman 12y agoI wonder how long it will be until Rob Pike decides that "Handling error values should be enforced by the type system".
- waffle_ss 12y agoThere are better ways. The Erlang way would be to code for the happy path, and not try to anticipate most errors. If your process encounters an error, it will crash, the supervisor will restart it, and it will continue processing. Of course, you can choose to handle anticipated errors if you would like to be more user friendly (e.g. provide a user-friendly message if a file is missing), or if you want to log the error or something. But generally it's considered defensive programming and an antipattern to try and handle every error condition in an Erlang program like you have to do with e.g. Java exceptions. In languages with powerful type systems like Haskell, errors can be handled by using (for example) the Either[1] type. That way, you are forced to pattern match on the type (it will be a Right if successful or a Left if unsuccessful/errored), so it's not possible to "forget" to check for a null reference like you do in Go (i.e. checking err != nil all over the place). [1]: http://hackage.haskell.org/package/category-extras-0.53.4/docs/Control-Monad-Either.html http://hackage.haskell.org/package/category-extras-0.53.4/do...
- inkel 12y agoThank you for the explanation. I think it makes sense somehow, however, and high likely because I've never dealt with errors in that way, the _encounter error, crash, restart, continue_ workflow seems a bit awful to me :) The Haskell solution seems really nice. I've only used Haskell for pet projects and it's one of my favorites languages (though I don't have any proficiency with it,) so thank you for teaching me something else about it.
- rdtsc 12y ago> _encounter error, crash, restart, continue_ workflow seems a bit awful to me :) Granted it is better and less awful than error, everything stops, get calls from customer at 4am, fix, continue cycle ... ;-) It is a bit different. Learn about it some more and you'll love it. The reason you can't easily do it in other languages is that errors are not isolated. That crash that happened, it might have left some global variable some place in a strange un-expected state. That is why Erlang/Elixir processes do not share memory. That way it lets you feel a bit safer about crash-restart parts of the system.
- 12y ago
- rdtsc 12y ago> "real easy way"™, but in a very conscious and deliberated way. If your don't deal with errors in a "real easy way"™, your customers will deal with errors in "a harder way"™ and they'll call you at 4am about it too. You can't avoid dealing with errors basically. > deal with in a certain special manner, Ok is this "manner" easy or hard to handle. You can also look at it this ways, errors will happen. Some will be predictable (as in you expected a "connection refused" exception or error value. Some will be unpredictable ("this library I downloaded return -5 and just silently doesn't send the value" or throws some un-expected exception). There are 2 good ways to deal with errors: 1) Use a stronger/better type system or other static checkers. Something like Haskell or Rust, hoping the type checker will prevent some class of errors. You hope to avoid errors ahead of time here. This approach is taken for some critical systems. Navigation, medical equipment, military hardware etc. 2) Isolate errors when they happen. Errors will happen at runtime, even in a typed-checked language. How do you want to deal with them? You can deal with them in an easy way -- isolate the component that fails and degrade the system slightly without completely stopping service. Or even better restart just that one sub-components. Or, have everything come crashing down with an exception or tracelog and get a call from a customer. Erlang and Elixir (and other BEAM VM) languages do this best. You can to a certain way replicate this with OS processes, but Erlang has that built-in. The reason for that is concurrency primitives (processes in Erlang, others have tasks/goroutines/threads) do not share a global heap. So that lets you both isolate the errors they happen to one component, and lets you separate main code from error handling code (you can have supervisors that watch other processes and if they crash they can deal with it better).
- Retra 12y ago>To me errors are very special constructs Special how? Surely they are not special in a way that can't be captured by a type system. You say they should be handled in a special/deliberate way, but that's what it means for something to have a distinct type: it can't be handled like everything else. You just define special error-handling functions and they will work on errors and nothing else, and nothing else will work on errors. As far as syntax goes, the I feel the big issue is about deferring error handling. You don't always want to mix error handling code with your algorithm logic, so you need to defer. We also talk about 'errors' so generally that we never bother to qualify which kinds of errors should be deferred or not in a way that is encapsulated in the type system. Or what kind of deferring should be available -- return errors or just move them later in the same scope? (Java's checked exceptions are a good example of attempting to do this, but it doesn't work out in practice. Programmers don't think like programs do: deferred error handling isn't the same as deferred error-handler writing, and both are needed in different ways.) From what I can tell about error handling in Go, it isn't really a step forward. (And we really want a step forward.) Go just kind of falls back on "this was the last thing that worked, and making significant progress is probably too hard, so we won't bother trying." That's the impression I get, at least. I don't actually do any Go programming, but I'm not inspired by its approach, either.