←back to thread

294 points NotPractical | 7 comments | | HN request time: 0.41s | 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. abrookewood ◴[] No.41857895[source]
Kind of off topic, but can someone explain why else C# has factories and interfaces? Is it just mocking? I really don't understand the pattern at all. FWIW I am no dev.

EDIT: Found xnorswap's comment below about configuration, which makes sense I get - but as they mentioned, it does feel like "turtles all the way down".

replies(3): >>41858290 #>>41858837 #>>41860269 #
2. FroshKiller ◴[] No.41858290[source]
A non-mocking use of mine: I have a factory with a method that returns instances of a particular interface for publishing typed events to a pub/sub service. The caller of the factory doesn't have to be updated with new concrete types as new events are added, because it's the factory's responsibility to create the events. The event types themselves just implement the interface that's required to serialize them for the pub/sub service.
3. criddell ◴[] No.41858837[source]
I've used them in the past to keep interface and implementation separate. It's an easy way to stick an adapter between something concrete and the thing that needs something but doesn't care where it's coming from.

So, for example, I could have a IGadgetStore with methods for creating, retrieving, updating, and deleting gadget instances and then I can have a bunch of different classes implementing that interface. An obvious example is to have a PostgresGadgetStore and a MysqlGadgetstore and CsvFileGadgetStore. If the user wants to implement their own store that I haven't written, they can.

replies(1): >>41864692 #
4. nonameiguess ◴[] No.41860269[source]
I don't think it's off-topic at all. The ideal this kind of thing is trying to achieve is separation of concern. One developer or team or even entire organization is writing things like serializers for specific kinds of file formats or other sources of persisted data like databases and environment variables or things like the Java Springboot externalized config. Another organization is just trying to create an application that requires configuration. They don't necessarily want to have to worry too much about where it comes from. Especially in places that strictly separate development and operations, they'll probably have no say anyway and it'll change over time and they're not gonna want to change their own code when it does.

You can analogize this to non-software use cases. I've got phillips head and flathead screwdrivers and ideally don't want to have to worry about the specific qualities of a particular kind of screw when selecting one. It either has one slot on the head or two. That's the interface to the screw and it should be the only thing I have to worry about when selecting a screwdriver.

Unfortunately, this kind of thing can balloon out of control, and in the worst kinds of "enterprise" Java shops I was involved in deep into my past, where concrete classes were injected at runtime by xml file loaded into the framework, it was literally impossible to tell what code was going to do simply by reading it, because it is impossible to know what is being injected at runtime except by inspecting it during runtime. It's a pretty frustrating experience when reading an entire code base doesn't tell you what the code actually does.

replies(1): >>41862760 #
5. IggleSniggle ◴[] No.41862760[source]
> it was literally impossible to tell what code was going to do simply by reading it, because it is impossible to know what is being injected at runtime except by inspecting it during runtime. It's a pretty frustrating experience when reading an entire code base doesn't tell you what the code actually does.

I've worked in "legacy" nodejs code since node 0.x. Glad to hear that there might be hope of codebases that don't have this problem. I thought typescript would help, but I've learned that fancy generics can ensure that it's still quite possible to have no idea what something will actually do in a real world environment, you'll just have a lot more cognitive overhead in wondering about it.

To be clear, I love ts and fancy generics that try to impose a Haskell-like determinacy on js Object structure with exclusivity and exception guarantees and all the rest; I just also hate it/them, at the same time.

6. abrookewood ◴[] No.41864692[source]
OK that makes sense. As an outsider, the C# code bases I look at seem to do this as standard practice, even if the requirement for different classes never materialises. I guess you get used to looking at it, but it seems (perhaps naively) as wasteful and a bit distracting.
replies(1): >>41864911 #
7. neonsunset ◴[] No.41864911{3}[source]
> it seems (perhaps naively) as wasteful and a bit distracting.

It is.

Nowadays, .NET is usually able to do away with the abstraction cost of such interface abuse luckily, but it remains an additional item you mentally have to deal with, which isn't good.

Single-implementation interfaces are still considered an anti-pattern, and teams that over-abstract and mock everything out when writing unit tests usually just waste time in pursuit of multi-decade old cargo cult. These also often test that modules comprise of specific implementations and not whether they simply satisfy the interface contract, which is terrible. And often turn stateless parts of logic that could have lived on some static class into an interface and an implementation injected with DI, that is then mocked out, instead of just calling methods on a class. More difficult to remove, worse locality of behavior, does not answer the question "if tests are green, are we confident this will work in prod?", sadness all around.

I agree with your sentiment. It's much more practical to write functional and component-level tests with coarser granularity of individual test items, but with more extensive coverage of component inputs. There's a wealth of choices for doing this with little effort (e.g. testcontainers).