... until you write unit tests, at which point you need either interfaces or global mutable function pointers to swap out underlying implementations with mocks.
I don't know much about Go but in Java and C# mocking frameworks use runtime bytecode manipulation to make it really easy to swap out concrete implementations without needing to create separate interfaces.
> I don't know much about Go but in Java and C# mocking frameworks use runtime bytecode manipulation to make it really easy to swap out concrete implementations without needing to create separate interfaces.
You cannot do that in Go, you need to write interfaces upfront. Or you can't unit test a controller that depends on a repository which concrete implementation relies on a database.
There is no "bytecode manipulation" here, since there is no bytecode.
We really need some better solutions to this. Aside from abstraction it also comes with the performance penalty of virtual functions everywhere.
I think we had some good tools to solve this but either threw them away in newer languages or just forgot they were possible. Working with c recently I wanted to mock something and the language obviously couldn't help me so I came up with something along the lines of:
#ifdef TEST
#define foo mock_foo
#endif
This worked for mocking single functions but would need to evolve for more complex code. There are a number of ways to do this from optional includes to compiling test fixtures to their own binaries with the mock implementation being the actual implementation. Both options should work with just about any language to varying degrees of effort.
The biggest barrier to approaches like this is the reliance on IDE's and what they can do. I've never seen an IDE that can handle rules like this, they want to compile the project and control it's structure. In c# for instance, it would be pretty easy to have a mock library with concrete alternative implementations and compile the tests individually, something like:
This is perhaps even easier in c/c++ where the includes are more explicit. Once you take back control of the compilation process a lot more options open up though.
I don't think we're in disagreement here. I have no qualms with abstractions/interfaces. It's simply a matter of putting the horse before the cart. The implementations should not have to tip-toe around pre-conceived abstractions.