Discussions have been flying around the ThoughtWorks communication channels on balancing the risk of dynamic languages against the pain of static languages. This got me thinking about the similarities between what makes teams work well and what makes for good code and object interactions.
The mailing lists at ThoughtWorks have been running hot since Jay Fields stoked the flames of the dynamic versus static language schism. His basic point is summarized in the opening line:
“Given a good test suite the return on investment simply does not justify the use of static typing”
Basically he’s saying that statically typed languages have benefits, but there is an associated cost – anyone who’s seen object definitions disappear off the screen with Java 5 generics will understand this. The benefits definitely exist – modern IDEs (auto-complete, refactoring, etc), compiler optimizations, basic verification and clarity in communicating integration requirements – but there is also a cost incurred in terms of complexity and inflexibility. Neal Ford has started calling this the “static type tax”. Jay’s solution is, rather than relying on static typing for partial protection, rely on tests for full protection.
Work to rule
This pain or tax associated with static types reminds me of the parody of union rules gone bad – “I can’t plug that in, I’m a carpenter, you need an electrician”. These rules start out having important uses (protecting workers against abuse and ensuring quality results), but can end up just causing pain and making the jobs they’re trying to protect less secure by making the industry less responsive. There is an obvious correlation with the trade-offs between dynamic and statically typed languages: it’s nice to know from an integration and tooling perspective exactly what type of object a method requires; it’s also nice just to be able to give that same method any object that can respond to the messages it will be sent.
One of the pains of the statically typed method signature is that various different requirements tend to get bundled up together. A method may well require a parameter to be of type (implement interface) X, where interface X defines 10 methods, even though the method will only call one of those declared methods. Ideally you would define a new interface for each combination of methods you want to call so you can keep the contract as simple to fulfill as possible, but this in itself would be a burden as the amount of interfaces would explode.
The arrival of complexity and restriction in the name of preventing accidents (passing an object that can’t react to the messages sent to it) reminds me of how school trips have become harder and harder to organize because of the ever more stringent “risk assessments” required:
“Endless lengthy risk assessments and fears of being blamed if anything goes wrong have put teachers off taking children away on trips and led to youngsters being cooped in classrooms instead.”
I remember loving some fairly wild school or youth club trips as a kid. Rock scrambling was one of my favorites, which involved climbing, jumping, swimming, hopping and whooping round sections of coastline like the beautiful Lulworth Cove. Sure there were risks involved, but learning to handle yourself in those risky situations lead to really important lessons and personal growth. Risk breeds responsibility.
I have similar feelings about using dynamic languages – with great power comes great responsibility. Programming itself is inherently risky (when was your last zero-defect release?) so programmers have to make decisions about how to act responsibly. One way is to impose risk assessment forms on every method call through static typing; another option is to force objects to act responsibly and be good citizens. This is the route that interests me more. Testing definitely plays an important role in acting responsibly, but it’s not the end of the story.
Team collaboration : object collaboration
Explicit and clear communication is the biggest of these – the others can be derived from this principle. The importance of communication in teams is fairly obvious, but it can also be important at the code level in areas like explicit tests, clearly named methods and transparent intentionality.
Simplicity aids communication and also lowers risk. The simpler the routine is, the easier it is to understand, the easier it is to see what is required of collaborating objects and the less likely it is that bugs will creep in.
Responsibility underpins this all. In teams it means talk to people if you’re not sure, or tell people if you’re going to do something strange; in objects it means testing your interactions, handling errors appropriately and checking compatibility where necessary.