Quick and Clean in Go
shadynasty.biz
shadynasty.biz
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
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.It seems a rather bad example anyway, not sure why I'd prefer:
try {
http.ListenAndServe(":8080", nil);
} catch(ServeException se) {
// handle se
}
over the Go alternative. http.ListenAndServe(":8080", nil)
the uncaught exception would kill the task because, really, what is your http server going to do aside from exit if it can't serve?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.
_ := http.ListenAndServe(":8080", nil)
?You could do:
_ = http.ListenAndServe(*port, nil)
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.
# /bin/echo "Hello World" >&-
/bin/echo: write error: Bad file descriptor
You get this kind of thing for free with exceptions.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.
No generics actually harms code clarity and destroys type safety.
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?
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.
defer s.Close()
I assume it should be: defer func() {
err := s.Close();
// handle error
}();
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.
func ContrivedExample() (file *os.File, err error) {
file, err = os.Open("something.txt")
defer func() {
if err = file.Close(); err != nil {
file = nil
}
}()
return
}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.