This is a proposal for a way to simplify the error handling in Go.
The idea is to use attempt{}
and recover{}
blocks to separate the happy path from the error handling. NOTE: this is NOT exception handling as you would see in Java or PHP. More on why below.
In this proposal Go would allow the developer to omit capturing the last returned variable from a function call if and only if that call was within a attempt{}
block. The Go compiler would handle providing access to the error
instance via the geterror()
function within the recover{}
block if any function returns a non nil
error as its last omitted parameter, i.e.:
attempt{
data:= GetData()
}
recover {
println( "ERROR: " + geterror().Error() )
}
Instead of this which you currently have to do:
data, err:= GetData()
if err != nil {
println( "ERROR: " + err.Error() )
}
This proposal suggests Go add a new built-in function named punt()
— or it could be a statement — which would merely transfer control to the recover{}
block before first setting an internal static variable to be returned by a suggested new built-in and go-routine safe function named geterror()
.
In addition this proposal suggests a new //go:action
comment type that would be used like the following example, where the Go compiler would call a SetAction()
method defined in an interface maybe named action
if it was applied to the error
object to set the value returned by the Action()
method:
attempt{
//go:action getting the data
data:= GetData()
//go:action processing the data
data:= ProcessData(data)
}
recover {
fmt.Printf( "ERROR: unable to %s; %s",
geterror().Action()
geterror().Error() )
}
Also there should be a built-in punt()
function or statment that simply accepts an error and returns it, which when called inside a attempt{}
would trigger a jump to recover{}
for any non nil
error:
attempt{
//go:action getting the data
data:= GetData()
//go-action checking if data is empty
if data == nil {
punt(errors.New("data is nil"))
}
//go:action processing the data
ProcessData(data)
}
recover {
fmt.Printf( "ERROR: Occurred while %s; %s",
geterror().Action()
geterror().Error() )
}
And a last point is that this approach would allow using functions that return both a value and an error to be used in conditional expressions, e.g.
attempt{
//go:action getting the data
data:= GetData()
// evaluating if data should be processed
if ShouldProcessData(data) { // <-- this contrived func returns (bool,err)
//go:action processing the data
data:= ProcessData(data)
}
}
recover {
fmt.Printf( "ERROR while %s; %s",
geterror().Action()
geterror().Error() )
}
If you read this far then you are obviously interested, at least somewhat. Can you please at least provide your questions and/or impressions in the comments?
BTW, what triggered me to envision this is I was doing a code review and refactor for an open-source project and I was adding a lot of second parameters to return errors where the developer just ignored that an error might occur.
But when I did that every one of these:
Had to be refactored to this:
Which means that too many developers will simply not return errors when they should.
But if it was not as painful, then they would me much more likely IMO to return an error in the cases an error can occur.
Which doesn't look much better. But it does when you have many of them:
Someone who I first showed this to said "I'd just fire the developer who doesn't handle errors" to which I pointed out:
He also said "i’m not wild about throw / catch because in java then people end up not catching them and the propagate up unexpectedly" to which I responded:
Finally I said: