10 ms·
> We define the UserProvider interface I'm strongly against creating interfaces for non-library (not shared) code, that will only ever have one concrete implem
by ronnier 7y ago
> We define the UserProvider interface
I'm strongly against creating interfaces for non-library (not shared) code, that will only ever have one concrete implementation. I see it all the time in internal service code, code that will never have a second implementation. It makes code hard to navigate, harder to understand, and tedious to manage as instead of updating one file, now you'll need to update two.
- 3fe9a03ccd14ca5 7y agoI find myself doing this often and it’s annoying. Usually it’s because I want to use some other implementation for testing that never materialized, and then the interface sits there.
- oldmanhorton 7y agoDo you consider test mocks to be a second implementation that warrants the interface?
- ses1984 7y agoThere are test frameworks that obviate the need to do this.
- gautamdivgi 7y agoNot if you use something like c/c++. There are test frameworks but there is no way from preventing testing code from leeching into functional code. But yes - Java/python, etc. you shouldn't need to do this.
- taco_emoji 7y agoNot on all platforms. In .NET you need an interface or virtual method in order to allow the mocking framework to dynamically generate a concrete subclass that mocks out that method's functionality.
- CraigJPerry 7y agoWhat’s cheaper to own though - a whole test library / framework (assuming you don’t use any other feature), or a simple interface? I’m generally hesitant to add test libraries if there’s an easy design choice alternative that keeps the code simpler.
- organsnyder 7y agoMockito is a single pom.xml (or build.gradle) entry. edit: Not "Moquitto".
- CraigJPerry 7y agoIt is - but my point is about complexity not ease. The fact that dependency is versioned separately from your code base is one source of complexity that it adds. The fact that it has many lines of code is another complexity. It is however often easier, even although i'm advocating very small self-written test doubles, they are still less easy than using mockito.
- organsnyder 7y agoIt's a dependency that's only needed during testing, though—it's not shipped with production artifacts.
- CraigJPerry 7y agoThat argument doesn't address complexity. Tests are part of the system.
- MockObject 7y agoClose! That's Mockito. https://site.mockito.org/ https://site.mockito.org/
- organsnyder 7y agoOops. Haven't done Java programming full-time since last year, but I did set up a home MQTT broker recently...
- chopin 7y agoI do that for this reason but only if I am able to inject the implementation into the classes under test. Very often I just resort to constrictor injection for internal code.
- marcus_holmes 7y agoI did this, and then switched back to just running the code on a test database. I found I was writing two sets of tests: one that tested the code to see if it injected the right parameters into the SQL, and another to see if the SQL worked. It just got easier to test them both together.
- dcolkitt 7y agoThere are tradeoffs to this approach. When the tests break it becomes a lot harder to triangulate the origin of the problems.
- runeks 7y agoI don’t buy this argument. Once you know there’s a bug, you will definitely want to write more tests, in order to find the cause. But always writing a lot of tests, which are only usable in case you find a bug, seems like wasted effort to me. If you need to triangulate the origin, do that when it’s necessary (i.e. when your broad test fails) and not all the time.
- GordonS 7y agoI prefer to just runs tests using a real database too. They might not complete as fast as mocked out ones, but they give me much more confidence of correctness, and still complete in milliseconds.
- breischl 7y agoThis can become a challenge when you want to scale a dev team. You either have people conflicting in the DB, or each dev needs to set up a local DB and deal with DB migrations. Containerized DBs help with this to some extent, but it's still a maintenance headache. There's also a tradeoff around testing error states, but that can be a wash. Some errors are easier to create with a mock, some are easier to create with the actual driver.
- bcrosby95 7y agoNo. This is what many considered test induced damage. If the only reason you create an interface is because a test requires it, you are damaging the integrity of your design. Interfaces are one of the more powerful abstractions available and should be well thought out points of flexibility you specifically designed into your solution. If you use interfaces for other reasons your codebase is full of false flexibility and not-actually-abstracted abstractions which makes it difficult for other people to figure out what they can actually do with it.
- iamaelephant 7y ago> No. This is what many considered test induced damage. If the only reason why you create an interface is because a test requires it, you are damaging the integrity of your design. Writing testable code is not "damage". An interface is a small concession in exchange for the well known benefits of automated testing. I see where you're coming from and many programmers use too many interfaces as a reflex. But I absolutely think interfaces should be used to create seams for automated tests in front of components that do things like interact with databases, file systems, networks etc.
- klmr 7y ago> Writing testable code is not "damage" Correct, but you can go a long way in writing testable code without requiring mock implementations. While I don’t deny the usefulness of mock testing, it comes at a steep cost (= vastly more complex code base), and I increasingly question whether that’s worth it. In practice I find it more useful to unit test what can be tested in isolation, and to perform integration testing for the test, and to limit mock testing (and thus unit tests which would require mocking) to a few well-defined interfaces. This cuts down drastically on boilerplate, with little (if any) loss of testability. And these few remaining mock object dependencies can be modelled via simple parameters and higher-order functions, further cutting down on extraneous interface classes.
- BeetleB 7y ago> Correct, but you can go a long way in writing testable code without requiring mock implementations. For some languages (e.g. C++), if you are using classes, it can actually be fairly challenging to do unit tests without mocks. And almost every solution to this problem out there is to use interfaces/dependency injection/inversion.
- devit 7y agoTesting with mocks is generally a bad idea; instead, one should just test using the same dependencies that are used in production (after testing those dependencies, if also part of the project). This avoids having to introduce the extra interface, having to code the mock, sometimes having to change the mock or tests when changing the code and also lets you catch bugs in the dependencies that the tests on the dependency itself missed and that would otherwise show up in production. One might have to do some work to partially replicate the production setup in testing and making it fast to start and run, but that's usually less work and gives a much better result.
- ludjer 7y agoThat is more integration tests. Unit tests should target logic more then dependencies/integration.
- ludjer 7y agoYou do know you can mock out concrete classes aswell. You do not need a interface.
- commandlinefan 7y agoThe Spring "Framework" essentially demands this, though.
- Eduard 7y agoNot true, and where the Spring Framework <strong>allows</strong> to use interfaces only (i.e. without having to write an implementing class) it's for neat reasons, e.g. autogenerating Spring Repositories.
- playworker 7y agoI think the GP is referring to the use of interfaces with only one implementing class, rather than interfaces with no implementing class. i.e. you end up creating a UserProvider and UserProviderImpl for every service class you write. OP is probably referring to the JDK Proxy support which is used by Spring and requires classes to implement at least one interface.
- wvenable 7y agoSo much over-engineering comes from the fear of changing already written code. Some programmers, I believe, have the impression that changing code any reason other than new features or requirements is wasted effort. So they try and build out all possibilities they can imagine first so they won't have to change it later. If you eventually need that interface, you can change your existing code to use it. Making that change is relatively easy compared to maintaining a bunch of code that isn't needed. But still I find it hard for some programmers to get past that mentality.
- sna1l 7y ago+1 to this In my own progression as an engineer who writes Java, I read Clean Architecture by Robert Martin which lead to AbstractFactory(s) and interfaces up the wazoo. The reasoning would always be "this can change!" It definitely took me some time to understand that not everything will change, and if it does change, having some extra work for simplicity is a reasonable tradeoff sometimes. In general, knowing when to utilize OO design patterns takes time and experience.
- pscanf 7y ago> The reasoning would always be "this can change!" As I understood Uncle Bob's argument for using interfaces though, the main benefit is not supporting alternative implementations, but rather aiding in the enforcement of the Dependency Rule[1] and of the Single Responsibility Principle[2]. Interfaces make the relation between components explicit in two ways: - they make it clear what depends on what - they make it clear what are the responsibilities of the components on either side of the interface So aside from the fact that it might make it easier in the future to swap implementations, it first makes it easier when writing the code to decide how to organize its components (i.e. laying out its architecture). Of course I agree with you that interfaces can definitely be abused, and can make code more indirect and difficult to read. I just wanted to point out what I think is Uncle Bob's point. :) [1] https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html#the-dependency-rule https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-a... [2] https://en.wikipedia.org/wiki/Single_responsibility_principle https://en.wikipedia.org/wiki/Single_responsibility_principl...
- protonimitate 7y ago> I'm strongly against creating interfaces for non-library (not shared) code, that will only ever have one concrete implementation. I agree with this, but the problem I see is that people have bad judgement when thinking about if something will only be implemented once or not. Personally, I don't think the boiler plate / interface bloat is as cumbersome as others do. I'm currently working in a code base where things were decided to "never change", and now we need to change those things. In a perfect world, I'd rather have less abstract code that never changes. But if it's a choice between poorly written highly coupled code or poorly written code that relies on interfaces, I'll take the interfaces and their bloats.
- sfblah 7y agoInterfaces in that case can be useful for unit tests since they enable mocking.
- Quekid5 7y agoYou can do that with classes -- as long the dependents don't instantiate their dependencies directly. However, I personally like interfaces (well, traits, I'm doing scala), because it makes it much easier for readers to understand what bits are supposed to be part of the interface and which parts are supposed to be an internal implementation detail. Without interfaces it's really easy to accidentally change e.g. a return type into a more concrete one which accidentally leaks impl. details and suddenly other bits of code will come to rely on those impl. details. I work on rather large/complicated systems and I've seen this gradual spaghettification time and time again. Disclosure: I'm usually the person who gets called in to fix the mess -- so that might bias my perceptions here.
- tcoff91 7y agoWith Java you can mock concrete classes using mock libraries which can change a class to a mocked class at runtime.
- octorian 7y ago> I'm strongly against creating interfaces for non-library (not shared) code, that will only ever have one concrete implementation. Even if it does, for a mild behavior change or a test implementation, the simple fact that Java methods are virtual-by-default is a huge benefit here. C#, on the other hand, is final-by-default. This makes code much more annoying to deal with unless it implements an interface and/or is thoughtfully written with these cases in mind.
- buzzy_hacker 7y agoYou're actually reinforcing the OP's point. Coding to an interface is ideal but we are reluctant to do so because it involves more boilerplate that is a pain to manage.
- acidictadpole 7y agoIf you know there'll only be one concrete implementation, then yeah don't bother. What people sometimes forget though is that any implementations of this in your tests are also concrete. Some things like a DB accessor, will use a mocked/lite implementation in testing for code that doesn't need access to the db (but still needs to be provided data) to test it's functionality.
- zzbzq 7y agoI strongly agree with this, but if are using an inversion of control pattern for your business logic vs. I/O, then interfaces are often needed to prevent circular references. If using multiple compilation modules, this becomes evident because the code wouldn't even compile without the interface! When using this design, interfaces can sometimes be required more often than not. That's the litmus test I use to determine if I need an interface: Will the code compile without it? If not, the interface is declared alongside the code that depends on it, and the implementation is in a second compilation module which depends on the first. Inversion of control.
- giancarlostoro 7y ago> I'm strongly against creating interfaces for non-library (not shared) code, that will only ever have one concrete implementation. I see it all the time in internal service code, code that will never have a second implementation. It makes code hard to navigate, harder to understand, and tedious to manage as instead of updating one file, now you'll need to update two. I was just telling someone this yesterday. Not every damn thing needs to be an interface you implement if you're only implementing it once. Especially if you have to put "I" in front of it (as is standard in C#) and name the class the same thing, you should think if it's even necessary...
- whycombagator 7y agoI generally agree, but you can have the inferface and implementation in the same file.
- GordonS 7y agoI see a lot of this with C# too, with devs creating separate projects for "contracts" and "implementations" - yet almost every interface has 1 implementation, and will never have more. The rationale I'm given is generally one of two: 1. "It's best practice"/"it's clean architecture" - which is basically dogmatic cargo-culting 2. To support mocking in unit tests The later is also found with unit test projects rammed with mocks, making for brittle, unreadable tests that often don't really seem to actually test anything!
- stouset 7y agoYep. People fundamentally misunderstand how to use mocks and stubs, and often end up writing tests that test little more than “the function was written the way it was currently written”. This is the worst of all possible worlds, because a test suite should accomplish two goals: identify bugs and enable refactoring. Tests like these fundamentally can’t identify bugs because they don’t test any behavior. And worse, they prohibit refactoring because any meaningful code change will alter what functions are called and in what order they’re called. So any refactor breaks almost every test, and it’s impossible to quickly tell if something “real” was broken or if it’s just an artifact of poor testing.
- apta 7y agoIronically, I see this in golang all the time. Interfaces are defined with a single implementation in the code, and then the interface is used to generate mocks.
- imtringued 7y agoI've seen things that you wouldn't believe. "Business Objects" that do absolutely nothing but forward function calls to "Data Access Objects" UserController -> UserBo -> UserBoImpl -> UserDao -> UserDaoImpl. Needless to say the implementations have never been replaced with something else. The middle layer is completely useless. UserBo, UserBoImpl and UserDao should be tossed into the garbage. Every single time you wanted to add a new SQL query you had to edit 5 files... Code navigation features of your IDE become useless and you are better off with string based search.
- Roboprog 7y agoI use the term “monomorphism”, as an antonym to polymorphism. Yeah, not good. And you meant update 3 files, not 2. You forgot the 1 file that uses the 1 implementation, which should have simply been nested the only place it was used.