Hacker News new | past | comments | ask | show | jobs | submit login

> A quick Code Search for rows.Close() on GitHub shows that pretty much nobody is explicitly checking the return value of rows.Close() in their Go code.

Seems to be a pattern in all languages. Plenty of C code ignoring the result of the close system calls, or Java code that just wraps exceptions from close calls in try/catch blocks that do nothing with the exception.




Even Rust, famous for caring about safety, does that: for instance, look at the documentation for File (https://doc.rust-lang.org/std/fs/struct.File.html), and you won't find any close() method, it's always automatically closed when the File goes out of scope (in its Drop implementation). Of course, this means that any errors reported by close() are simply ignored. The only way to get the errors from close() is to do a file.into_raw_fd() (which extracts the file descriptor and suppresses the Drop) followed by a FFI call to the libc close().

It gets even worse when you have a BufWriter (https://doc.rust-lang.org/std/io/struct.BufWriter.html) on top, since it can have buffered data which will be written when it goes out of scope (in its Drop implementation), once again ignoring all errors; you have to remember to do a flush() (or an into_inner() to recover the File, which also does a flush()) before it goes out of scope to get any write errors (but you still won't get any errors from closing the file).


Well, what should the code do there?

At that point, the status is uncertain, there's potentially a problem but IMHO there's nothing the code can do to reliably recover from it.


The code should at least log the error because it means there is something wrong somewhere, eventhough you can't do much about it it helps understand some other issues.

The only way to log the error is to handle it in Go.


The code should throw away the data it read from the connection just before it closed it, because there's a high chance of it being corrupt.


That's just crazy. How do you define "just before" ?


The last iteration before the current check. But I'd assume the foolproof way would be to return an error if the Close() returns an error, erroring out the entire read. I don't know if a partial read is ever a good thing.


What if it is a write?


That complicates things, but no different from seeing an error either way. The point is that checking for an error at point one is not enough, if you want to be sure there wasn't an error you need to check point 2 as well.

In the case of a write, if you seen the success message at point 1 you might not care about a failure at point 2, because you already know the write was a success, assuming your confirmation at point 1 was explicit and well formed.

In the case of read, say you read an email address, and received 'someone@somwhere.co'. The thing is, if the connection was cut you don't know if that address was supposed to be '.com', '.co.in' or '.co'. The only way you can tell in the current MySQL protocol is to check for an error on point 2 (the Close()). If you see an error, you know the read was incomplete, and you throw it away. If you don't check, you might have read the wrong (corrupt) value.


That sentence is very misleading. Nobody checks the Close() error because the right thing to do is to call Err() like with the text.Scanner.

I suspect the right github code search would reveal way less buggy code.

https://golang.org/pkg/database/sql/#Rows.Err


And here we see why the claim that golang forces users to handle errors is simply not true. Error handling in golang is just badly implemented.


Functions with a single return (err) are a problem but generally Go is better at forcing users to handle errors than some languages.

It's also true that error handling in Go is generally less sophisticated than some languages.

For developers who prefer more sophisticated tooling, Go might appear badly implemented. However that's a preference thing.

I hope you can see what I'm doing here; wild generalisations just end up starting flamewars which nobody wins because everyone is using a different yardstick to measure their "correctness".


I am not at all fond of the go aesthetic personally, but given that aesthetic they really do make the most of the trade-offs they've chosen, and all things considered, yeah, within go's style the way error handling works is actually damn good.


> Functions with a single return (err) are a problem but generally Go is better at forcing users to handle errors than some languages.

Which languages?


It's not a preference specifically because golang's error handling is error prone. The things that slide in golang code don't happen in languages with exceptions.


Go has exceptions too however they serve a different use case.

Sometimes an error is a problem that should be handled with granular logic and sometimes an error is something that should hard terminate that function and any parent functions until it is captured.

The problem with exceptions is that some languages don't surface that a particular function or API might raise an exception so you're often left having to capture all exceptions and having to write generic failure messages hoping that the exception is descriptive (often it isn't). Or worse yet, you fail to capture that exception and end up with your application hard terminating over something that isn't serious to the programs function.

This is why err is so widely used in Go; it enables granular logic, makes it clearer where errors are communicated and thus when they need to be handled; and enables developers to escape code or report errors in ways that are more meaningful to those who actually run the application and who might not have the ability to read the code.

By more useful error messages I mean something like this pseudo-code

    function writeLog(message string) {
        open(CREATE | APPEND, "/var/log/example.log")
        writeln(message)
        close("/var/log/example.log")
    }
If you had an exception in open(), writeln(), or close() then how do you report that to the user? What state has the function been left in? Do you have to capture the exception from each function or does some functions not generate an exception (close() probably wouldn't but the others could)?

Whereas (again pseudo-code)

    function writeLog(message string) error {
        err = open(CREATE | APPEND, "/var/log/example.log")
        if err != nil {
            return errors.New("Unable to open log: %s", err)
        }

        err = writeln(message)
        if err != nil {
            return errors.New("Log open but unable to write: %s", err)
        }

        close("/var/log/example.log")
        // doesn't return an error
    }
It's clear to the end user what specifically has failed, it's clear to the developer which functions can error and ok, the code is a little uglier for it but no more so than many of the other examples handling errors / exceptions (error handling is inherently ugly).

This is clearly a bonkers example made up on the spot and it wouldn't matter too much if there was one generic "unable to write to log" message. But the concept described above has proven very useful in larger applications I've written which have needed to handle errors nested several layers deep in a way that's meaningful for end users (as opposed to developers).

Now I'm not saying Go got it right nor that other languages haven't solved this in better ways and I'm certainly not trying to advocate that Go's error handling is "good". However it often feels like most of the complaints leveraged against Go's error handling don't really understand the point behind Go's design in the first place or have other missing pieces of information about what can and cannot be done in Go.


Regarding an exception approach -- at a business level, the user cares that the log hasn't been written. Only one exception is needed to report that (so long as the exception includes the cause).

The causative IO exception should indicate which operation failed (open or write), and -- for those with access to source -- should indicate the line number.

Encapsulation principles suggest that both the user & calling code should care only that the log was written successfully (or not), but not how or why it succeeded or failed. Any recovery in code (if possible, usually it's not) should be encapsulated within the method.

In most error cases there is no realistic or sound recovery (occasionally a generic retry might be valid). The main purpose of error handling is to stop successfully and report the problem in a diagnosable manner.

Some context on the purpose of error handling/exceptions in this article: http://literatejava.com/exceptions/checked-exceptions-javas-...


> Regarding an exception approach -- at a business level, the user cares that the log hasn't been written. Only one exception is needed to report that (so long as the exception includes the cause).

I thought I made it clear that example was intended to be more illustrative than literal, but obviously not. However there will clearly be occasions when exceptions are too blunt of a tool. Maybe think back to one of those occasions?

> The causative IO exception should indicate which operation failed (open or write), and -- for those with access to source -- should indicate the line number.

You're making a lot of assumptions there:

- that any imported libraries have well written error messages (sometimes they don't but they still the best at handling normal operation).

- that there is only one operation of a given type within a function (eg what if you're writing to two different logs? It would certainly help to wrap that error with which log you're writing a generic error for the whole function)

- that the person debugging the executable has access to the source code -- or even understands the language enough for the source to be of use. The target audience might not even be technical so having useful error messages that they can report back to support helps reduce the cost on your support team

> Encapsulation principles suggest that both the user & calling code should care only that the log was written successfully (or not), but not how or why it succeeded or failed.

Not entirely true. Code shouldn't care but humans who would need to rectify the fault definitely DO care. eg how often do people on HN comment about the usefulness of Rust's compiler messages? It's exactly the same principle you're now arguing against.

Also the log example is illustrative.

> Any recovery in code (if possible, usually it's not) should be encapsulated within the method.

The example I'd written does exactly that. So we're all good then ;)

> In most error cases there is no realistic or sound recovery (occasionally a generic retry might be valid). The main purpose of error handling is to stop successfully and report the problem in a diagnosable manner.

That's a rather large generalisation and often isn't true eg

- Some frameworks return EOF as an error state when reading past the end of a file (eg from pipes).

- If you're running a web service and someone's connection terminates while you're still writing to it, that would produce an error but you'd obviously need to recover otherwise your whole web service crashes.

Errors and exceptions are a pretty normal way of breaking control flow and aren't just used in edge cases where the application should be terminated in a controlled manor.

> Some context on the purpose of error handling/exceptions in this article: http://literatejava.com/exceptions/checked-exceptions-javas-.... .

I've been writing Java since v1 days. In fact I have 30 years of development experience in dozens of languages. Java does a lot of things better than Go but frankly I'd take Go's error system over Java's.

I think the biggest drawback of Go's error system is it's manual and verbose. Ironically that's also it's strength, as in that's why it works (as I described in my previous post). However from the perspective of developers looking from the outside in, err blocks just luck ugly. Which I also get. Sometimes it is a chore writing err blocks but they are also immensely useful in practice (and when isn't it tedious capturing errors?)


No.

Error handling in Go is very explicit since it doesn't alter the control flow of the program through an exception. I'm hesitant to agree this is just a bad implementation, because you will have to make decisions on error paths in any case. If you don't do that, you are just doing bad programming.

On the surface, it looks like you want to use exceptions or other control operators for handling this. But after having programmed for more than 20 years, I'm not too sure there is a good solution to this problem. Programs and operations in programs can fail. And you will have to think about these failures eventually.


The point is that Go doesn't force any developer to deal with errors. Its error system isn't better than exceptions. In fact Go doesn't really have an error system (aside from panics), Go errors are purely a convention and no mechanism around it is actually baked into the language.

It's exactly like the "good old" return codes in C. Nothing more, this isn't an error system at all. It's primitive. It's just that Go has multiple returns to make it a bit more bearable.

> Programs and operations in programs can fail.

Yes, and using return codes absolutely does not guarantee you will avoid panics at all. In fact you can use error codes in any other language if it pleases you, because that's not a language construct.

The whole "we have exceptions but we don't have exceptions so you shouldn't use panic/recover" dance is just stupid.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: