FotoBlogr is the first toy I wrote using
Go. As a part of FotoBlogr, I also wrote a
Flickr client library, an
App Engine helper library, (unpublished) Blogger client, and Picasa client, so I got to see some idiomatic Go. One thing I really missed when writing Go was exceptions. Many things that are one-liners in languages with exception support (Java, Python, etc.) become 3 or 4 lines in Go. Let’s take parsing a string as an integer for example. In Java, you’d write:
int n = Integer.parseInt(str);
and you’re done. If you expect this conversion to fail, you’d catch
NumberFormatException
, but it’s not mandatory. Compare this to equivalent Go code:
n, err := strconv.Atoi(str)
Of course, you can choose to ignore the error by using an
underscore in the place of
err
, but it still won’t be as expressive like in Java. For instance, you cannot write
return math.Sqrt(strconv.Atoi(str))
. You must write that in two different statements:
n, _ := strconv.Atoi(str)
return math.Sqrt(n)
Of course, this is wrong, and it should actually be something like:
n, err := strconv.Atoi(str)
if err != nil {
// Handle this error.
}
return math.Sqrt(n)
It was
very annoying to get used to writing code this way... almost as if writing dumb code.
Fast forward a few months. I had moved to a C++ project where the style guide prohibits using exceptions. Existing code in this project ignored errors in several places, but when I was writing new code I always made sure I did something about errors... at the minimum log it and proceed. I didn’t have to make a conscious effort to do that; writing Go code had trained me to naturally think about error handling. It felt
awkward to not check for errors, in fact.
Now, I am fixing a bug in a Java project. This is an old project with design decisions independently made by hundreds of engineer over several years. The change itself is fairly simple: instead of simply sending a request to an RPC server, now we have to first check whether it’s okay to send that request. If this query fails, we’d show an error message to the user. But the complication is this: I’m changing a void method that does not throw any exceptions. (This method in effect promises all its callers that it can never fail!) This method is being called by half a dozen classes, which are being called a few others, and so on. Unless I touch a large number of files to handle the new error, I can’t get the code to compile.
I was discussing this issue with a teammate and what he said was remarkable. He said, “Every method in every class should throw a
checked exception irrespective of whether that method can fail or not. If it cannot fail today, maybe it can tomorrow.” He didn’t
literally mean that, of course. What he meant was we weren’t using checked exceptions as extensively as we should have. If this method I am changing now threw
some checked exception, I could simply use that in my client code without scratching my head too much. This is
precisely what Go does. Almost all functions return an error status. You can choose to ignore it, but when someone new to the codebase reads it, it’s obvious you’re ignoring an error. I’m starting to think that Go’s way of error-handling is a much superior way than Java’s and C++’s.
SIDENOTE: The easy and obvious fix is to throw a
RuntimeException
and catch it in only the places I like to change now. But that’s a really wrong way to fix a problem like this. First of all,
RuntimeException
s are not meant for cases like this. They are for things like division by zero, dereferencing a null pointer, etc. Things that can happen in
any code and thus are impractical to handle specifically. Since they are rare and usually very serious, they are allowed to propagate through deep call stacks. Defining application errors, input errors, etc. as
RuntimeException
can lead to severe debugging issues over time. You really don’t want that.