Programming considered harmful

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.

In strong and statically typed languages, like Java, the method signature is a shorthand explicitly stating what type of object is required for the method to work. In a dynamic or weakly typed language, like Ruby or JavaScript, you have more flexibility about what you can pass into the method, but less visibility into whether it will work or not – you have to check (visually or through tests) that the object you pass in can respond sensibly to the instructions it’s given.

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.

Risk assessments

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

Coming back to the analogy of objects as workers on a job, rather than using strict role definitions in an attempt to control quality, I would much prefer to use the example of self-organizing Agile teams. On an Agile development team there is an expectation that people can work in a cross-functional manner with common ownership of the code-base. This means that team members should be interchangeable and comfortable in a variety of areas – just like in dynamic languages there is no type checking before picking up tasks (“sorry I only do JavaScript / Java / SQL”). This leads to highly flexible and productive teams, but there are a few techniques needed to make it work. Some of these techniques are conceptually transferable to working responsibly with dynamic languages:

  • communication
  • simplicity
  • responsibility

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.

5 Replies to “Programming considered harmful”

  1. And what do you think about complexity of tool support? It’s easier to create robust rename, move etc refactorings for languages like java than for dynamic languages. These refactorings save a lot of time for developers which can be larger than time saved by skipping static bureaucracy.

    There are also thing like structural subtyping and type inference which can be statically typed. They solve problem of cryptic generic annotations and large number of interfaces. Mainstream languages don’t support them yet but I think it’s a matter of time.

  2. @Konstantin: I definitely believe that static typing has many benefits – I mentioned auto-complete, refactoring, etc as examples of this. There are many arguments on both sides of the benefit / pain discussion around static / dynamic languages – and the balance changes as well when you use something other than Java (and all it’s legacy baggage) as your reference point. Haskell’s typing system seems to throw some interesting extra ideas into the mix.

    The point of my article wasn’t to say that static typing is bad or dangerous (Jay did say that though), rather I was trying to see what is the best way to act sensibly and responsibly if you choose (or have been chosen) to work on a dynamic language. I actually think that as programmers we’re going to have to get used to coding in more languages as time goes on, so finding out how to be effective in a range of types of language is useful.

    When I started writing Ruby I definitely missed some of the refactoring support I was used to in Eclipse – I felt like I was back in the 90’s with a text editor and javac … however now there’s not much I miss. I’m not going to make the argument that the “static type tax” of Java actually requires all the IDE support available just to make it usable, but I do find there’s a value in going back through your calling code when you’re refactoring to make sure you still understand how it’s used. So stepping through search & replace can be a useful activity.

  3. @Josh: Your language taxonomy link is useful (http://eli.thegreenplace.net/2006/11/25/a-taxonomy-of-typing-systems/) but I still prefer seeing static / dynamic / strong / weak discussions as being on a continuum rather than a yes/no decision.

    Agreed that Ruby is strongly typed, but it is not statically typed and types are not specified in method declarations. This dynamic nature is the source of both the power and the risk attached to using Ruby. What has been interesting me is whether there are useful parallels to be drawn between how individuals collaborate within successful teams and how objects should act in a successful dynamic language system.

  4. 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.

    I agree with you.
    I expanded a bit about this some time ago . It’s interesting to note that the number of interfaces needed to respond to all posible clients of a class with n methods would be 2^n – 1. It really explodes!

Comments are closed.