Plus the same problem arises with modules instead of objects, which traditionally are even harder to customize.
Plus the same problem arises with modules instead of objects, which traditionally are even harder to customize.
You can get pretty far with good abstractions and dependency injection. Go's io::Reader and io::Writer interfaces are a great example of this. The resulting functions aren't pure in a technical sense, but they're pretty easy to unit test none the less.
> Plus the same problem arises with modules instead of objects, which traditionally are even harder to customize.
Maybe you could elaborate. I really don't understand what you mean here.
From what I understand, modules just scope names, they don't maintain state. I don't see how they have the same problems as objects.
Which goes back to the article's point of having to write code that is unit test friendly.
Now architecture decisions have to integrate interfaces that wouldn't be needed otherwise.
> Maybe you could elaborate. I really don't understand what you mean here.
Modules keep state via global variables, module private functions and the surface control that they might expose via public API for the module.
Additionally on languages that support them, they can be made available as binary only libraries.
> Now architecture decisions have to integrate interfaces that wouldn't be needed otherwise.
You're not wrong.
But in the context of functions, that doesn't seem to me to be particularly onerous. If the worst I'm forced to do is change the type of my parameters to an interface instead of a concrete type, that seems like a pretty small price to pay for easy testability. Certainly a much smaller price than the examples in the article.
Imagine doing unit tests for a C application, where modules == translation unit/static/dynamic library, thus you can only do black box testing.
Now one needs to clutter it with function pointers everywhere, or start faking interfaces with structs, just for the benefit of unit tests.
And with static/dynamic libraries than one might need to start injecting symbols into the linker to redirect calls into mocking functions.
All just to keep QA dashboards green.
The fact that it makes unit testing easier is just icing on the cake.
Mainly due to the linking hacks and low level debugging sessions required to mock all necessary calls.
Plus that was just an example, there are plenty of languages with modules and binary libraries.
The libraries dependencies should all be indirected through whatever context struct you pass to all your calls.
Sadly not all code is great.
You probably want it rigged up to your own logger instead of just blindly writing to stdout. You probably want the library's allocations tagged somehow on the heap so you can track down memory leaks. You probably don't want it doing IO directly, because of how many different way there are to do IO.
It's all more a function of how incredibly varied c envs are, than design for testability. It just happens to be very testable as an aside.
Keep the impure code and the pure code separated.