I went on in this vein for a good year or so. And as the millenium's first decade entered early evening and its bright lights began to fade I took a step back and looked at my code. My god, I thought, this is terrible, what the hell have I been doing? And it was terrible, hundreds of interfaces named somebody's Manager, Persister, Service (oh so many classes called Service), Validator etc. etc., all with a single namesake class implementing them. There were tens of classes whose purpose seemed to be to co-ordinate these little buggers - for they were little, ten or so lines each (oh how I thought I was doing a grand job) - and they would hand off to other similar classes who co-ordinated other classes and it was turtles all the way down. And in amongst all of it there were about a dozen classes that had real names like Customer, Account (but even they had interfaces too) and, aye how the sting of memory pains me to this day, they only had getters and setters (it's the shame, you never get over the shame).
It was with great pain I realized for the last year of my professional life I had been producing procedural code with all the idealistic misguided joy of a card holding Russian worker pouring cheap cement to build pointless roads. I knew the culprit alright: TDD and DI. I had gone decoupling, unit testing mad. The result was an irrational fear of the new keyword. As far as I was concerned the new keyword was banished to the dark depths of Containers, it was a clumsy language feature never to be used again. But what I had instead was a dependency graph made up almost entirely of stateless singletons. A simple Ruby script could have replaced every injected dependency with calls to static methods and sure the application would have lost its testability, it would no longer be decoupled, but essentially, at it's essence, and in terms of structure, it would be the same. TDD and DI had simply given me a fancy way of decoupling static classes.
The new keyword is a wonderful thing. It lies at the heart of OOP. Its sole objective is to create a new instance of an object. If you don't use new you can't create new objects and if you can't create new objects you can't write object orientated code. And the other fundamental thing about objects is they encapsulate behavior and state. Singletons don't. Singletons are procedural. Regardless of whether they are loosely coupled and injectable. Real OO classes have private fields (the fewer the better) and methods which do things based on those private fields, sometimes based on arguments passed in, but always, always in the context of those private fields. And the only way, the one single true way you get values into those private fields is via the constructor and that means using the new keyword. Let's put it clearly: this 'new ValidatedCustomer(customer).IsEmailAddressValid' is OOP. This 'customerValidator.Validate(customer).IsEmailAddressValid' is procedural.
Now I was writing code using TDD and DI that was object orientated code. But my fear of the new was still there. In order to maintain the injectable, loosely-coupled gorgeousness I had begun to do a horrible thing. I started injecting factories everywhere. Sure it was an improvement but there was still something horribly smelly going on. After all the factories were essentially static, singletons whose sole purpose was to delegate to the new statement. I mean there's single responsibility and then there's craziness!
So I started doing something I thought was really bad: I created objects in my classes! But I wanted to keep loosely coupled and all of that wonderfulness. This required a dramatic shift in thinking. Remember when you first did TDD and how it really hurt because you had to structure programs in a different way? Well it was like doing that all over again. Every time I started doing something I had to spend half an hour thinking, how do I make this work? I know I'm right but how?
I learnt a few lessons:
- Not all classes are about interactions, sometimes they are about results. Mocking everything out for the sake of it doesn't gain you anything. Use state based testing to assert the end results not interaction testing to assert what is happening to achieve those results.
- It's OK for your collaborators to new up something if you ask them, so you can say fileSystem.NewFile(name) if you need to.
- Collaborators don't always have to be passed in the constructor: they can be passed as arguments to methods as well, so you can say new File('myfile.txt', 'Some text').SaveTo(filesystem).
- If a class is unavoidably all boilerplate and co-ordination consider using integration tests, after all mocking the interactions of boilerplate code doesn't tell you anything useful at all.
- The container shouldn't contain everything, it should be the things that generally require configuration or are likely to change: databases, filesystems, web services etc. Rarely do core domain classes or business principles need to be 'switchable'.
