代码行为取决于开关操作符中的类型顺序,如何摆脱呢?

Recently I started to learn the Go language.
I am trying to understand interface principles in Go and was completely puzzled by one thing.
The duck principle says: if something quacks like a duck and walks like a duck, then it's a duck.
But I wondered how Go will behave if we have three interfaces like this:

// Interface A
type InterfaceA interface {
    ActionA() string
}

// Interface B
type InterfaceB interface {
    ActionB() string
}

And interface C, which does something different but has functions which are similar to interfaces A and B functions:

// Interface C with methods A and B interfaces
type InterfaceC interface {
    ActionA() string
    ActionB() string
}

Then we have three structures which implement the interfaces above:

type StructA struct{}

// If it does ActionA then it's interface A
func (a StructA) ActionA() string {
    return "Interface A implementation"
}

type StructB struct{}

// If it does ActionB then it's interface B
func (b StructB) ActionB() string {
    return "Interface B implementation"
}

type StructC struct{}

// If it does ActionA and ActionB, it's an Interface C
func (c StructC) ActionA() string {
    return "Interface C implementation"
}

func (c StructC) ActionB() string {
    return "Interface C implementation"
}

And a function that identifies which type it gets:

func getType(data interface{}) string {
    switch data.(type) {
    default:
        return "Unknown"
    case InterfaceA:
        return "Interface A"
    case InterfaceB:
        return "Interface B"
    case InterfaceC:
        return "Interface C"
    }
}

Code inside main function:

func main() {
    a := StructA{}
    fmt.Println(a.ActionA())
    fmt.Println(getType(a)) // should return InterfaceA
    fmt.Println("")
    b := StructB{}
    fmt.Println(b.ActionB())
    fmt.Println(getType(b)) // should return InterfaceB
    fmt.Println("")
    c := StructC{}
    fmt.Println(c.ActionA())
    fmt.Println(c.ActionB())
    fmt.Println(getType(c)) // should return InterfaceC
}

Output:

Interface A implementation
Interface A

Interface B implementation
Interface B

Interface C implementation
Interface C implementation
Interface A

After some experiments I found out if we change the case order inside switch then the function identifies the type correctly:

func getType(data interface{}) string {
    switch data.(type) {
    default:
        return "Unknown"
    case InterfaceC:
        return "Interface C"
    case InterfaceB:
        return "Interface B"
    case InterfaceA:
        return "Interface A"
    }
}

Output:

Interface A implementation
Interface A

Interface B implementation
Interface B

Interface C implementation
Interface C implementation
Interface C

See also full code on play.golang.org

My question: Is it a bug or a feature? And if it's a feature, how should I change getType so that the function doesn't depend on case order?

This is the intended working, as defined by the language spec.

There are 2 types of switch statement, Expression switches and Type switches, and this behavior is documented at the Expression switches:

In an expression switch, the switch expression is evaluated and the case expressions, which need not be constants, are evaluated left-to-right and top-to-bottom; the first one that equals the switch expression triggers execution of the statements of the associated case; the other cases are skipped. If no case matches and there is a "default" case, its statements are executed. There can be at most one default case and it may appear anywhere in the "switch" statement.

[...]

A type switch compares types rather than values. It is otherwise similar to an expression switch.

In Go a type implicitly implements an interface if its method set is a superset of the interface. There is no declaration of the intent. So in Go it doesn't matter which interface defines the methods, only thing that matters is the method signatures: if a type has all the methods an interface "prescribes", then that type implicitly implements the said interface.

The problem is that you want to use the Type switch to something it was not designed for. You want to find the "widest" type (with the most methods) that is still implemented by the value. It will only do this if you enumerate the cases (the different types) in this intended order.

That being said, in your case there's no such thing that a value is only InterfaceC implementation. Your code doesn't lie: all values that implement InterfaceC will also implement InterfaceA and InterfaceB too, because the method sets of both InterfaceA and InterfaceB are subsets of the method set of InterfaceC.

If you want to be able to "differentiate" InterfaceC implementations, you have to "alter" the method sets so that the above mentioned relation will not hold (method set of InterfaceC will not be a superset of the method set of InterfaceA and InterfaceB). If you want StructC to not be an InterfaceA implementation, you must change the method signature of ActionA() (either in InterfaceA or in InterfaceC), and similarly ActionB() to not be an InterfaceB implementation.

You could also add a method to InterfaceA (and to InterfaceB) which is missing from InterfaceC:

type InterfaceA interface {
    ActionA() string
    implementsA()
}

type InteraceB interface {
    ActionB() string
    implementsB()
}

And you have to add them to StructA and StructB of course:

func (a StructA) implementsA() {}

func (b StructB) implementsB() {}

This way you get the desired output. Try it on the Go Playground.

If you can't or don't want to do this, your only option is to enumerate the cases in the right order. Or don't use type switch for this.