I've seen this kinds of stuff in PHP database APIs, and I've seen how much smaller and more readable the codebase becomes with proper use of exceptions. This is a huge downside of Go.
Conversely, this is a huge upside of Go. I enjoy the explicit error handling. It's a very conscious decision. There is panic/recover style "exceptions" but that's for truly exception states and not just error states.
It seems a rather bad example anyway, not sure why I'd prefer:
Exactly. It horrifies me that there are still programmers who don't understand this after so many years of OOP advocacy.
The benefits of a properly designed (not Java-like, but C# or PHP like) exception system stems from the fact that in the wast majority of cases you don't need to catch exceptions right away. Most exceptions can be handled in a few centralized locations higher up the stack. If you have 10 calls to ListenAndServe, this means having 10 times less exception handling code.
Moreover, when you design a library and the users actually care about errors, exceptions save you from needing to pass your error codes up the call chain from deeply nested methods.
ListenAndServe blocks forever. If you have 10 calls to ListenAndServe, it's implied that you're in 10 different, concurrently running processes (goroutines). A panic, in Go, does not cross a goroutine boundary. If, in imaginary parallel universe Go with exceptions, exceptions did cross goroutine boundaries, then one goroutine could crash another goroutine.
The point of Go is to be able to sensibly write stable, concurrent programs with compile-time safety. Exception-based error handling would be a mess with Go's concurrency model.
In practice, you would only call ListenAndServe 10 times if you were listening on 10 different ports, because you had 10 different projects on the same server. That's a perfectly realistic situation. Now consider that one site has an error and throws an exception. Should all the other sites go down?
Error handling in Go is admittedly tedious, but when you work with the language for a bit you'll start to understand why they chose to design it in that manner.
The whole point is, it wouldn't make sense for them to cross goroutine boundaries, but the pleasure of Go is the ease with which you can use goroutines. Thus, in a lot of places where people would lazily use exceptions to aggregate potential errors, you would be unable to because the exception is occuring in a goroutine.
Which is why there are other nice ways of communicating such states in go that go hand in hand with handling errors at their occurrence. If you find an error in a goroutine, you can signal that on a channel, return and wait for a new goroutine to be restarted in place of the errored one.
So because you can't think of an alternative, there should be no way of handling a scenario where, say, maybe that port is already in use?
Again, the explicitness is by design. My "handle" code would be checking the failure and acting on it: trying another port, logging a message, sending an email telling me my server isn't starting.
I doubt the existence of a real world app that really fires up an HTTP server and blindly dies without so much as logging to indicate failure.
> So because you can't think of an alternative, there should be no way of handling a scenario
And more strawmanning, great. Of course there is a way to handle failure: catch the bloody exception. Which is completely unlike what the original code does, hence your original comment being a (not very original) strawman.
But yes, since ListenAndServe is normally used to block before main() returns, you could let it fail and terminate. (But you'd probably want to exit with a non-zero value, at least I would).
Go has committed the same faux-pas as C. It is true it won't make a difference for the truly diligent programmer, but not everyone is, and even the best can be off at 3am.
The simple proof that Go's approach is wrong is to look for all the Hello World examples. I have yet to find one that looks at the error return of the println, just like none look at the return of C's printf.
Your example is because of Java's use of checked exceptions. Exceptions are fine - checked exceptions are stupid and cause a different class of programming errors.
The simple proof that Go's approach is wrong is to look for all the Hello World examples. I have yet to find one that looks at the error return of the println, just like none look at the return of C's printf.
You're also not meant to use those, they're generally discouraged.
rogerbinns is right, fmt.Println() does indeed return an error value. I still don't understand the argument. Println() can fail. That's why it is able to return an error.
You can choose to ignore it, that's fine. If it's something that you're worried about failing, then it's worth the "effort" to handle the error where and when it happens.
I guess ultimately, all of this stuff is such a non-issue. It's a cliched response to a lot of Go criticism, but acutally write something in it. Armchair critics would have you believe that exceptions and generics missing is the end of the world. Number one, it's not. Number two, these simplifications exist for a reason. One of Go's guiding principles is minimizing magic that is not visible or explicit. (That's why new/make exist, no exceptions, no generics). It eases development speed, code clarity and compile times.
A canonical hello world program should have a non-success exit code if the "hello world" printing failed. This will happen automatically with exceptions and similar mechanisms.
The problem with Go's error approach is that you have to handle it where it happens. You can't delegate the error handling to a function 5 levels back in the call chain. So you end up having to write boilerplate code around every single call and intermediary layer, or just not be diligent, and that lack of diligence starts with hello world.
It is the boilerplate code I object to, and exceptions are just one pre-existing solution. For example Go could recognise code patterns and automatically run code that returns errors to the caller if not otherwise handled or marked as ignored, although this is a certain amount of magic.
Since there is no code for ignored errors, that means there is no code test coverage for them. For example coverage on hello world will always be 100% even though the case of println failing didn't happen. Multiply this by a larger program and there be gremlins.
But how do you handle the error? Say the method returned successfully until the defer, do you then 'unreturn' the success return value and set the error return value? Return both a success and error value? Or say it returned failure, do you trash that value and replace it with the s.Close() error? Create some ad hoc meta error that contains both?
Error handling in golang is a total mess all by itself... then you throw panic/recover into the mix and it just gets worse.
EDIT: forgot hacker news readers get offended by "Google Go" so changed to golang.
Presumably code affected by an error state is also affected by a success state. The reasonable place to take action based on this result is directly in the async function, or otherwise in some callback.
That is incorrect. session.Close() doesn't return an error. Still, there are cases where Close() methods return an error, so let's pretend it does.
You're in the mess you describe above because you insist on making the wrong choice up front, which is to use defer for something which is non-trivial. If your error handling is so important, why would you bury it in an anonymous function? That's a bad decision in any language. Write it out explicitly. People will notice and give it due care because if it weren't special, you'd use defer.
Look at sql.go in the standard library: http://golang.org/src/pkg/database/sql/sql.go?s=5813:5840#L2.... You can see that the error handling in such as putConn() and Close() do not use defer because it's the wrong choice. Conversely, if you grep the codebase for defer, there are plenty of places where it is useful.
The 'defer file.Close()' ignoring errors is used throughout the examples for golang so I assumed it was the same here. My bad.
you insist on making the wrong choice up front, which is to use defer for something which is non-trivial.
Closing a file is non-trivial? So you have multiple returns in the function and you also need to close the file... better repeat it several times instead of using 'defer'. I'm not sure what the point of 'defer' is if that is the case.
Also the code you linked silently ignores the first N-1 errors, only returning the last one. Maybe in this case it is acceptable, but what kind of error does it return? Could the ignored errors be more relevant than the final one? Who knows.
I was just trying to argue in good faith by addressing the spirit of your criticism, not the letter of it. As I said, go and look at the prevailing usage of defer. Closing a file is trivial most of the time.
When closing something (a file, net connection, db connection) you're really worried about, that's when you ought to think twice about using defer. That's really all I'm saying.
I'm not sure what's wrong with multiple returns, as you're no worse off than if you were using exceptions. At least return is a standard, predictable mechanism which behaves the same regardless of what codebase you're in: exit the current function. You don't have to guess what file you need to look at next; control reverts to the caller.
And, as far as the example code is concerned, it is completely orthogonal to exceptions vs. error codes. You still have to prioritize what's catastrophic enough to abort and notify the caller, and what isn't. The author decided those errors weren't important. So what's the big deal?
Not surprisingly, I completely disagree. While this individual line of code may seem particularly offensive, it was written that way to be extremely clear and minimize dependencies. A more structured web app would be doing much more with the error depending on the situation. I'm glad that the code to handle those errors would be right around the logical point it should be: where I'm performing the action.
Many of the handlers have some boiler plate error checking on some actions that could fail. I agree those lines of code are repetitive and aesthetically displeasing, but as I alluded to in the article, a more filled in app could define a handler type that returns an error instead of being expected it to handle it within. The http.Error calls would change to returns, but that's a good thing: a quick scan allows me to see when the function can return to the caller. I don't have to inspect every line of code and remember details about which calls throw exceptions or not. Nothing is hidden, and the code does exactly what it says on the page.
Give me a slightly more verbose but clear code base than a terse one with spooky action at a distance any day.
That was a very nicely written article - thanks! I don't know Go (well, I am good playing the game, but I don't yet use the programming language :-) and I especially liked the way the MongoDB client code worked.
This is an excellent article (or I could be particularly stupid). I have been playing with webapps using Go for about 6 months and this shows a much more intelligent use of templates than the naive methods that I have been using.
Missing generics -- it doesn't destroy type safety, it just forces the developer to write boilerplate variations of every method.
The official stance on that issue is: yeah, but it's better than the hassle of supporting generics (compiler effort, object code size/slowness, and excessive high-level abstraction (a bad thing in the Go world).
I have a foot on both sides: I miss generics when I'm writing one-off low-scalability code, but it's nice to have the speed of low-level Go code, and perversely fun to actually spend some of my coding time writing all those low-level sorting and array-building routines that are the meat of programmer interviews.
Now, when a candidate asks "When will I need to write a method to sort the keys of a map?", I can answer "Every time you use a map[string]T for a novel T!"
I hate novel T now. (<-- This is also a design feature of Go, the passive pressure to not use large hierarchical types, and try to do your work with just maps and slices of primitives)
Most of the time, a combination of interfaces and slices (read: souped up arrays) is enough. In practice, when your arrays don't suck like they do in C, C++, or Java, you can get a lot of work done with them.
It's actually clearer as to what's going on since slices are a language primitive. They are simple and extremely transparent. This is absolutely not the case with Java's collections library or the C++ STL.
It wouldn't be a trade-off if there weren't situations where generics are really useful. But this rings more like an armchair criticism of Go than a criticism from someone who's written some Go programs. For my part, I wish languages like Java were less hostile to arrays.
Arrays and slices really have nothing to do with generic programming. Java has poor generic support (type erasure) and C++ has poor syntax and a bad design for iterators. Languages with decent generics support have none of these problems.
If this is 'armchair criticism' why do Go's designers say they are considering adding generics support?
39 comments
[ 3.4 ms ] story [ 57.4 ms ] threadIt seems a rather bad example anyway, not sure why I'd prefer:
over the Go alternative.Not sure why you added a bunch of lines which end up doing the exact opposite of what the original code does.
The benefits of a properly designed (not Java-like, but C# or PHP like) exception system stems from the fact that in the wast majority of cases you don't need to catch exceptions right away. Most exceptions can be handled in a few centralized locations higher up the stack. If you have 10 calls to ListenAndServe, this means having 10 times less exception handling code.
Moreover, when you design a library and the users actually care about errors, exceptions save you from needing to pass your error codes up the call chain from deeply nested methods.
ListenAndServe blocks forever. If you have 10 calls to ListenAndServe, it's implied that you're in 10 different, concurrently running processes (goroutines). A panic, in Go, does not cross a goroutine boundary. If, in imaginary parallel universe Go with exceptions, exceptions did cross goroutine boundaries, then one goroutine could crash another goroutine.
The point of Go is to be able to sensibly write stable, concurrent programs with compile-time safety. Exception-based error handling would be a mess with Go's concurrency model.
In practice, you would only call ListenAndServe 10 times if you were listening on 10 different ports, because you had 10 different projects on the same server. That's a perfectly realistic situation. Now consider that one site has an error and throws an exception. Should all the other sites go down?
Error handling in Go is admittedly tedious, but when you work with the language for a bit you'll start to understand why they chose to design it in that manner.
Which is why there are other nice ways of communicating such states in go that go hand in hand with handling errors at their occurrence. If you find an error in a goroutine, you can signal that on a channel, return and wait for a new goroutine to be restarted in place of the errored one.
Again, the explicitness is by design. My "handle" code would be checking the failure and acting on it: trying another port, logging a message, sending an email telling me my server isn't starting.
I doubt the existence of a real world app that really fires up an HTTP server and blindly dies without so much as logging to indicate failure.
And more strawmanning, great. Of course there is a way to handle failure: catch the bloody exception. Which is completely unlike what the original code does, hence your original comment being a (not very original) strawman.
You could do:
But why would you? Just omit it entirely. The only time you need to explicitly ignore errors is in multiple returns.consider: http://play.golang.org/p/o8vLQQmRvp
which would be fixed like: http://play.golang.org/p/DH0j1cwRqW
But yes, since ListenAndServe is normally used to block before main() returns, you could let it fail and terminate. (But you'd probably want to exit with a non-zero value, at least I would).
The simple proof that Go's approach is wrong is to look for all the Hello World examples. I have yet to find one that looks at the error return of the println, just like none look at the return of C's printf.
Your example is because of Java's use of checked exceptions. Exceptions are fine - checked exceptions are stupid and cause a different class of programming errors.
If printf or equivalent in a hello world program can't print to stdout, what should it do instead of crashing? Send an email?
I don't think I've ever seen a hello world for a language with exceptions (checked or not) that actually handled an exception from printf either.
The builtin println does not return a value. As stated in the language specification: http://golang.org/ref/spec#Bootstrapping
rogerbinns is right, fmt.Println() does indeed return an error value. I still don't understand the argument. Println() can fail. That's why it is able to return an error.
You can choose to ignore it, that's fine. If it's something that you're worried about failing, then it's worth the "effort" to handle the error where and when it happens.
I guess ultimately, all of this stuff is such a non-issue. It's a cliched response to a lot of Go criticism, but acutally write something in it. Armchair critics would have you believe that exceptions and generics missing is the end of the world. Number one, it's not. Number two, these simplifications exist for a reason. One of Go's guiding principles is minimizing magic that is not visible or explicit. (That's why new/make exist, no exceptions, no generics). It eases development speed, code clarity and compile times.
A canonical hello world program should have a non-success exit code if the "hello world" printing failed. This will happen automatically with exceptions and similar mechanisms.
The problem with Go's error approach is that you have to handle it where it happens. You can't delegate the error handling to a function 5 levels back in the call chain. So you end up having to write boilerplate code around every single call and intermediary layer, or just not be diligent, and that lack of diligence starts with hello world.
It is the boilerplate code I object to, and exceptions are just one pre-existing solution. For example Go could recognise code patterns and automatically run code that returns errors to the caller if not otherwise handled or marked as ignored, although this is a certain amount of magic.
Since there is no code for ignored errors, that means there is no code test coverage for them. For example coverage on hello world will always be 100% even though the case of println failing didn't happen. Multiply this by a larger program and there be gremlins.
Error handling in golang is a total mess all by itself... then you throw panic/recover into the mix and it just gets worse.
EDIT: forgot hacker news readers get offended by "Google Go" so changed to golang.
You're in the mess you describe above because you insist on making the wrong choice up front, which is to use defer for something which is non-trivial. If your error handling is so important, why would you bury it in an anonymous function? That's a bad decision in any language. Write it out explicitly. People will notice and give it due care because if it weren't special, you'd use defer.
Look at sql.go in the standard library: http://golang.org/src/pkg/database/sql/sql.go?s=5813:5840#L2.... You can see that the error handling in such as putConn() and Close() do not use defer because it's the wrong choice. Conversely, if you grep the codebase for defer, there are plenty of places where it is useful.
you insist on making the wrong choice up front, which is to use defer for something which is non-trivial.
Closing a file is non-trivial? So you have multiple returns in the function and you also need to close the file... better repeat it several times instead of using 'defer'. I'm not sure what the point of 'defer' is if that is the case.
Also the code you linked silently ignores the first N-1 errors, only returning the last one. Maybe in this case it is acceptable, but what kind of error does it return? Could the ignored errors be more relevant than the final one? Who knows.
When closing something (a file, net connection, db connection) you're really worried about, that's when you ought to think twice about using defer. That's really all I'm saying.
I'm not sure what's wrong with multiple returns, as you're no worse off than if you were using exceptions. At least return is a standard, predictable mechanism which behaves the same regardless of what codebase you're in: exit the current function. You don't have to guess what file you need to look at next; control reverts to the caller.
And, as far as the example code is concerned, it is completely orthogonal to exceptions vs. error codes. You still have to prioritize what's catastrophic enough to abort and notify the caller, and what isn't. The author decided those errors weren't important. So what's the big deal?
Many of the handlers have some boiler plate error checking on some actions that could fail. I agree those lines of code are repetitive and aesthetically displeasing, but as I alluded to in the article, a more filled in app could define a handler type that returns an error instead of being expected it to handle it within. The http.Error calls would change to returns, but that's a good thing: a quick scan allows me to see when the function can return to the caller. I don't have to inspect every line of code and remember details about which calls throw exceptions or not. Nothing is hidden, and the code does exactly what it says on the page.
Give me a slightly more verbose but clear code base than a terse one with spooky action at a distance any day.
P.S. in my day time job i work with ruby.
The official stance on that issue is: yeah, but it's better than the hassle of supporting generics (compiler effort, object code size/slowness, and excessive high-level abstraction (a bad thing in the Go world).
I have a foot on both sides: I miss generics when I'm writing one-off low-scalability code, but it's nice to have the speed of low-level Go code, and perversely fun to actually spend some of my coding time writing all those low-level sorting and array-building routines that are the meat of programmer interviews.
Now, when a candidate asks "When will I need to write a method to sort the keys of a map?", I can answer "Every time you use a map[string]T for a novel T!"
I hate novel T now. (<-- This is also a design feature of Go, the passive pressure to not use large hierarchical types, and try to do your work with just maps and slices of primitives)
It's actually clearer as to what's going on since slices are a language primitive. They are simple and extremely transparent. This is absolutely not the case with Java's collections library or the C++ STL.
It wouldn't be a trade-off if there weren't situations where generics are really useful. But this rings more like an armchair criticism of Go than a criticism from someone who's written some Go programs. For my part, I wish languages like Java were less hostile to arrays.
If this is 'armchair criticism' why do Go's designers say they are considering adding generics support?