Interfaces Generally Belong with Users
neugierig.org
neugierig.org
I'd rather have one global "backend" interface and have mocks use that. If the type system gives me issues with unimplemented methods, than that's the problem with the type system itself -- allowing for partial interfaces (through keywords or something) should be a first-class feature.
It does now strongly couple you to the core interface—you can’t put this function in a separate module that’s unaware of the core interface anymore—and now if the core interface changes, the function implementation itself will break instead of the call sites (which could be a good or a bad thing).
The whole point is to avoid this. Currently in many languages you could trivially generate method stubs that throw errors or some such thing but that still slows down iteration and opts you into far more baggage than necessary.
The approach is that client's should declare their own interfaces and libraries/backend's should implement those interfaces.
- So instead of:
app > depends on > api
- It's api > depends on > app interface
&
app > depends on > app interface
Basically another version of [1] "The Dependency Inversion Principle", which allows you to break hard dependencies into lightweight interfaces.[1] https://en.wikipedia.org/wiki/Dependency_inversion_principle
app > depends on > app interface
service > implements > api
adaptor > composes (api >> app interface)
That way the app doesn't need to know about the API, nor the API about the app. The wiring up the two are managed by the adaptor which is free to be implemented on the client/app side or vendor/service side.It might be hard to get for me as a C programmer because we make manually decoupled interfaces all the time (we create header files explicitly). In a sense all interfaces in C are abstract, and only the linker matches actual implementations to the interfaces. I believe with explicit header files there isn't any of the issues mentioned in the post.
The post is talking about who writes the interface that client code relies on. Is it the author of a package (eg like header files) or the user of the package?
In Go and Typescript its easy for users to write their own interfaces describing just the functionality they rely on. This makes testing a lot easier.
... In Go, Typescript, and many other languages, but sadly lacking in many of today’s most popular languages...
Is there still something like "Backend extends ProfilePage" in Java?
If a class implements all the methods of the interface (as seen at compile-time - same method name, arguments, returns) then that's it, period. Compiler will allow you to use it. Beyond that the interface doesn't "know" about the class and the class doesn't "know" about the interface.
So it doesn't matter the interface didn't even exist when that class was written.
One major notable advantage is that you are checking the existence of every method you may call, not just the ones your tests happen to use.
These are type classes, or what Rust calls Traits.
dang, the joke was so close
Python 3.7 added compile structural typing using what they call Protocols. It works very similar to typescript. See https://www.python.org/dev/peps/pep-0544/
Structural typing.
The instance does not assert to the compiler an exhaustive list of all interfaces it claims to conform to; instead, the compiler determines, during compilation, whether the concrete instance being passed meets the requirements of a declared interface. The net result is that an implementation need not have known about an interface when it was written; it need merely match the interface’s requirements when called upon to do so.
It is, effectively, statically enforced duck typing.
Go's structural interfaces are often harped on, but this is exactly a case where they are very useful. Perhaps an approach is to use named interfaces for variance in implementation strategy, and structural directives for usages as described in the blog post. I wouldn't discount how often this pattern comes up.
ie the client can create a new interface and explicitly implement it for existing classes/structs.
I think Swift and rust allows this.
Though, Go and Typescript are not necessarily the best languages in which to fully embrace this idea. This is a scenario where type inference is very useful.
As other comments say, it can be a hassle to define sub-interfaces for each function, which can lead to a fair bit of duplicated text. But in a language with type-inference for function types (instead of only within-function type inference), the used interfaces can be inferred instead of explicitly written down, providing a lot of flexibility and allowing this principle to be followed in more cases.
>>> the obvious thing is to do in a nominally typed language like Java or C++ is the "extract interface" refactoring, where you create an interface [...]
well, first of all, I still think that a library (even an internal one) should expose only interfaces by definition. It's not only for the sake of the tests, it's also because it forces to keep boundaries "clean".
and by second... what I normally use is to trow in mockito[0] and let it do the subclassing, instantiation and definition of custom methods in 2-3 lines...
I'm not sure that "make it easier to define interface <a posteriori>" it's a good selling point to structural typing
In the specific example given for the given product, a real life developer would write all the code to get the UI up and running, test it works, and probably just ship it.
What do you need to write tests for? Well: there’s nothing wrong with writing tests and a better developer would include at least some testing with their new product feature. They’ll certainly want to know if their product breaks in production, and to be alerted automatically, detecting any regressions.
If you write a test around the backend code with a test double / mock / fake for the backend, then indeed it will be the case that when you break the code that interacts with the “backend”, your test dashboard will alert you with “BackendTest FAILED”. But goodness me it’s a lot of work to make the code testable like that: work to rethink the code into a testable form, and more importantly cognitive load on the next person who comes along as has to read your now not-so-simple backend fetching logic. The linked article certainly shows how to reduce the impact of making the code testable, but you’d still have to make the mock, which is effort as well as more lines of code to go wrong.
A much better tool in this sort of situation is an integration test. It mirrors what you do as a developer when you first write the code, for starters. And if your real life backend is sufficiently stable such that the integration test is reliable, or can at least be ignored when the backend is in a known-broke state, then an integration test combined with hygienic version control is a much better way of isolating regressions to particular diffs.
There are a lot of assumptions needed to make this work — linear commit history, good testing tools, developers making focused and logical commits which change one thing at a time — but having that kind of infrastructure and culture is far more beneficial to an organization anyway, with the bonus that you don’t need to write complex code that is clouded with test infrastructure wizardry mixed in with the business logic.
I've seen such statements here and there, and it makes me feel I'm missing a whole subculture of our industry. Seriously, are we learning things by IDE operations nowadays? Is selecting text and applying an option from the "automatic refactor" menu a first-class programming concept nowadays?
Martin Fowler's book 'Refactoring' gives names a whole bunch of these. That might be where the op is coming from.
As for the example, it feels contrived and unprofessional. There should never be a monolithic "Backend" type. Functionality should be separated into services, each one with an interface, and each one injected dynamically for easy testability and upgradeability.
Sounds good to me, then!
You're wrong.
I think the point the article makes is that you can instead think of an interface as a list of requirements demanded by a user (so your criticism here is a bit of a tautology). The article backs this up by describing concrete advantages of that approach.
Maybe you could share why (or whether) you think these don't apply, or how they contrast with your preferred approach?
No worries, I've read the usual suspects. Taking your example of Gang of Four though (and this applies similarly to the other usual suspects), it definitely does not discuss this observation (and potential trade offs), given it predates common use of structural type systems by far.
I think as professional engineers it behooves us well to prefer actual argument and exposition of engineering tradeoffs over authority ("whole industry does it", "learn from good books").
That really depends on what you want to achieve. e.g. if you want to retain the possibility to fire a whole team and replace it with newcomers, or external contractors, at any point in the development cycle, it is muuuuch safer to stick to what is taught in most schools (which is "tradtional OOP").
Also, I would not use the advice below as a _blanket rule_ as the author puts it.
> interfaces generally belong in the package that uses values of the interface type, not the package that implements those values.
It could be a good rule for many applications, but I find it difficult to swallow as a universal rule. I feel there is so many possibilities where this might not apply, for example perhaps you have a module comprised entirely of interfaces in order to decouple a layer of implementation.
But the author doesn't claim it's a universal rule, the author claims that the rule applies generally. Good thing to think about the cases it doesn't apply, but the author did put it in a proper way to express this.
This pattern violates DRY the hard way. I think most seen example is Go, where stdlib has no "http.Client" interface but only a struct.
So every implementation of http client at some point gets the same copy-pasted code for the interface, so it could use it for that pattern of mocking. I'm not saying it's bad - I'm also doing it. It's just a design decision.
If you're using all/most methods of the class, you'd be better off if the interface was defined directly by the class.
If there are many different users of the same subset of methods, it's better if the class designer declares that interface.
If there are multiple classes implementing the same interface, it's almost crucial that the implementers agree on and define the interface.
So I would say, the statement should be 'in general, interfaces belong in the package that implements value of the interface type; but don't forget that sometimes they can be declared by the code that uses them'.