Most active commenters
  • xnorswap(3)
  • throwaway365x2(3)

←back to thread

294 points NotPractical | 11 comments | | HN request time: 0.001s | source | bottom
Show context
xnorswap ◴[] No.41856752[source]
> Redbox.HAL.Configuration

> .ConfigurationFileService implements IConfigurationFileService

> STOP MAKING SERVICES AND FACTORIES AND INTERFACES AND JUST READ THE FUCKING

> JSON FILE YOU ENTERPRISE FUCKERS

I know it's cool to "hate" on OO, but "just read the fucking file" doesn't work if you want to run your unit tests without reading a fucking file.

It makes sense to abstract configuration behind an interface so you can easily mock it our or implement it differently for unit testing.

Perhaps you also want to have some services configured through a database instead.

This isn't a ConfigurationFileServiceFactoryFactory.

replies(12): >>41856822 #>>41856831 #>>41856836 #>>41856965 #>>41857895 #>>41858054 #>>41859117 #>>41859509 #>>41859750 #>>41859882 #>>41860221 #>>41864182 #
1. xnorswap ◴[] No.41856831[source]
Could or should there just be a `IConfigurationService` instead of a separate IConfigurationFileService? Yes, probably.

"Interface all the things" is a bit lazy, but it's easy, especially if you have Moq as a way to auto-mock interfaces and a DI framework to setup factory methods.

But spinning into rage just because you see an interface or abstract factory isn't healthy.

replies(2): >>41856869 #>>41861534 #
2. throwaway365x2 ◴[] No.41856869[source]
I don't follow .Net closely, but it seems like there should be a better alternative. Java has a library called "Mockito" that can mock classes directly without requiring an interface. I assume something similar exists for .Net, as they have similar capabilities. Making an interface for one class, just so another class can be tested seems like we allow the tool (tests) to determine the architecture of what it is testing. Adding complexity in the name of TDD is a close second on my list of triggers

There's nothing that triggers* me more than seeing an interface that only has one implementation. That's a huge code smell and often a result of pre-mature architecture design in my opinion. It also often leads to complexity where if you have an interface, you create a factory class/method to instantiate a "default" implementation. Fortunately it seems that it is not used as often as before. Our code has no factories and only a few interfaces, that actually have a practical use. The same applied to my previous workplace

* The trigger applies to 2024 Java code written as if it was 2004. I may have a form of PTSD after many years of interfaces and FactoryFactory, but fortunately times have changed. I don't see much of that today except in legacy systems/organizations.

replies(2): >>41856928 #>>41858730 #
3. xnorswap ◴[] No.41856928[source]
I'm sure the same exists for .NET ( Moq can probably do it? ), but writing against an interface and having concrete implementations supplied by the DI framework is pretty much the ordained way to do things in .NET.

I used to be in the "Interfaces with only a single implementation is a code smell" camp, but I prefer to follow the principle of least surprise, so going with the flow and following the way the MS standards want you to do things makes it easier to onboard developers and get people up to speed with your code base. Save "Do it your own way" for those parts of the system that really requires it.

And technically the auto-generated mock is a second implementation, even if you never see it.

replies(2): >>41857064 #>>41857205 #
4. throwaway365x2 ◴[] No.41857064{3}[source]
I think you have good approach. I also tend to go with the flow and follow the common practice. If I tried to do "Java in C#", it would make it more difficult to follow my code and decrease maintainability.

I sometimes work on legacy C# code that we inherited from another team and I try to follow the style as close as possible. I just haven't invested enough time to make any informed decisions about how things should be.

replies(1): >>41857197 #
5. Jerrrrrrry ◴[] No.41857197{4}[source]
You are both over thinking it.

GIT? unit tests? and i thought debuggers spoiled us?

although cavemen-esque in comparison to 'modernity'; it wasn't a nightmare to Pause/resume program flow and carefully distill every suspected-erroneous call to Console.Log(e)/stdout/IO/alert(e)/WriteLine(e); `everything to find the fun/troublesome bits of one's program - instead of a tedious labyrinth of stack traces obfuscating out any useful information, further insulted by nearly un-googable compiler errors.

Tests were commented out functional calls with mock data.

If you never need to instantiate another instance of a structure so much so that it would benefit from an explicit schema for its use - whether it be an object or class inheritance or prototype chain - then sure, optimize it into a byte array, or even a proper Object/struct.

But if it exists / is instantiated once/twice, it is likely to be best optimized as raw variables - short-cutting OOP and it's innate inheritance chain would be wise, as well as limiting possibly OOP overhead, such as garbage collection.

  >interface in C#
Coincidentally, that is where my patience for abstraction for C# had finally diminished.

yield and generators gave off awkward syntatic-over-carmelized sugar smell as well - I saw the need, to compliment namespaces/access modifiers, but felt like a small tailored class would always outweigh the negligible time-save.

replies(1): >>41858351 #
6. Kuinox ◴[] No.41857205{3}[source]
Moq® cannot do it. I forked Moq® and made a library that can mock classes: https://github.com/Kuinox/Myna. It can do that by weaving the class you mock at compile time for your mocks (you can still use your class normally).
7. Bjartr ◴[] No.41858351{5}[source]
I love the idea of syntactic engagement gone too far as "burnt sugar"
8. pylua ◴[] No.41858730[source]
What’s wrong with having an interface with one implementation ? It’s meant to be extended by code outside the current repo most likely. It’s not a smell in any sense.
replies(2): >>41859526 #>>41863712 #
9. klibertp ◴[] No.41859526{3}[source]
90% of single-implementation interfaces (in Kotlin on Android projects I've seen) are internal (package/module private, more or less.) So no, they are not meant to be extended or substituted, and tests are their only raison d'etre (irony: I've almost never seen any actual tests...) This is insane because there are other tools you can use for testing, like an all-open compiler plugin or testing frameworks that can mock regular classes without issues.

An interface with a single implementation sometimes makes sense, but in the code I've seen, such things are cludges/workarounds for technical limitations that haven't been there for more than a decade already. At least, it looks that way from the perspective of a polyglot programmer who has worked with multiple interface-less OOP languages, from Smalltalk to Python to C++.

10. gregmac ◴[] No.41861534[source]
Yeah, IConfigurationService implies separation of concern. Code using it doesn't have to care where the configuration came from, just that it is there. Someone separately can write the concrete ConfigurationFileService:IConfigurationService that reads/parses files.

IConfigurationFileService implies abstraction of file system-based configuration. Are we planning that there's going to be a different way to read configuration files in the future, and what exactly is that? If no one can articulate it, it just seems like architecture astronautism and: YAGNI.

IConfigurationService makes writing unit tests for anything that uses it way easier, too. There can be a simple TestConfigurationService:IConfigurationService that just implements everything as settable, and in your test code you can provide exactly the properties you need (and nothing more), and easily have 100 variations of configs to ensure your code is working. Without the headache of dealing with actual files separate from your test code, or worse, shared with other test code.

I've actually written multiple long-lived pieces of software this way, and more than once ended up implementing stuff like environment variable-based configuration, REST API-sourced configuration, and even aggregations that combine multiple sources, eg:

    new AggregateConfig(new ServerConfig("https://whatever"), new EnvironmentConfig(), new FileConfig("/some/path.config"));
All that code that used IConfigurationService is completely untouched and unaware of any of this, letting whoever is doing this as part of changing deployment (or whatever) be productive quickly with very little knowledge of the rest of the (possibly massive) app.
11. throwaway365x2 ◴[] No.41863712{3}[source]
In that case you have more than one implementation, or at least a reasonable expectation that it will be used. I don't have a problem with that.

My comment was regarding interfaces used internally within the code, with no expectation of any external use. I wrote from a modern Java perspective, with mockable classes. Apparently interfaces are used by .Net to create mocks in unit tests, which could be a reason to use that approach if that is considered "best practice"