Discussion: reduce error handling boilerplate using?

I’m sure many of you have seen this, but there’s an interesting proposal here:

A change to one of Go’s most hotly-debated subjects: error handling. My only concern with it is: it might muddy the waters for those of us who like go’s current error handling (now we have MORE ways of doing things) while not really solving what people who criticize it really want (exceptions that are magically handled somewhere like middleware).

I do see benefit in cases where you have many funcs that might return errors such as:

func DoLotsOfStuff() error {
	mightReturnError1() ?
	mightReturnError2() ?
	mightReturnError3() ?
	mightReturnError4() ?
	return nil
}

But I don’t really see too much benefit in this:

// New way:
r := SomeFunction() ? {
	return fmt.Errorf("something failed: %v", err)
}
// ... vs old equivalent:
r, err := SomeFunction()
if err != nil {
	return fmt.Errorf("something failed: %v", err)
}

Anyway, this is potentially one of the biggest language changes since generics were introduced. What do you all think about it?

1 Like

For me, generics were desired and welcome; iterators were unexpected, meh but useful; and most other changes were barely visible but QOL improvements nonetheless.

I just plain don’t like this one. The original idea in Go was to minimize syntactic sugar and be explicit about things. I hope they don’t do it, just like they decided against the ternary ?: operator. _ and iota are enough magic for me (I don’t get the hate towards iota btw).

There are other areas where the Go team’s energy would be better spent IMO:

  • Figuring out enums or, if the POC exposes fundamental issues, make a final decision against them.
  • Better syntax for iterators (but still in true Go style, whatever that might mean here).
  • Improving the standard library, for example addressing the warts in time.
  • Tooling improvements or stabilizing stuff in the /x/ libraries.
1 Like

I hear your points. I like how error handing works in Go and I’m iffy about this change. It seems like the community is pretty split as well. I appreciated generics and also the updates to routing in net/http. That was a QOL improvement that actually helped my day to day work.

Anyway, I can see why they are doing this. As mentioned in the discussion, error handling is the #1 most complained about thing in Go. But this feels like somebody learning Rust and complaining about the borrow checker; when the borrow checker is like the POINT of Rust.

2 Likes

I am against the change. Error handling is really that important, the developer has to decide what to do in every case. The freedom to use _ for an error is already there. :wink:

Also it is very easy to write a multierror struct to collect errors and handling them later. Maybe it is just a better idea to move this into the stdlib so people share the same multierror struct.

What I have spotted in many projects is that they sometimes are getting over engineered by adding features that are not really needed. It feels like this often happens simply because we humans are not OK with the feeling to stay still. Or they get too much money and people feel the need to do something.

One example I can mention, where that happened, is react and it’s routers that can be very nice :ok_hand: to use if you avoid a lot of it’s newer features, just stay simple. Do not always use the newest bloaty over engineered stuff and the result is still blazingly fast.
But you have to know what to use and what 90% of the features you can just skip and be fine.

The outcome of this over engineering is:
That many people do not like react, because it feels clumsy for them. They are correct, I mean you build menus, come on.
React is harder to learn.
There are full time react developers, who know all that stuff that is not really needed.

2 Likes

Good to see you around, Karl.

Agreed. And it will always come back to haunt you!

Yeah - this is something I’ve thought about a lot. Like - when projects don’t have a lot of recent commits people view them as “dead”. But what about projects where you just built out all the features you ever need and don’t need anything new?

Engineers by nature like to over-engineer things. I think Go, to date, has done a really good job of being slow to add new features. I became a gopher by way of .NET development; and with changes to things like null safety, .NET code is completely different from the code I was writing in years prior. Suddenly I’m seeing keywords like required and need to look them up, etc. Also - if you want to learn to love Go’s zero values even more, ship a .NET or Dart project!

This is my biggest fear I guess. Part of the appeal of Go is that it is very easy to learn. When I built my first Go project (with a few caveats like package management because this was before Go Modules) I was pretty much productive on day 1. I don’t think the ? operator would add much cognitive load to new gophers, but it is one more thing to learn.

If you haven’t, check out preact. It’s a drop-in replacement for react without the bloat! And if you want routing, it comes with a very simple (optional) router.

1 Like

I don’t like the proposals like ? sign. I prefer errors handling in go as it is and have no hard feelings about verbosity. Ppl can simply setup their IDE for template code. Imho, those who are constantly asking for something to fast-throw an error out of a function, simply missing the point how powerful go’s errors are. When I use 3d-parties and run into an error, I want an explanation where and why it may occur. Not a simple ErrOfMyLibrary = errors.New("some random and not useful text error") for everything. I’d suggest we need to learn how to properly wrap out errors with useful information, not how to reduce a number of keystrokes we do during coding. Even if devs will rework the way we handle errors now, there will be pros and cons. It’s impossible to fulfill desires of everyone.

2 Likes

Imho, if it was possible to use already existing one line error check without annotating type of other than error values, it could be a progress already, something like:

// var val type <- avoid this without shadowing.

if val, err := doSomething(); err != nil {
    // Handle or return.
}

Yeah - scoping val to an else block is the thing I don’t like about doing one-liners when doSomething() returns more than just an error. Clearly that’s a large part of what this proposal is trying to address.

This is really the essence of my objection. This proposal gives prominence to the happy path, whereas one of Go’s strengths is forcing the developer to think what to do with the error right there right then. I’m a Java and Python developer for a living and the ease with which you can ignore exceptions in so many situations was behind quite a few bugs I’ve seen over the last couple of decades.

I know that community pressure is something to be reckoned with, and I’ve seen a language creator get burned out because of it. But there are times when the language creators/maintainers shoud put their foot on the ground and say “Not gonna happen. We did things this way for a reason and it was a good reason.”

I miss the Pike-Thompson-Griesemer trio.

?
This syntax sugar feels like a very confusing behavior to me. It uses a ternary-like syntax to handle errors.
But this form is a new special syntax, which will increase the learning cost of golang, and the actual code is not intuitive enough.
Just like one of the usages, r := SomeFunction() ?, I didn’t even react to what it meant! This approach will only hide err for processing, but this should not be recommended.

// old :
err = Func()
if err!=nil{
    return err
}
// new :
Func() ? will be return
// but
Func() will be continue

And the {} execution logic triggered by ? also makes me worried, for example:

F1() ? {
		return F2() ? {
               		return F3 ...
               }
}

A very unintuitive “pseudo” callback hell!

I don’t expect to think too much about what logic this ? will generate when I see it. The original if logic is very consistent with the general logic.

I think golang’s error handling should be explicit, so that people can know at a glance that an error will occur here and make corresponding error handling.

I am mostly go on the back-end these days but I still do some .NET work. Whenever I see people ignoring/throwing exceptions I always wonder how/where the exception will even be handled. And the answer is sometimes more convoluted than you might think. Writing go APIs for years then going back to .NET feels a little like chaos at times.

I agree with this as well. If every language implements every feature, they eventually become homogenous; and the things that made go feel uniquely suited to the problems it was originally trying to solve will be lost.

This is addressed as one of the disadvantages in the proposal/discussion. The idea that it will be easier to accidentally ignore errors because you miss that your function call didn’t have ? after it.

Anyway, if you haven’t, get on that GitHub issue and vote! There are a bunch of comment threads where you can thumbs down to vote nay on functionality.

In my personal experience in every production code I will always come back and add individual error messages to almost every if err != nil { return err } Since most errors have different reasons and need different actions to address.

There are also editor-plugins to hide/minify the if err.... if you don’t like the look of it. If you can solve the problem with an editor-plugin you should not change the basic syntax of the language.

2 Likes

Yeah - in other languages error handling is kind of treated as “It’s not my problem! Some exception handler somewhere will deal with it!”. But I usually want to do something specific based on the error type. Being primarily a go developer for a while has shifted my view on errors. Instead of thinking errors are exceptional/bad, we know that of course errors are going to happen; but they are just values and we deal with them.

Counterpoint: have you built a flutter project? People nest widgets so deeply there’s a lot of tooling built around trying to make sense of it. But it’s a bandaid at best. And you could have made the same argument against adding modules to Go since we already had tooling before modules. I agree with you in this specific case but just saying it’s a little more nuanced sometimes.

1 Like

You are right, the blanket statement was a bit too wide. I would narrow it to simple visual plugins, which just shorten/highlight code. In this case a simple region-folding can collapse the if-err parts into a single line, no syntax change necessary.

Personally, I think we should be open minded. Especially, of other languages that are inspired by go like odin which take their own approach to solve the error boiler plate issue. In odin the or_return operator, returns the ending value for a function call by first checking whether its not nil or false.

For example, if a function call returns a (string, error) the end value here
would be the error value, which if not nil would be returned by or _return.
Below are examples of it I written in the style of go.
Examples 1 & 2 are equivalent.

ex1:

func someFunc() error {
str1, err := giveStr()
if err != nil {
 return err
 }
}

ex2:

func someFunc() error {
str1:= giveStr() or_return
}

Hopefully my explanation, got the point across.
For more details or a better explanation you can read the odin docs specifically about or_return.

I think this is a nice feature for prototyping. But for real world applications you will most likely seldom just return the error. You will usually use fmt.Errorf() to append relevant information (otherwise happy hunting, which call and variables resulted in the error “element not found”).

The current verbose style has several benefits, which we will lose with these alternatives - and all that to make the code a little bit shorter to look at/write, when Autocomplete and editor-folding can already provide this without the downsides.

4 Likes