4 ms·
Simple code is easy to change, I feel like a lot of architecture astronauts (cough Enterprise Java cough) don't get that. "Why is everything an interface?" "B
by SPBS 3y ago
Simple code is easy to change, I feel like a lot of architecture astronauts (cough Enterprise Java cough) don't get that.
"Why is everything an interface?"
"Because we may need to swap out its implementation in the future. It's Clean Code."
"Then refactor it out into an interface when you need it! What's an IDE for?"
- bluejekyll 3y agoInterfaces have a different value, they make it easier to build testing facilities for unit tests as well. This works in many languages, Java, traits in Rust, etc.
- rf15 3y agoIs it really a good idea to bend your code to satisfy your testing environment and not the other way around?
- rtheunissen 3y ago"Bend" is not a good description of the intention here. Good code should be easy to test effectively and interfaces are a common way to achieve that. We should absolutely design our code with testing in mind. For example, you have a repository for some entity as a dependency of a service and you would like to test some behavior of the service when the repository fails. In this case, the service can depend on an interface of the repository instead and the test can create a stub that implements that interface, whereby you can simulate the error. If the service depended on the actual repository, this becomes very difficult to do.
- agalunar 3y ago> Good code should be easy to test effectively Can you explain why this is? or maybe more specifically what you mean by this.
- robertlagrant 3y agoYour tests are part of your code. Your implementation code should not make writing your test code difficult.
- pjc50 3y agoIt's kind of unavoidable once you're trying to do unit tests with mocks. You either end up with interfaces for everything and a dependency injection framework, or you end up with some very non-isolated tests that take ages to run because they need inseparable parts of the whole real system.
- Jach 3y agoThe real answer is interfaces, even if they only have one implementation, let you not have to worry about cyclic dependencies.
- cjblomqvist 3y agoHow? (honest question, I'm curious!)
- Jach 3y agoIf you start with AService in module A, other modules need to depend on A to use it. You get into circular dependencies trouble when A needs to use a service from another module B that itself or through a dependency needs the AService. One solution is to split it into an AService interface you put in a new module, call it e.g. A-api, and leave the implementation in A which now depends on A-api. Do the same for B/B-api. It's a lot less likely that your -api modules will circularly depend on each other because they only contain interface definitions, so A can safely depend on both A-api and B-api and B can safely depend on both B-api and A-api. A more concrete example of an A and B that need to interact with each other might be a user account service and a (financial) transactions service. (Why not let them live in the same module? That can also be a solution, but as the scope of each grows and especially if separate teams end up owning each it can make sense to split them and enforce some boundaries via the interfaces.) Another example could be a notifications service and several of its clients, like whatever is handling user posts kicks off a notification event and asynchronously the notification service processes it and may need to reach around to call back into other services that could be part of the sender's module. (Passing simple lambdas may be an adequate alternate solution too.)
- jt2190 3y ago> One solution is to split it into an AService interface you put in a new module, call it e.g. A-api, and leave the implementation in A which now depends on A-api. This is what Fowler describes as “Separated Interface”. [1] Specifically he calls out the specific need for this special case: > “[Y]ou might need to invoke methods that contradict the general [module] dependency structure. If so, use Separated Interface to define an interface in one package but implement it in another.” It’s a special case because until you need to “contradict the general dependency structure”, you don’t need to do it. In particular, this is not the first tool one should reach for if you have a circular dependency between two modules. [1] https://www.martinfowler.com/eaaCatalog/separatedInterface.html https://www.martinfowler.com/eaaCatalog/separatedInterface.h...
- lenkite 3y agoI dunno - I found "Enterprise Java" objectively quite easy to refactor to fit high-scalability and modularization needs. Having stuff defined as interfaces actually helped greatly with this.
- Retric 3y agoStraightforward yes, quick no.
- preommr 3y agoBecause then someone else comes along and builds to the implementation and now that simple change becomes a mess of a refactor. An interface isn't just a type construct, it communicates the intent of the systems design.
- magicalhippo 3y agoDepending on language, interfaces also places additional constraints as it limits interdependencies. For example, in Delphi classes in the same unit (file) can access each others protected members. This is usually a code smell, which interfaces prevent.
- marginalia_nu 3y agoIt's not really about replacing the implementation. Depending on interfaces means you (by definition) can't depend on the implementation. It's a decoupling thing more than anything else. You get some benefits for mocking during testing and so on but it's mostly just to get something like a C++-style class declaration/definition (or .h/.cpp)-split. It's admittedly taken a bit too far in some codebases and often done without much thought to why, but it's also a bit of an artifact of pre Jigsaw-era Java where it was very hard to enforce boundaries between modules. In that light it makes at least some sense.
- ParetoOptimal 3y ago> Simple code is easy to change, I feel like a lot of architecture astronauts (cough Enterprise Java cough) don't get that. It's not easy to uniformly change in a way that doesn't add regressions. It's also not usually easily changed except by a large team. That large team likely adds more regressions because communication is lossy. "Simple code" that avoids abstractions leans more on people, code that embraces abstractions (good or bad) attempts to lean more on the machine.