4 ms·
Personally I like to differentiate subclassing from conforming to an interface. Both are subtyping, but interfaces are much cleaner.
by cobbal 2y ago
Personally I like to differentiate subclassing from conforming to an interface. Both are subtyping, but interfaces are much cleaner.
- marginalia_nu 2y agoInterface inheritance is indeed much less of a foot-gun than class inheritance, but I'll argue even interfaces are generally fairly overused. The single-class interface pattern that exists in some parts of Java-land is just bizarre.
- vips7L 2y agoI see that a lot more in C# than in Java tbh.
- marginalia_nu 2y agoIt's a cultural thing. I've seen code bases where every class was an FooImpl and had an associated FooIf, all in name of decoupling and mocking. Though it may be a trend that's dying down.
- caspper69 2y agoIt's a lesson that everyone learns eventually. You cannot eliminate complexity (although you can minimize it, to be clear). But for most things, you can shuffle the semantics around, you can paper over them in a new way, but at the end of the day, someone has to pay the piper. In my experience, complexity eliminated from one spot will invariably bubble up somewhere else in an unexpected way.
- vips7L 2y agoI remember at some time you couldn’t mock in C# without an interface or maybe it was just the popular library that required it. Java never had that requirement so at least in the codebases I’ve been in you didn’t have IBar and BarImpl everywhere.
- int_19h 2y agoIn Java, all method dispatch is virtual (dynamic), but in C#, methods being virtual is an opt-in, so intercepting and mocking such calls requires a lot more effort. It's still possible, mind you. TypeMock has been offering this exact ability for C# for many years now. But the free TDD frameworks generally didn't have this.
- marginalia_nu 2y agoJava's method dispatch is more complicated than that due to JIT compilation. The affordances are those of dynamic dispatch, but hot Java method calls will not go through a vtable-like lookup equivalent unless the code actually sees a need to.
- neonsunset 2y agoIndeed. Both OpenJDK HotSpot and .NET RyuJIT perform guarded devirtualization of monomorphic or polymorphic with few instances callsites. OpenJDK also computes optimized call table for megamorphic callsites, which .NET does not need to do for virtual calls, it does have something similar for un-devirtualized interface calls however (virtual stub dispatch[0]). This is not necessarily zero-cost however - if the compiler cannot prove specific type members being invoked, it has to construct an execution profile and then apply it to subsequent compilations, and also emit a guard when doing dispatch on those. [0]: https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/botr/virtual-stub-dispatch.md https://github.com/dotnet/runtime/blob/main/docs/design/core... (note - subject to change)
- bmm6o 2y agoIt seemed really encouraged in early Java. I think C# picked it up more to support mocking for unit testing. The Go approach is a breath of fresh air when it comes to mocking and testing.
- neonsunset 2y agoIt's mostly a culture issue. Mocking components is a code smell. You should aim to have the implementation sufficiently self-contained and test these modules end-to-end. Some behaviors cannot be controlled and should not be abstracted away in some more exotic cases, like Environment.TickCount64-based cache expiration, in which case you should reproduce the actual environment. The goal of the tests is to answer the question "if the pipeline is green, does it give us confidence it will work exactly as intended in production?". Mocks work against this goal. Especially nowadays when writing tests is dirt cheap because LLMs can often get them right at the first try, there is little reason to avoid approaching this problem without prioritizing sanity over following stupid cargo cult that should have died a decade ago.