Welcome to another essay reaching Brooks’ “No Silver Bullet” conclusion.
Genesis from Analogy
I’ve been reading Klein’s somewhat harrowing The Shock Doctrine and I found an interesting (to my twisted mind) analogy between the application of pure capitalism and the application of pure software concepts/paradigms/technologies, or as I have titled this, ideology.
Around 1990 the Cold War was over and capitalism had emerged as the triumphant economic ideology. But this capitalism, which had underpinned the great scientific and social achievements of the 1950s-1970s, was very different – vastly more state controlled – than the free-market approach of ‘pure’ capitalism espoused by Friedman, and with which we are more familiar today. During the 1990s there were major attempts to achieve this pure capitalism in Russia and East Asia, and both were dramatic failures. The attempted conversion of Russia to capitalism was a disaster for all but a few scavenger capitalists and even the IMF admitted that its approach to the East Asian financial crisis late in the decade significantly exacerbated what was a fairly minor problem. Since then, such unfettered free-market reforms, ‘pure’ capitalism, have become viewed with much more suspicion and caution, and it’s quite ironic that in the demise of its ideological enemy, communism, capitalism should lose such face.
My supposition is that no ideology when enacted in its purest form will be successful, as there will always be aspects of the target domain that the ideology doesn’t address effectively. Oddly enough most economic-political ideologies fail in the same area, which is the negative impact they have on the population, a population which typically has no input into the selection of the ideology. The most successful outcomes arise when ideologies are mixed and moderated, as in ‘Swedish socialism’ or the aforementioned success of the tightly controlled United States of the third quarter of the 1900s. I believe that this mixing of concepts to achieve success applies just as much to software development as it does to nation building.
Back to software development
When I started software development, object-orientation was the great new hope, heralded through its flagship, the recently released Java. Java, with its fairly pure object-oriented implementation, solved the problems of imperative languages like C and Pascal through encapsulation – the idea that data and the actions that manipulated it should be bound together and hidden behind some contract.
Except that Java sometimes made things more difficult. One aspect that has consistently bugged me is event handling, where Java demanded an extra object to implement an event interface separately to the object that wanted to respond to the events. By comparison, C was happy with a function pointer (albeit hidden behind some typedef because the type signatures for function pointers were not exactly intuitive).
Another example of Java’s purity, although not to object-orientism, was the need to state all the exceptions which a function could possibly receive. This meant that aggregating functions either needed to have a ridiculously unmanageable list of exception types appended to them or that every function was decorated with the base exception, making the whole thing pointless.
C#, some five years later, was much more pragmatic about both of these cases, supporting function references (delegates) and keeping exception handling in the code body rather than in declarations. C# is a much less pure object-oriented language than Java, but (in my experience at least) is better for ‘getting things done’.
Functional programming as a pure ideology has struggled get to traction outside of academia. For all the expressive power of being able to write ‘what’ a program needs to do rather than ‘how’ to do it, in its purest form functional programming makes it surprisingly hard to do the basic computational task: modify data. I’ll admit it’s been a while since I last got my head fully stuck into monads and lenses, but I recall genuinely understanding these to be fairly challenging compared to understanding, say, manual memory management and pointer manipulation.
In its attempt to bring functional programming more mainstream, F# takes the non-pure approach of allowing explicit mutability (while defaulting to a functional style). Naturally this weakens the benefits of the pure paradigm, but it also allows people to get things done! Again, the pragmatic compromise provides a more workable solution, while the pure concept remains a curiosity.
Conversely, functional concepts, like list-processing, have made their way into other paradigms where their expressive power is a welcome respite from pure imperative constructs, like nested loops.
Whether it’s micro-services, CQRS, REST, TDD, or any other of the myriad of software concepts, the costs of a pure implementation of a concept always outweighs the benefits. This is because, like with our political ideologies, each concept focuses on a specific type of problem – its core domain – and simply can’t handle all the situations that real software requirements will throw at it. Every concept has its pros and cons, problems where it is a good fit and places where it isn’t. To solve problems effectively we must understand what options best fit in a given situation and how a combination of concepts can be applied to achieve the desired end in a quick, inexpensive, and good-quality fashion. Or as Brooks’ put it, there is no silver bullet.