top | item 37678410

(no title)

blindseer | 2 years ago

Sure, but this code propagates the errors and that has the same problem:

    err := db.Get(&latLong, "SELECT lat, long FROM cities WHERE name = $1", name)
    if err == nil {
     return latLong, nil
    }

    latLong, err = fetchLatLong(name)
    if err != nil {
     return nil, err
    }

    err = insertCity(db, name, *latLong)
    if err != nil {
     return nil, err
    }
In Rust propagating errors is a lot more succinct and easy to do. It is usually what you want to do as well (you can think of Python and C++ exceptions as essentially propagating errors). The special case can be handled explicitly. In Go, you have to handle everything explicitly, and if you don't you can fail catastrophically.

I guess it comes down to what features the language provides that makes it easy to do "the right thing" (where "the right thing" may depend on where your values lie; for example, I value correctness, readability of domain logic, easy debugging etc). And in my opinion, it's easy to do what I consider bad software engineering in languages like Go.

discuss

order

bheadmaster|2 years ago

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'.

Yoric|2 years ago

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.

blindseer|2 years ago

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...