The big news this week is that @dhh thinks TDD is bad. Oh my gosh let’s all shake our fists in the air, “He’s so wrong! I love me some TDD!” And I do like tdd, I even wrote a gem called tdd.
But I think the point @dhh is trying to make is following a set of principles without thought can lead to bad outcomes. This I agree with. This is fundamentalism. When we examine other’s fundamentalist attitudes they seem silly, this is the bread and butter of the Daily Show. But when the camera is turned on us, it’s a different story. Some will defend their beliefs to the end.
Testing Isn’t Dead
TDD isn’t dead, nor is testing. The allure of “test first development” is that you suss out a great design and end up with tests. But for me in practice it’s just allure. It ends up being really hard and I start drawing out architecture which doesn’t exist instead of writing code that works. My friend who owns a web development agency said that if they did everything test first, he’d be out of business in 10 days. Their main goal is to ship mostly working software so their customer can play with it, make changes, and move forward. At this point they add tests on the stuff they don’t scrap from the prototype.
I think this is important: you can’t quickly get to a prototype when you unit test the shit out of everything because it makes it really hard to change anything without tediously changing your tests.
Pragmatic Not Dogmatic Testing
Not unit testing the crap out of everything is hard, and it takes time to learn how not to do it. I’ve seen junior devs submit code for review and say, “Boom done, tests and everything!” and they have 5 times the amount of test code as they do actual code. When requirements change the hard part is changing the tests, not the actual code. You have to pragmatic about what to test instead of blindly following TDD principles.
Some questions I ask myself to avoid TDD traps are:
- How do I know when this code is done?
- Can I write a test that tells me the code is ready to ship?
- Can I avoid testing the framework or boilerplate code?
- Will I be able to sleep at night knowing this code is in production?
- What if I had to change the behavior of this class, how hard would my tests be to change?
Curse of the Middle Path
I started reading “The Curse of the Excluded Middle” by Erik Meijer which introduces the idea that sort of doing functional programming is not making your life any better. You must, in his mind, not take a middle path and be a fundamentalist. The world must be black and white.
The world, as I see it, is mostly grey. There are few circumstances where things are clear cut, where the decision just makes itself apparent. Meijer argues you must be:
(a) accepting that programming is ultimately about mutating state and other effects, but for pragmatic reasons tame effects as much as possible; or (b) abolishing all implicit imperative effects and making them fully explicit in the type system, but for pragmatic reasons allow occasional explicit effects to be suppressed.
As a Rubyist, option B sounds awful. It sounds like math. I like math just fine, but programming for me is about modeling the world around us, it’s about modeling social contracts. I pay you money, you give me things.
Rarely in my experience are things easily reduced to functions where the outcome is repeatedly the same. Even if it was, it’s hard for me to reason about the world that way. I’m a social creature who exists inside a large system (the world) which I cannot model in my head at all times. But I do understand relationships within context. I cannot boil down everything to simple functions. Modeling the entire world with functions is the dream of physicists everywhere “if we just knew the position and velocity of every particle in the universe we could predict everything!”
But we know this is not (yet) possible. Even if we could model the universe this way we couldn’t understand it. The simulation would be as complicated as the real thing. Fiercely simple functions at a large scale can act very strange. This is the basis of Chaos Theory. You’ve probably seen a representation of this in Conway’s Game of Life or Sierpinski’s triangle. To really see how far this rabbit hole goes check this out: http://www.oftenpaper.net/sierpinski.htm
This is where I think OO has an advantage. It allows us mere humans the ability to reason about a part of a large system by understanding relationships. But, functional programming has a place too. We need to be certain, in some cases, the outcome is always the same and having to pick a side is a waste.
Scala Is a Middle Path
I recently took a class on Scala. We use Scala at work and I made mention I’d like to know how it all works. Holy options. There’s at least three ways to achieve the same thing in Scala both in the object oriented way and in the functional way. Scala is really terrible to read and understand later, but damn, you sure feel clever writing code which is going to take someone, likely yourself, a lot of time to decode in the future. It’s this weird mix of OO and FP, I think they get a lot of things right but I can be really hard to understand the FP side of things.
And I think this where Meijer is drawing a line in the sand. He’s saying, you can only see the world in two ways, OO or PURELY FUCKING FUNCTIONAL. If you try to do both, you’ll end up with a mess. And this is the same as people saying that TDD IS THE ONE TRUE WAY. It’s not. People create kick ass projects writing some tests, or testing after the fact. Some people make kick ass products in Scala (like uh… Twitter). But you, the user of these ideas, techniques, or frameworks are left with the careful task of not DoingBadThings™. There is no one true way which buys you both safety from DoingBadThings™ and a kickass product, but maybe we can learn from these fundamentalist crazy people.
When confronted with fundamentalism you have to ask yourself a few questions:
- Is this fundamentalist approach going to help me achieve my goals?
- Is there a middle path that is easier?
- What can I learn from these far flung crazies?
Often I find that fundamentalists may have some merit in their ideas. They must think their idea is awesome and have had some success with it to shout it from the rooftops (but cognitive bias is a strange animal). It’s up to us choose the right path. This is challenging because it’s easy to say “Well that dude over there told me there were answers so I just started doing what he told me.” It’s a struggle to evaluate the good and bad of a new way of doing something or a new way of thinking about things, but the outcomes are real and tangible. I wouldn’t be where I am today without picking up Ruby and Rails and following some of its dogma. I also wouldn’t be where I am without picking up some dogma about testing. I’m now considering how I can be more successful with some functional programming in my life.
We are adverse to failure, but I urge you to pick up some ideas, try them out, and throw them away when they stop working.