2

Let's say I need an abstraction for some type that has a method Do, which returns an error

package main

import "myproject/complex"

type Doer interface {
    Do(i int) error
}

func main() {
    var doer Doer
    doer = complex.Doer{}
    i := 1 // or any integer
    err = doer.Do(i)
    // How to check which error was returned?
}


package complex

type Doer struct{}
func (Doer) Do(i int) error {
    if i < 0 {
        return fmt.Errorf("negative numbers not allowed") // or any complex nested error but this can be expected (bad input, etc)
    }
    if i == 40 {
        return fmt.Errorf("this number cannot be used")  // or any complex nested error but this can be expected (bad input, etc)
    }
    if i > 100 {
        return fmt.Errorf("some unknown error, everything broke") // or any complex nested error which is unexpected (e. g. some 3rd party failure)
    }
}

I would need a better architecture to be able to identify if this is known error or not. I could do it java-style and make implementation know about our error types. e. g.

package do

type Doer interface {
    Do(i int) error
}

var (
    ErrNegative = fmt.Errorf("negative numbers not allowed")
    ErrCannotBeUsed = fmt.Errorf("this number cannot be used")
)


package complex

import (
    "myproject/do"
)

type Doer struct{}
func (Doer) Do(i int) error {
    if i < 0 {
        return do.ErrNegative
    }
    if i == 40 {
        return do.ErrCannotBeUsed
    }
    if i > 100 {
        return fmt.Errorf("some unknown error, everything broke")
    }
}


package main

import (
    "errors"

    "myproject/complex"
    "myproject/do"
)

func main() {
    var doer do.Doer
    doer = complex.Doer{}
    i := 1 // or any integer
    err = doer.Do(i)
    if err == nil {
        return
    }
    if errors.Is(err, do.ErrNegative) {
        // some handling
    } else if errors.Is(err, do.ErrCannotBeUsed) {
        // some handling
    } else {
        // handle unknown error
    }
}

However, in such architecture, I'd force implementation (complex.Doer) to adapt to the interface (do.Doer), which, according to Go Wiki, is not how interfaces are supposed to be used.

What would be a proper way to handle errors in such case?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
savolro
  • 39
  • 2
  • 1
    To answer that, we'd need to know how the error handling would differ for the different error types. Most of the time it doesn't, so having strong static programmatic differentiation between them is generally useless. – Adrian Jan 16 '20 at 19:01
  • 1
    Your `complex.Do` function does not return `error`. When you make it return `error`, there's nothing wrong with the second solution. The implementation doesn't need to know about the interface. – Burak Serdar Jan 16 '20 at 19:03
  • 6
    It is idiomatic for interface implementations to agree on well known error values. An example is that `io.Reader` implementations are expected to return `io.EOF` at end of file. – Charlie Tumahai Jan 16 '20 at 19:04

0 Answers0