I've read a lot recently about the advantages that people perceive in using dynamic languages. I understand some of the enthusiasm -- in fact, JavaScript is my language of choice for building little utilities both at work and home. But the idea of actually using a dynamic language for a major project at work gives me the willies. Since many people are using dyanmic languages for significant projects, I started wondering whether this was an unjustified predjudice on my part. I needed to get beyond simple fear and figure out what it was that really made a difference to me.
In the end, there was one feature of strongly typed languages that stood out for me -- and is significant enough that I am still inclined to avoid using dynamic languages in large projects. That one thing is interfaces.
So why the fixation on interfaces? I find that I use interfaces in a number of ways: for modeling, for awareness of important changes, to increase decoupling, to help enforce architectural patterns, amongst others. And I really do think that interfaces -- and strong typing -- are critical to each of these. When I start thinking about how to approach a new problem, one of the first things that I do is to consider what components will be needed and how they will interact. In effect, I come up with a model of the system. Writing down a series of interfaces is an easy way to make such a model concrete. It quickly captures significant boundaries in the system and, through the types of parameters and return values, at least suggests the interactions which will occur. In more complex situations, I might use a UML sequence diagram or a class diagram to capture and express elements of the model, but the nice thing about a set of interface declarations is that they cross the boundary from model to implementation. In effect, the model is embedded directly in the implementation. Futhermore, by separating interface declarations into their own library/module, although part of the implementation, they remain distinct within it.
This type of structure is very useful when you've got a group of developers working on a project. It emphasizes the importance and the independence of the model. Developers know that if they change interfaces they are changing a more fundamental part of the system. Changes to the interfaces will be scrutizined more closely in code reviews and will raise questions about the downstream impact of those changes. Also, it becomes very easy to spot unwanted coupling in the implementation. The implementation of one component should never directly reference the implementation of another -- only interfaces. So, as soon as you see code which refers to a concrete implementation rather than an interface, you know something is wrong. Similarly, you can create interfaces and even implementations for common design patterns to help ensure consistency throughout a project. The projects that I am working on at the moment have standard interfaces for the visitor pattern, the factory pattern, etc.
Of course, to some extent this is all possible in dynamic languages too. You can adopt conventions with regard to all of the techniques that I've mentioned above and derive most of the benefits noted. I still think its useful to have the compiler tell you about type errors, but I can understand the value that the quick turn-around that dyanmic languages bring. But in every project that I've ever been involved in one of the biggest enemies of well-structured code is entropy. Given the ongoing demands of getting releases out the door, corners get cut. Test cases that should be written, aren't. Refactoring that should be done gets postponed. With a dynamic language I think its much easier for this sort of decay to creep into the fundamental aspects of the system. Because interfaces aren't checked by the compiler, its easier for the code to decay in small increments. With interfaces and strong typing there is a barrier. And even if the barrier is half psychology and half technology, in my experience its enough to help keep entropy at bay.