To make an attempt at articulating why the error handling in Go is valuable 
as-is, I note the following points:

   - It is explicit, not hidden
   - The default idiom highlights "error checking being done here" (Go 
   developers learn to look for lines "if err != nil".)
   - It is possible to set breakpoints on the error case
   - Code coverage of unit tests reports when the error case has been 
   exercised.
   - It has the full flexibility of identifying the failure scenario, not 
   limited to err != nil (although that is the overwhelming use)
   - It has the full power and flexibility of the language for handling the 
   error - it can be logged, returned, recovered from, sent to a channel, etc.
   - If wrapping my error extends to multiple lines (due to longer 
   messages, number of parameters in the error, etc., that works using 
   existing language constructs.
   - It supports arbitrary return statement complexity, including multiple 
   return values.
   - It is straightforward to parse / analyze.

That said, it has some drawbacks, particularly in the most common cases.

   - Vertical space: Even in complex cases, the error handling inside the 
   "if" block can be reduced to at most two statements. One line for 
   "handling" the error, and a second line for returning it (or a modified / 
   wrapped version of it). With two additional vertical lines of boilerplate, 
   that's either a 100% or 200% increase in vertical space (the "if" 
   statement, and the closing brace). This limits the amount of code that can 
   fit on my screen, which constrains my ability to comprehend larger 
   functions.
   - Verbosity: The moment we're know we're checking for errors, the "if 
   err != nil {", the "return", and the "}" are usually just extra syntax.
   - Scanning complexity: What if the check is not err != nil, but err != 
   NotFound? Is this an error case, or an expected case? The current construct 
   hides that distinction. We have to look to the next line to see how it is 
   handled to know that it is an error return.
   - When there are multiple returns on a function, the error handling must 
   specify them all, even though the common case is to return a "zero" value.

For language design purposes, the most common scenario for handling an 
error is a single-line error return. When it comes to error handling, there 
are two paths to choosing a new solution:

   - Identify a solution that captures the 80-90% case of if err != nil { 
   return ... }
   - Identify a solution that can be used for *all* error handling, where 
   the existing if err != nil then becomes a legacy code base choice, because 
   it does not use the new construct which more clearly conveys "error 
   handling here".

For the benefit of the language, I would prefer a solution that fits the 
second category, not one that fits the first category. Due to the fact that 
this proposed solution does not address the arbitrary response complexity, 
it seems like it fits the first category. If there's a way to tweak the 
proposal so that it can be used for *every* error handling case, then that 
would be better.

Go has a long history of implementing orthogonal solutions that happen to 
combine well. It is possible that error handling is one of those places 
that could benefit from teasing this question apart a little more.

Eric


On Thursday, February 6, 2020 at 7:28:24 PM UTC-8, addi...@gmail.com wrote:
>
> Error checking in Go: The try keyword.
>
> This isn’t a complete proposal it only describes basic idea and the 
> changes suggested.
> The following are modifications that deals with some of the problems 
> introduced in the original proposal 
> <https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md>
> .
>
> (I apologize if something very similar that uses a simple method to deal 
> with adding context has been posted before but I could not find it.)
>
> First, try is a keyword not a function builtin.
>
> Here’s how error handling with the try keyword works:
>
> try err
>
> is equivalent to:
>
> if err != nil {
>     return err
> } 
>
> That’s it.
>
> Example:
>
> f, err := os.Open("file.dat")
> try err
> defer f.Close()
>
> f.WriteString("...")
>
> But, how to add context to errors?
>
>  because try only returns if err != nil.
> You can create a function that returns nil if an error is nil.
>
> In fact, a function like this already exists in the errors package here.
> https://github.com/pkg/errors/blob/master/errors.go
>
> // Wrap returns an error annotating err with a stack trace
> // at the point Wrap is called, and the supplied message.
> // If err is nil, Wrap returns nil.
> func Wrap(err error, message string) error {
>     if err == nil {
>         return nil
>     }
>     err = &withMessage{
>         cause: err,
>         msg:   message,
>     }
>     return &withStack{
>         err,
>         callers(),
>     }
> }
>
> Example using it with try
>
> f, err := os.Open("file.dat")
> try errors.Wrap(err, "couldn't open file")
> defer f.Close()
>
> f.WriteString("...")
>
> That’s it.
>
> This should reduce repetitiveness with error handling in most cases.
>
> It is very simple to understand, and it feels like Go.
>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/f90d43bb-58e0-4cff-9149-48c4082ab183%40googlegroups.com.

Reply via email to