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

The point of verbosity in Go error handling is context. In Go, you rarely just propagate errors, you prepend them with context information:

    val, err := someOperation(arg)
    if err != nil {
        return nil, fmt.Errorf("some operation with arg %v: %v", arg, err)
    }
It really sucks when you're debugging an application, and the only information you have is "read failed" because you just propagated errors. Where did the read happen? In what context?

Go errors usually contain enough context that they're good enough to print to console in CLI applications. Docker does exactly that - you've seen one if you've ever tried executing it as a user that isn't in "docker" group:

    docker: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post http://%2Fvar%2Frun%2Fdocker.sock/v1.35/containers/create: dial unix /var/run/docker.sock: connect: permission denied. See 'docker run --help'.



For what it's worth, in Rust, this pretty much translates to

   some_operation(&arg)
      .with_context(|| format!("Some operation failed: {arg}"))?;
or, in most cases, the shorter

   some_operation(&arg)
      .context("Some operation failed")?;
If you forgot to call `with_context` and simply write

   some_operation(&arg)?;
the error is still propagated, it's just missing whatever context you wanted to add.

Whether that's better or worse than the Go snippet is something I'll let the reader decide.


If your error is 5 or 10 levels deep, do you prepend contextual information every time? External libraries typically have good error messages already, why do I have to prepend basically useless information in front of it?

Not to pick on any of these projects, but this pattern is way too common to not have a some sugar:

https://github.com/goodlang/good/blob/3780b8d17edf14988777d3...

https://github.com/kubernetes/kubernetes/blob/1020678366f741...

https://github.com/golang/go/blob/6cf6067d4eb20dfb3d31c0a8cc...


> If your error is 5 or 10 levels deep, do you prepend contextual information every time?

Yes, because each level is (hopefully) there for a reason, and provides a deeper context about where and why the error occurred.

If your contextual information is too repetitive and redundant, it may be the time to refactor your code and remove the unnecessary layers.


Wait that’s interesting and I haven’t formulated it this way.

It reminds me of A Philosophy of Software Design:

The utility of a module (or any form of encapsulation such as a function) is greater, the smaller the interface relative to the implementation. Shallow ve deep modules.

Error handling might be a proxy to determine the semantic depth of a module.

Another perspective: (real) errors typically occur when a program interacts with the outside world AKA side effects etc.

Perhaps it’s more useful to lift effects up instead of burying them. That will automatically put errors up the stack, where you actually want to handle them.


> Perhaps it’s more useful to lift effects up instead of burying them. That will automatically put errors up the stack, where you actually want to handle them.

This perspective also made me think of another advantage of Go error handling over traditional try/catch logging:

In other programming languages, when doing some asynchronous work in some background worker or whatever, exceptions become useless as error reporting mechanisms, because stack traces don't correspond to the logical flow of the program. I remember Java's Spring to be exceptionally painful in this regard.

In Go, since errors are values and you're expected to add context information whenever appropriate, you can shift and shuffle those errors between stacks, and still keep useful context information when you eventually read those errors.




Join us for AI Startup School this June 16-17 in San Francisco!

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

Search: