Hacker News new | ask | show | jobs
by jammycakes 2544 days ago
The problem with explicit error handling is that it's all too easy to get it wrong (by forgetting to check the return value) and when it does go wrong, it goes wrong silently, introducing a risk of leaving you with corrupt data. In production.

The beauty of exceptions, on the other hand, is that the default option is the safe one. Sure, forgetting to add error handling may leave you presenting a user with a stack trace, but at least you're not billing them for something that never gets delivered.

2 comments

>The beauty of exceptions, on the other hand, is that the default option is the safe one

Except, when it's not. Exceptions tend not to solve the problem, only make it subtly worse. The biggest wart on exceptions is the fact it introduces non-local control flow. All of the sudden any function you call can cause you jump out of your current function. In any situation where an unhandled error will corrupt your state, exceptions have that problem as well, on top of the fact that they are invisible.

An `err` aliased to `_` or shadowed can be found by a linter or a human reading the code. A function `foo()` causing your stack to blow up and possible corrupt any IO you are doing is worse. Therefore, the guys in Java-land discovered CheckedExceptions which were even more controversial, and arguably led to languages like Go and Rust dropping exceptions in general.

The problem with explicit error handling is that it's all too easy to get it wrong (by forgetting to check the return value) and when it does go wrong, it goes wrong silently, introducing a risk of leaving you with corrupt data. In production.

The problem with exception error handling is that it's all too easy to get it wrong (by forgetting to complete the handle code) and when it does go wrong, it goes wrong silently, introducing a risk of leaving you with corrupt data. In production.

In either case, error handling needs to be code reviewed. The best thing to do, is to make the right thing the easiest, minimal friction thing to do. Unfortunately, getting off the "happy path" is often a messy business. My suggestion is to explicitly implement a standard "developer scaffold" to be used when filling in the error handling during development, with penalties for not using it. This makes it easier to find where error handling needs to be fully fleshed out.

The beauty of exceptions, on the other hand, is that the default option is the safe one. Sure, forgetting to add error handling may leave you presenting a user with a stack trace, but at least you're not billing them for something that never gets delivered.

The entire reason why Unit Testing and Test First made it into Extreme Programming and Agile methods, is that it was way too easy for end-users to see error notifiers and stack traces in production in Smalltalk.

> The problem with exception error handling is that it's all too easy to get it wrong (by forgetting to complete the handle code) and when it does go wrong, it goes wrong silently, introducing a risk of leaving you with corrupt data. In production.

It's much more difficult to "forget completing the handling code" than it is to overwrite a golang error value and not handle it (which does happen in code bases). The default behavior of unhandled exceptions is to bubble up, unlike golang errors that can get silently dropped quite easily, and I've seen this is in large code bases. Unless you're writing

  try { ... } catch (..) { /* do nothing */ }
in which case you explicitly opt into doing nothing, and for which there are linters that catch this sort of behavior automatically.

On the other hand, even the simple

    func main() {
        fmt.Println("")
    }
doesn't handle errors.
in which case you explicitly opt into doing nothing, and for which there are linters that catch this sort of behavior automatically.

There are linters for golang. No reason why those sorts of tools and community norms shouldn't squash those sorts of behaviors. (As has happened for race conditions in golang!)

I used such linters, and they fail at certain things. The `fmt.Println("")` example still holds. Another example:

  a, err := foo()
  b, err := bar()
  c, err := baz()
err doesn't get handled in the first two cases, and the linter doesn't complain.

And race conditions in golang are very much possible, and cause issues in prod.

I used such linters, and they fail at certain things.

Of course. They're just a tool.

For the amount of concurrency done, golang is doing pretty good. I know of no other programming community which has such community standards on using race condition checking to the same extent as linting.

> Of course. They're just a tool.

Which is why it is strictly superior to use a language feature which does not have this issue to begin with (e.g. exceptions or actual compiler enforced error handling like Rust).

> I know of no other programming community which has such community standards on using race condition checking

I assume you're referring to the golang race detector. It's better than nothing, but then again, it's a tool and is not guaranteed to find all issues that may arise. Compare (again) with Rust, or with languages with proper immutable data structures like Scala and Java (e.g. https://immutables.github.io), not to mention https://openjdk.java.net/jeps/8208520 or https://clang.llvm.org/docs/ThreadSanitizer.html