NBJack 8 days ago

Wait until you try refactoring in Go. Java has plenty of warts, but the explicit inheritance in it and other languages makes working across large codebases much simpler when you need to restructure something. Structural typing is surprisingly messy in practice.

1
chuckadams 8 days ago

I love structural typing, use it all day long in TypeScript, and wish every language had it. I've never run into issues with refactoring, though I suppose renaming a field in a type when the consumers are only matching by a literal shape might be less than 100% reliable. It looks like Go does support anonymous interfaces, but I'm not aware of how much they're actually used in real-world code (whereas in TS inlined object shapes are fairly common in trivial cases).

kbolino 8 days ago

I too like it, in fact I find myself missing it in other languages. I will say that anonymous interfaces are kind of rare in Go, and more generally interfaces in my experience as used in real codebases seem to lean more on the side of being producer-defined rather than consumer-defined.

But there are a couple of problems I can think of.

The first is over-scoping: a structural interface is matched by everything that appears to implement it. This can complicate refactoring when you just want to focus on those types that implement a "specialized" form of the interface. So, the standard library's Stringer interface (with a single `String() string` method) is indistinguishable from my codebase's MyStringer interface with the exact same method. A type can't say it implements MyStringer but not Stringer. The solution for this is dummy methods (add another method `MyStringer()` that does nothing) and/or mangled names (change the only method to `MyString() string` instead of `String() string`) but you do have to plan ahead for this.

The second is under-matching: you might have intended to implement an interface only to realize too late that you didn't match its signature exactly. Now you may have a bunch of types that can't be found as implementations of your interface even though they were meant to be. If you had explicit interface implementation, this wouldn't be a problem, as the compiler would have complained early on. However, this too has a solution: you can statically assert an interface is supposed to be implemented with `var _ InterfaceType = ImplementingType{}` (with the right-hand side adjusted as needed to match the type in question).

NBJack 7 days ago

I have to trace structure and data handoffs across multiple projects sometimes that span multiple teams. I'm used to rather easily being able to find all instances of something using a thing, where something is originally defined, etc. I can't do that easily in Go due to the structural typing at times without either a very powerful IDE (JetBrains is the only one I know of that does Go well via Goland) or intimate knowledge of the project. It's surprisingly painful, and our tooling while not Jetbrains level is suppose to be some of the best out there. And Yahweh help me if I actually have to refactor.

I really doubt this comes up in smaller, one-team repos. But in the coding jungle I often deal with, I spend much more time tracing code because of this issue than I'drefactoring. Make no mistake: I like Go, but this irks me.