8 ms·
> But getting back to the issue at hand: what I see from the Java/C# world is a mentality of "abstract unless there's a reason not to", instead of "don't abstra
by philliphaydon 6y ago
> But getting back to the issue at hand: what I see from the Java/C# world is a mentality of "abstract unless there's a reason not to", instead of "don't abstract unless there's a reason to".
When I was starting out and reading lots of books and blogs this was very much my mindset.
Must use all the patterns!!!
But after working on a huge code base with many many many layers of abstraction, I now am of the mindset of just keeping it dead simple. If I need to get something from the database. Just get it. Introduce an abstraction when it’s needed. It’s easy to add an abstraction, it’s harder to take it away.
- oefrha 6y agoObligatory “huge” code base with many many layers of abstraction: https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris...
- tacker2000 6y agoThis is hilarious!
- raducu 6y agoCargo 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.
- brnt 6y agoI have followed an academic path so far, so none to little formal training and lots of room to try out my own ideas. Often ten lines of Python can already be a significant improvement over using some convoluted 'easy-to-use' GUI tool (looking at you, Origin...). I started exactly like this, and the pattern I wanted to use/learn was 'classes'. After struggling for a while, I realized that what I wanted to do was much easier to solve with a 'table' (list of lists) and creating functions that return the resulting table modified. It taught me that there is value in simplicity, in particular at the level you understand right now, or are at least not too far outside of your knowledge-zone. If you want to get things done, that is ;) Note that this was a few years before scientific Python and its ecosystem came into full-swing, I'd probably have discovered and uses Pandas right now. Later I learned that this was called functional programming, I learned how classes work and what they are useful for (far larger programs than I was writing at the start of my career). C++ is a fixture in the scientific community (I'm now into Monte Carlo and solvers), and while I'm happy C++11 happened, the functionality of the C++ stdlib enables so many different styles that it is really hard to get a team to stick to a subset, and not get too distracted with all the nice new tools. Sure, much of it is nice, but how we can write C++20 is pretty much another language from C++03. I guess I rambled a bit there. What I'm saying is that I'm generally critical of using new features, unless I've seen a convincing and for me personally concrete demonstration of how it saves mental CPU-cycles or boiler plate. This makes it easy for me (in the end, I'm not here for the code but for what you can do with it) and onboarding others (who, in science, might barely know that C++ has versions). KISS KISS KISS. Nothing will convince me of the opposite :)
- jcelerier 6y ago> Sure, much of it is nice, but how we can write C++20 is pretty much another language from C++03. why is that a problem ? in the end ideally each of your program should be its own mini-DSL that solves the problem at hand in the best way ; successive C++ versions just allow your to create more and more expressive DSLs
- brnt 6y agoIf every program is written in its own language, we're going to have a hard time collaborating on code!
- commandlinefan 6y ago> Must use all the patterns!!! In my experience, Java developers don't use all the patterns, they use exactly two: Factory and Singleton.
- speed_spread 6y agoThat's the beginners. Average ones use plenty of Adapters and then build Facades. The cool ones use Strategies. They all suck.
- alzoid 6y agoWe had a vendor deliver some C# source code that was exactly this. A 'repository' interface that had a concrete class that called an abstract data access class etc. A path of 3 or 4 calls to run a SQL statement against a MSSQL Server. There were no requirement for this. The Gang Of Four design patterns are great when you actually use them to solve the problem they were designed for. I worked on a product that grew in complexity and a factory was needed. Implementing was easy in Netbeans - right click and extract interface and provide the new implementations and the creation logic. I'm not sure when using patterns all the time got so prevalent. As the saying goes, when all you have is a hammer, everything looks like a nail.
- vsareto 6y agoThat's pretty typical in C# land (another is service->repo->data access with DI/interfaces for each). Very likely the dev had that pattern down as a reflex. There was no consideration on their part because they already knew a solution that worked. They could move on to the other problems or requirements. As another poster elsewhere said, the annoying thing is only the single implementation for the interfaces, and no real code that makes use of the abstraction. It only really alleviates other devs from asking "where are the interfaces for these classes?"
- malka 6y agoUsing an interface forces one to think about a clean API, that does not rely on internals.
- lmz 6y agoIn theory. Sometimes it only has one implementation and the details leak through.
- imtringued 6y agoThat doesn't make any sense. Java already has private and public as modifiers. The only meaningful difference is that interfaces prevent you from using private. That's such a shallow benefit. If you have the discipline to create interfaces you also have the discipline to use the right modifiers. The reality is that all those interfaces will just mirror the class and cause extra friction even though polymorphism is never needed.
- collyw 6y agoYes, with experience you realize our job shouldn't be about knowing as many different techniques as possible but rather knowing how to manage complexity by using those various techniques.