9 ms·
Cargo cult. One interface per one concrete class. It drove me nuts, it still drives me nuts that we are still having debates about it in 2020.
by raducu 6y ago
Cargo cult. One interface per one concrete class. It drove me nuts, it still drives me nuts that we are still having debates about it in 2020.
- kevin_thibedeau 6y agoWhat's more annoying is when they import this into other languages that aren't hobbled by the design features of Java.
- waheoo 6y ago"modern" PHP frameworks suffer from this idiocy to no end. I'm sold on "pure PHP" but you can get a hell of a lot done with a few simple libraries and avoiding all the bullshit pushing you into classes. Typescript is quickly falling down this rabbit hole as well.
- nojvek 6y agoWhich Typescript projects have you seen been over architected? In my experience most typescript tends to be sane. Interfaces are only used to describe shapes of objects, class interfaces are a rare and only used when the consumer needs that abstraction.
- brundolf 6y agoIt varies widely by org. TypeScript is intentionally designed to accommodate as wide a swath of styles as possible (even to a point where some functionality is duplicated: you can have enums or string unions, you can have interfaces or object types, etc). TypeScript code can be written squarely in a C# developer's comfort zone (unsurprising, given it's developed by Microsoft), or it can be a richly expressive and flexible immutable/functional/structural type system. It's similar to what I've heard Scala described as (though I haven't used that myself). In some cases though this is a strength: I usually to stick to a functional style where I can, but end up with a handful of classes where they make sense. Being able to mix the two smoothly is nice.
- nawgz 6y agoTypeScript and modern JS are doing more than ever to get away from classes. Not sure what libraries you're using but it's time to reevaluate.
- selfhoster11 6y agoIt drives me up the wall. If you have interface X and the only implementation class XImpl, just put the damn code inside X. If you ever need to have two implementations, make X an interface then.
- gregkerzhner 6y agoTo be fair, if you unit test your objects, there are usually two implementations - the real thing and a mock. If you have a solid mocking framework, that can alleviate this need.
- bird_monster 6y agoI have moved into thinking that mocks don't really have much value. Testing real things is the only way to verify real things. If you can, set up a real DB, cache, whatever, and run your services in tests with real instances of their dependencies. Obviously this falls through for external APIs and things that are very expensive, but your savings in "But the tests passed" confusion time will be huge.
- JamesBarney 6y agoI've come to this conclusion. Organizations will spend many years writing and maintaining dubious unit tests that break with the smallest change, when they'd get an order of magnitude more value spending that time on an automation suite. One of the biggest reasons is testing at the same unit of granularity as the spec means you're tests break when the spec changes, and usually unit tests are far too granular. *I still think unit tests are great for tricky logic, but a lot of coding is just gluing together different systems where unit tests don't have a lot of bang for their buck.
- bird_monster 6y agoI might-should've rephrased my original post to suggest that mocks for non-code-you-wrote deps are a waste, but being able to inject different implementations of code you wrote for the sake of testing still has value. If you want to test how your code behaves when getting/setting data from a database, you fundamentally cannot mock the database. If you want to test how your code forms arguments to send to a dependency, you probably can, as long as you do both. If you're just testing that your args are correct without actually using them, you'll never really know. A specific example stands out in my mind when I watched a TDD-minded developer suggest that their code was complete because they injected a mocked version of DynamoDB into a service and then verified their tests ran. They committed and pushed without actually testing against DynamoDB. When tasked with _actually running it_, they found that there was a fundamental flaw in the way that developer perceived Dynamo's behavior, which obviously meant their mock was totally useless. If you want to test that you're pulling the right ID off an object to send to a database you can mock. If you want to test how your code actually behaves with a database, including failure scenarios and invalid arguments, you cannot. As usual, solutions are much more nuanced than I previously stated. I've moved away from mocks, not banished them entirely.
- eyelidlessness 6y agoHonestly I think most of the OOP world simply doesn’t “get” interfaces/protocols/contracts/etc. Sure, there’s polymorphism (but it’s typically inheritance-based) and inversion of control (but it’s typically used for testing). But like you said, cargo cult. It’s hard (okay it’s not hard, it takes study and practice) to know what the “right” abstractions are, and it doesn’t produce IDE solutions (unless you’re working from a strong theoretical foundation). It certainly doesn’t help when the type system doesn’t help. Lots of great abstractions are available when you can derive and infer meaningful types and derivatives at the end interface without a lot of fuss. It doesn’t just improve DX for library and product devs, it also improves design instincts. A more functional style often fares better in my experience. Functions just accept “some data” and return “some other data”. Even without Haskelly algebraic data types, “some data” in a function that isn’t explicitly tied to a concrete type is a good prompt to think about the kinds of things that are common or disparate about the data and how to handle them. It doesn’t necessarily lead to fantastic patterns. Most functional-style-but-idiomatic TS is not as well designed as equivalent code in an ML, error handling can be hell. But at least in my experience it has better outcomes than the OOP equivalent.
- raducu 6y agoI keep hearing functional, functional, but all(not that much) the functional code I've seen is no better than your average @Service calling anothrt @Service calling anothrt @Service. Yeah, it looks a little better with less parentheses, yeah you get fatter instead of taller code which is not that much easier to understand. There's still a milion ways to do the same thing, and there are still smartass people going out of their way to produce code that makes people notice how unique they are. All at the expense of wondering what the memory and performance will be.
- yumaikas 6y agoYeah, that seems like an overkill to me. The balance I do like to strike is to use interfaces for any classes that interact with the outside world, and then have those faked out. That way, I can use them to provide data to smaller units, or record that business logic recorded the data I expected. Obviously this doesn't fix if the DB interactions themselves are correct, but it's a start.
- KMag 6y ago> One interface per one concrete class I vaguely remember language support for something like this in the SatherK dialect of Sather. Sather itself was inspired by Eiffel. One-interface-per-class actually is no problem if it's well supported in the language, by simply adding a single special character to the concrete class name. It worked something like putting some sigil (dollar sign, IIRC) in front of a concrete class name meant that the code was referring to the public interface exposed by the class. (I could have gotten it backward in that maybe you needed the sigil in function signatures/field declarations if you wanted to force the concrete implementation instead of the interface. I think it evolved from the normal Sather dialect, where interface declarations looked pretty much like class declarations except that the name began with a sigil. If it hadn't evolved from standard Sather, I suspect they would have required the sigil if you really meant to intentionally restrict your code to the concrete implementation.) Java could have headed off a lot of premature abstraction by making it easier to later replace a concrete implementation with an interface. Instead of making "new" an operator, Java should have made "new" a static method returning an instance of the class, with a bit of compiler flow analysis to allocate an object the first time you referenced "this" within "new". The chief advantage is that it would have minimized the amount of code change necessary to replace a concrete class with an interface if needed. If new is a static method, then the interface's "new" can return some reasonable default implementation, or decide among implementations at runtime. You'd still need to recompile everything, so that all callers would use the invokeinterface bytecode instead of invokevirtual. Alternatively, invokeinterface/invokevirtual could be collapsed into one opcode, and rely on polymorphic inline caches/hot spot inlining to remove most of the method dispatch overhead. A third option would be to have the bytecode verifier perform invokevirtual/invokeinterface substitution at class load time in cases where a concrete class has been changed to an interface or vice-versa. Making "new" static factory methods instead of an operator would also make it possible for a class to transparently swap in specialized subclass implementations (for instance, specialization for BigInteregrs that fit in 64 bits, or Strings where all codepoints are less than 256). PyPy does a small amount of this specialization internally for Python, but it would be nice to have language-level support.
- dmux 6y agoI think this issue stems from the lack of focus on components. To me, if a class is inside the component and has no external visibility, there's no need for it to have an interface. On the other hand, if you're exposing functionality to something "outside" the component at hand, you should definitely be using an interface as you cannot predict the future.