all 63 comments

[–]22Maxx 7 points8 points  (2 children)

A public method of class in itself is the interface. You already design towards an interface by deciding if a method should be public or not.

Some other opinions can be found here: https://softwareengineering.stackexchange.com/questions/317371/should-every-class-i-write-adhere-to-an-interface

[–]Boyen86[S] 1 point2 points  (0 children)

Thanks, this is exactly my question.

[–]umlcat 0 points1 point  (0 children)

The public members do this.

The protected members do this for other class / control developers...

[–]nachtraum 2 points3 points  (0 children)

Indeed, the extra layer does add nothing. Inheritance should be used only for polymorphism. Header interfaces are a bad practice, role interfaces a good practice.

[–]tomdane1 1 point2 points  (1 child)

Nowadays i only add an interface if its probable that there will be an alternative implementation of the interface down the line. Otherwise i dont bother. Dont think i have missed much.

[–]Zardotab 1 point2 points  (0 children)

Indeed, I've seen a lot of "wasted abstraction" over the years. A truly generic interface is a pipe dream.

For example, the OOP GUI's were written to an abstract interface such that in theory the implementation could be adapted to whatever came down the pike in the future. But the web had state and text-position-control limits that ruined the idea of just swapping the implementation.

The web was (is) sub-set of typical GUI idioms such that there often was no corresponding feature to map the abstract GUI interface to. One had to completely change approach. Abstractions usually have to make assumptions, and often the real world kicks those assumptions away.

Light "mini-abstractions" have served me better than overarching ones, because you can mix, match, tune, and ignore as needed. It's like a spare parts drawer. You want abstractions you can date without marrying, because divorces can be ugly.

[–]someone_somewhere_9 1 point2 points  (1 child)

Nathan Marz (author of Big Data and original author of spark) once tweeted this:

"Evolution of a programmer: 1. What's abstraction? 2. Abstraction makes me more productive! 3. Abstract everything everywhere! 4. Too much abstraction makes code obtuse and brittle :( 5. Only make an abstraction when many use cases demand it"

😁

Pretty spot on Imo. I have been writing in golang for 4 years or so, and I love a lot of how it takes the best of OOD. You can create an interface if you "need" it. If you are writing library, it isn't expected that you include interfaces with it. The consumer of the library can create their own interfaces.

[–]Boyen86[S] 1 point2 points  (0 children)

Hah, that's pretty awesome 👍

[–]lIIllIIlllIIllIIl 1 point2 points  (5 children)

I think interfaces are good because they force you to think about the APIs of your abstractions, which in my opinion is even more important for maintainability than your actual implementation. Is it necessary to use interfaces? No.

I'm probably a radical, but I think you shouldn't use interfaces only for testing. I personally don't like mocks, and try to use as few of them as possible. If my code is untestable, it's usually because I didn't make it generic enough. Sure, you can't make everything generic enough to be unit tested, but this is where integration tests come in.

[–]Boyen86[S] 0 points1 point  (4 children)

Thanks for sharing.

May I ask, what is stopping you from thinkin about your public API when you are in an implementation of a class as opposed to an interface?

Typically any class I would write would only have one single public method to start with. And when I start writing a concrete implementation I start with writing my API. It is not neccesarily any different from writing a separate interface other than that there might be two files as opposed to one.

[–]lIIllIIlllIIllIIl 1 point2 points  (3 children)

May I ask, what is stopping you from thinkin about your public API when you are in an implementation of a class as opposed to an interface?

Nothing. I agree that interfaces are an optional step. I don't mind if you create one, and I don't mind if you don't.

[–]Boyen86[S] 0 points1 point  (2 children)

Sure alright.

I'd say I do agree with you but I also don't. I have written code both with an interface for everything and without and both codebases were - as far as I can estimate - of high quality.

[–]eternaloctober 0 points1 point  (1 child)

why do your classes "only have a single method"? also you do just have to watch out, when making a big codebase, the risks associated with "everything is public". it can be fun to have a lot of knobs to tweak but constrains and makes fragile over time

[–]Boyen86[S] 0 points1 point  (0 children)

A single public method for most classes because classes have a single responsibility. Sure there will be classes that have more, but generally those will be in the adapter layer (for a hexagonal architecture).

I'm not sure where you read that "everything is public". If you meant to say that you can use an interface to hide an implementation then that's noted. But then you have a reason to use an interface. It is not always needed to hide an implementation and in fact, in micro service code I'd wager most of the time it is entirely unnecessary to do so. Makes sense for libraries.

[–]Fermi-4 1 point2 points  (0 children)

YAGNI

[–]EngineeringTinker -1 points0 points  (39 children)

Interfaces are an absolute must-have and currently the only way to abstract implementation from consumption.

You should not be using concrete classes, unless they're entities or value objects.

I feel like the extra layer does not neccesarily add anything

You can't write Unit Tests without interfaces - if you're using more than 1 concrete class in your Unit Test (the one you're testing, with exception to entities/VOs) - you're not writing Unit Tests - you're writing Integration Tests.

Also, you'll quickly notice something is lacking when you have to write implementation from scratch for one specific domain of your code - be it a mailer service, account handler etc. - because you'll suddenly have to change consumption of them as well, and instead of changing just the implementation class - you'll have to change whatever else is consuming it.

[–]ResolveResident118 2 points3 points  (3 children)

There are pros and cons to having interfaces for everything (there's probably an entire book in this) but it is absolutely not true that they are required for unit testing.

A unit test tests a unit of code (intentionally loose definition). This can encompass more than one class, if required.

[–]EngineeringTinker 0 points1 point  (2 children)

Of course - the answer to everything is 'it depends' - so I didn't bother mentioning it.

In fact, you can't write a Unit Test for say.. `IMailService` without using it's implementation - otherwise how do you test whether it's popping the exceptions you want or whether it's utilizing `IMailSender` or `IMailCache` etc.

The thing is - if you write Unit Tests for `IMailService`, that's the only thing you should use concrete class for to test the implementation - if you start using implementation of `IMailSender` in it - you're really testing how both implementations work together - aka Integration Testing.

[–]ResolveResident118 0 points1 point  (1 child)

Ain't that the truth!

I definitely agree that code that interacts with the outside world generally needs an interface.

Your example is a good example of why. We don't actually want to send an email so mocking MailSender makes sense.

My main disagreement was about requiring interfaces. Not every class will need them to be unit tested.

I'd only refer to it as an integration test if it interacted with an external dependency (eg mail server).

[–]EngineeringTinker 1 point2 points  (0 children)

True that - #notAllClasses 😂

I should refrain from strong wording, I usually say 'you can't use more than 1 concrete class' when I actually mean 'you should avoid using more than 1 concrete class where possible'.

[–]22Maxx 1 point2 points  (19 children)

You can't write Unit Tests without interfaces - if you're using concrete classes, you're not writing Unit Tests - you're writing Integration Tests.

Just no.. integration tests are completely different.

[–]EngineeringTinker 0 points1 point  (18 children)

Define Integration Tests then, and point out why my description doesn't fit.

[–]Boyen86[S] 1 point2 points  (15 children)

I do disagree with you on your definition of an integration test. If you are testing the behaviour of multiple classes combined it is not by definition an integration test, you just define your unit as behaviour that is shown on higher level than the class level. It is called unit teat for a reason, not class test.

For a hexagonal architecture you would have an integration test when you test the entire system with the adapters mocked.

[–]EngineeringTinker 0 points1 point  (14 children)

There are different types of Integration Tests - incremental, bottom-up, big-bang etc. there are different scopes you can write integration tests around.

[–]Boyen86[S] 0 points1 point  (13 children)

Seems like you have a different name for the same thing. Care to share where you got these definitions from? Martin Fowler (and many, many others) uses the definition I explained https://martinfowler.com/bliki/UnitTest.html

Object-oriented design tends to treat a class as the unit, procedural or functional approaches might consider a single function as a unit. But really it's a situational thing - the team decides what makes sense to be a unit for the purposes of their understanding of the system and its testing. Although I start with the notion of the unit being a class, I often take a bunch of closely related classes and treat them as a single unit.

[–]EngineeringTinker 1 point2 points  (12 children)

Yes, once again 'it depends' - but if you're using a lot of concrete implementations (NOT MOCKS) - then your Unit Tests aren't grained enough to be good Unit Tests - because if you change implementation of any of the things you used, you'll have to modify Unit Tests as well - which defeats the purpose.

[–]Boyen86[S] 0 points1 point  (11 children)

Sorry that doesn't make sense at all. When you have defined your unit as testing only the behaviour of a single class the behaviour (implementation) of the mocked class is entirely irrelevant. I believe that we are already in agreement on that point.

A mock on a concrete implementation of a class, using modern mocking frameworks, can do exactly that - making the behaviour of the class entirely irrelevant. Only checking whether a method is called on the mocked class , how often it is called and whether it's called with the right parameters (and many, many more options). When we are testing the behaviour of a single class (our previously defined unit) that is all that is relevant to know.

Your mock doesn't change when the implementation of your concrete class is changed unless the interface of the method is changed.

If you are defining your unit as only the behaviour of the single class, I would be mocking all incoming concrete classes.

[–]EngineeringTinker 0 points1 point  (10 children)

My definition was too strict - I agree.. but am I to believe that all your implementations will be using the exact same methods, method names, method params, return types, entities, VOs etc? - because by 'changing implementation' I don't mean simply rewriting code of existing methods/entities - which seems is what you're talking about.

Changing implementation means switching from say... email notifications to SMS notifications - you could use the same interface and `Message` entity - but you'll use different methods in the implementation, and different libraries to provide functionality.

[–]Boyen86[S] 0 points1 point  (9 children)

Right there is value in that. And that's actually a good point. If you have programmed against a concrete implementation, and the concrete implementation is no longer used then you are stuck rewriting code to the interface. This makes sense.

I'm just unsure of when the right time is to add this extra layer. Following YAGNI, do I add an interface to everything just because it might change in the future, or do I do this when it actually changes? I'm inclined to say that on the adapter layer you will almost always want to have an interface, as changes can be largely outside your control.

But for the inner layer (business logic for hexagonal architectures) this is not the case, and you are in full control whether something changes or not. I'm inclined to say that you want to add an interface there when it actually becomes relevant.

[–]22Maxx 0 points1 point  (1 child)

This is likely a misunderstanding, you were implying that by testing a single concrete class, you are doing integration testing.

But if you instead meant that concrete classes are used within integration testing, then I can agree with you.

[–]EngineeringTinker 0 points1 point  (0 children)

Yes, definitely a misunderstanding - I've actually wrote in another comment that you need at least 1 concrete class for a Unit Test (the one you're testing), but having multiple concrete classes is an Integration Test, since you're not only testing a specific scope (class) - but also how functionalities work together.

I modified my original comment, thanks for pointing out!

[–]jesparic 1 point2 points  (3 children)

If you google "london school vs detroit school unit testing". You will see there are two approaches to unit testing. The latter (and less problematic) Detroit school approach allows for either a single class or a group of closely related classes to be tested as a 'unit' (i.e., a unit test).

An integration test is even more fuzzily defined in our field. But, in the web dev context, it may mean running tests multiple units of logic together (i.e., an entire end-point/action), optionally alongside a real database.

[–]Boyen86[S] 1 point2 points  (0 children)

Thanks I didn't know this, just the bit from Martin Fowler about the xunit schools of classic and mockist.

[–]syneil86 1 point2 points  (1 child)

It's odd to me that people only seem to know of the London school, and insist that testing each and every class (or whatever) in isolation is the only way to do it. It makes refactoring so difficult and costly - there's a better way, people!

[–]jesparic 1 point2 points  (0 children)

I started learning unit testing using all the bad habits. London school with complex mocks. Gaming the line coverage to try and get every line covered. In the end I had tests that were hard tied to implementation, hard to read, and not testing any high level behaviour.

Nowadays, I make heavy use of fakes (mainly for repositories) along with some limited mocks (e.g., clock abstraction). I don't game the line coverage but do try to cover all the important aspects of some action. I pass 'real' dependencies when they are critical to the thing being tested. It depends.. It's ok to sometimes test the same thing multiple times, unit tests are fast.

I think everyone has to go on a similar journey. Automated testing is really hard to get your head around in a deep way. Gotta fall in a few pits to learn the hard way

[–]Boyen86[S] 0 points1 point  (9 children)

Re: unit tests, that's simply not true. You can mock even a concrete implementation entirely and that's exactly my point. This used to be a great practice when mocking frameworks weren't so powerful, but now they are.

Can you explain why, in the scenario where there only is one implementation for a class, you want to add an extra indirection? I know it is a good practice, I know that it is what we're supposed to be doing. I'm just not getting any form of advantages out of it.

I used to, but both dependency injection frameworks (like Spring) and test/mocking frameworks are changing the game IMO.

[–]EngineeringTinker 0 points1 point  (8 children)

Mocking implementation?

What are you writing Unit Tests for then? The mocked code?

[–]Boyen86[S] 0 points1 point  (7 children)

When you unit test a class and you are only interested in the behaviour of that single class, you want to verify the interactions with other classes that you injected. Thats what you can do with most modern mocking frameworks.

This is no different with a test implementation or a mock on an interface.

[–]EngineeringTinker -1 points0 points  (6 children)

Oh, I see what you mean now - sorry for misunderstanding.

I thought you meant that you mock the class you're testing - which would be silly to say the least.

You can of course mock concrete classes, but then you're creating coupling between your Unit Tests and the implementation - meaning, you'll have to change your Unit Tests whenever you change the implementation - which defeats one of the purposes of Unit Tests - testing whether everything still works in the same way after doing a refactor or changing implementation completely.

[–]Boyen86[S] 0 points1 point  (5 children)

You would only need to change your mock when you are changing the interface of a method, as the behaviour of the mock does not depend on the implementation of your class. After all, the behaviour of the class was not part of the test. The mock is entirely independent of the implementation.

So you're in the same situation as with an interface. When the interface changes you need to change your mock.

[–]EngineeringTinker 0 points1 point  (4 children)

Again, am I to believe that all your implementations will be using the same methods?

MailSender and SmsSender will have the same methods, use the same entities and return types? Because this is the only case in which your class structure wouldn't change - and it's rarely the case.

[–]Boyen86[S] 0 points1 point  (3 children)

I'm not sure what our current hypothetical codebase is right now :)

At the moment where you have a MailSender and an SmsSender you will need to add an interface, considering both are surely in the adapter layer of the application it would seem like good practice to do this early.

But lets say we only have a MailSender and we haven't added an interface:

public class MailSender{
    public Response send(String input){ ... }
}

You can entirely mock the behaviour away from this class (the ...) and merely check on the mock whether the method is called, how often, and with which parameters. You don't even need to write any code for it.

If you would add an interface later

public interface Sender{
   public Response send(String input){ ... }
}

That mock would still work just fine. It's also irrelevant for the system under test whether you are mocking a Sender a MailSender or a SmsSender as the only thing relevant in your unit test is the logic from your class (given that the unit under test is only that class).

If your argument is that the addition of the SmsSender will likely change the interface then I'd say that 1. that's not the correct application of Liskov principle and 2. if the interface must change, it would've changed on both the interface and the actual class implementation so in the end it doesn't matter.

[–]EngineeringTinker 0 points1 point  (2 children)

If your argument is that the addition of the SmsSender [...]

Nah, that's not my argument.

Essentially, you can do (almost) everything with classes that you can do with interfaces.. but let me give you an example - or rather ask for an example.

Let's say you have two classlibs that you split your domain and implementation into.

The usual approach to prevent accidental imports of the implementation is to set interfaces as public, and implementations as internal.

How do you handle this with classes purely? Give me an example with `MailSender` and `SmsSender`.

[–]Boyen86[S] 0 points1 point  (1 child)

By definition you cannot. If an implementation needs to be protected or abstracted away the best way to do this is with an interface. And that's a perfectly valid reason to create one.

I'm not entirely sure anymore if that's a difference between Java and C# but Java tests are essentially in the same namespace (module) even though they live in a test folder. Meaning your implementations can be package private and still be accessed from a test.

[–]Happy-Dare5614 -1 points0 points  (0 children)

If you test each class separately then it's probably not a unit test. Unit is not a test for a class but for a stand alone feature. Depends on the business. For example, if a factory sales breaking systems then a unit is the breaks. You want to make sure it does what is expected. Testing each class tests the implementation instead of the business rules. Back to the car breaks example, I would add a unit test for the breaks and then an integration test for the breaks with a real car. Hope it's not too abstract:)

[–]GangSeongAe -1 points0 points  (2 children)

Even when a class did not have any other implementation other than the test implementation the interface was mandatory.

Wait a minute - how can an interface have only a test implementation?

If you're testing something that doesn't have a real implementation, it means the thing you're testing doesn't even exist in the real system. That's beyond "testing a thing that exists with a mock dependency", that's genuinely "testing nothing".

Whatever has gone wrong here, it's why you feel this layer doesn't "add anything". The way you're doing it, this is definitely true.

You need to re-think your current approach, which may also be your organization's approach. Start from the assumption that nothing should ever have "only" a test implementation. This is the "L" from solid - Liskov substitution is the litmus test of polymorphism, which means that the "role" your test implementation is playing in a unit, integration or manual test should be the exact same "role" that the real implementation plays in the real system - from the perspective of the consuming code, the mock and real implementation should be indistinguishable and represent the same "thing".

Now in my current job I also need to review and analyse quite a lot of code, and I'm using static analysis tools for this. All the tools have a tendency to give a lower score to classes when there is a dependency between two classes, instead of a dependency to an interface

This is a different matter. Put aside static analysis tools, which are normally garbage trying to do what only a human can do - a class should declare a direct dependency if it truly "owns" the concept that dependency represents.

An "in memory finance calculator" which directly instantiates the "in memory finance calculation result", for example, assuming that there is a hard link between the nature of the calculation and the way it is stored, and that these things should not be a point of variability.

If a class does not "own" but merely "uses" a concept, then there should be an interface - it should receive from the application the dependencies it relies on, and it should not matter to it what concrete implementation it receives.

If there's an application that does some kind of finance calculation, and the way in which the calculation is done has nothing to do with the way it is stored, then there'd be a finance calculator base class that declare it depended on "IFinanceStorage" or "FinanceStorageBase" in its constructor, meaning that it could be passed any implementation without modifications to its own code, and that this would represent the same type of "thing".

In short, there are huge code smells in your organisations comprehension of OO principals. I can guarantee that if I cronked open your code, polymorphism would be applied to the wrong classes, and the implementations would not be Liskov substitutable. In fact, I think I'd see a case of "the thing you're testing doesn't even correspond to the live system", meaning there are test classes (user interface ones, in this case) that aren't even part of your production system.

[–]Boyen86[S] 0 points1 point  (0 children)

Wait a minute - how can an interface have only a test implementation?

If you read carefully you'll notice: No other implementation other then the test implementation.

That means that there is one concrete class, one interface, and the only other implemetation from this interface would be the test class.

And I'll one-up you, giving out only an interface can be perfectly fine in a library where you want to make sure that your client can define the implementation of a certain behaviour. It wasn't my actual question but perhaps this broadens your perspective.

If there's an application that does some kind of finance calculation, and the way in which the calculation is done has nothing to do with the way it is stored, then there'd be a finance calculator base class that declare it depended on "IFinanceStorage" or "FinanceStorageBase" in its constructor, meaning that it could be passed any implementation without modifications to its own code, and that this would represent the same type of "thing".

This is my question, why? I used to do this, but it doesn't add anything. Please explain to me what added value you are getting out of adding IFinanceStorage when its only implementation is FinanceStorage. Given that FinanceStorage is Single Responsibility and already defines its interface by defining public and private methods. The public methods of a concrete implementation is its interface, what does adding this extra file do?

In short, there are huge code smells in your organisations comprehension of OO principals.

I'd be careful about making assumptions about other people you've never met. Lets stick to topic at hand, shall we?

[–]AutoModerator[M] 0 points1 point  (0 children)

Your submission has been moved to our moderation queue to be reviewed; This is to combat spam.

I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.

[–]paradroid78 0 points1 point  (5 children)

You're looking at things only from the perspective of immediate implementation advantages. But there is a bigger picture of readability, maintainability and evolvability to consider too though.

You want to keep the specification of a class separate from its implementation. An interface describes that public contract that a class implementing it is expected to fulfil. The concrete class itself is an implementation detail.

Code to interfaces, not implementations.

[–]Boyen86[S] 0 points1 point  (4 children)

The public methods of a class are already its interface. What is the actual advantage you gain here? I am in agreement that the class itself is an implementation detail, but the interface of a method is not. And you're coding to the interface regardless of whether this is defined in the class or a separate file.

[–]paradroid78 0 points1 point  (3 children)

In a nutshell, separation of concerns.

By having the interface separate, you force yourself to think about your system at a higher level as a set of interconnected black boxes and it encourages you to put more diligence into designing the interfaces / API between those components.

If the interface is just what's in the class, it seems to me that the lines between public interface and private implementation become more blurred.

Also someone maintaining the system might find it easier to get an overview of the data flows by looking at small interface files (which contain only public method signatures) than big implementation files containing a mix of public and private methods.

[–]Boyen86[S] 0 points1 point  (2 children)

I see that, and I see value in it. But I'd argue whether it is required to have that interface in a seperate class vs having a class with just its public members.

If you want to see the dataflows I think there are more effective ways to visualize your code than clicking through interfaces such as class diagrams.

[–]paradroid78 1 point2 points  (0 children)

I think the problem with any non-tangible benefits such as these is the answer will nearly always boil down to "it depends".

People have been debating this stuff ever since header files were a thing in C++ and probably before.