Chris Granger has a wonderful post up “Toward a better programming” based on his Strange Loop talk. I’m in general agreement with most of it, and probably with anyone making serious attempts at trying to understand why programming is terrible.
Of course, there’s something about it I want to comment on:
Not once did I hear that programming is “solving problems.” Instead, I found out that it’s just clumping things together with half-dried glue and praying that it holds water long enough to replace it with a new clump of things. I heard over and over again that we’re plumbers and glue factories…
I mean, this is almost a success story, right? Ignore the metaphors that are framing this as a really bad thing for a moment. Wouldn’t we like it if most programming were about composing together small components to solve our larger problems? Would this not essentially be nirvana for programmers? If, for almost all the smaller problems we encountered, we could just use an existing solution, probably better designed, and fully debugged already? Would this not make us wizards?
It is, of course, not a success story, but I think that’s because:
- All our programming languages are terrible.
- All our standard libraries, libraries, and frameworks are terrible.
- All our tools are terrible.
And yet, despite all that, we’re actually sort of close. We have all those severe problems, and yet we’re still able to build systems by trying to compose together parts. It’s just painful and fragile (“teacups stacked on top of teacups”) instead of wonderful, and well… no wonder. Look at that list again.
If you haven’t clicked the link to read that post, please do. There are lots of good ideas there. But for the rest of this post, instead of talking about those, I want to elaborate a little more constructively about *why* I think all our languages (etc) are terrible.
Abstraction, Modularity, Composition
I think the history of programming languages, in the broadest possible terms, is the discovery of these three things as hugely important parts of programming. In fact, I believe we’ve discovered them in this order, over decades.
Abstraction goes back to the beginning of programming. The essential observation of the lambda calculus (and lisp, schemes, etc) was that abstraction was actually the bare minimum, sufficient to write any program.
Great abstractions are ones that hide away details that really don’t matter. Your task really does get easier using them. Good ones hide away details that usually don’t matter, but you aren’t tied to them in any way. If the details start mattering, just use a different abstraction, no problem. Poor abstractions hide details we care about, tie us down in using them, and give us no good way of dealing with it.
(For example, most of the historical arguments about garbage collection have been between people whose particular problem domains disagree on whether garbage collection is a great or poor abstraction. For most problem domains, it’s great. For some, it’s poor.)
Modularity as a principle wasn’t really developed or understood until the 80s. The basic idea is designing interfaces to isolate components of your program. This isn’t really about the module system of a programming language, instead it’s generally down at the level of types. In object-oriented languages, that’s interfaces or classes, and involves principles like encapsulation.
By and large, the adoption of modularity as an important principle in software design coincided with the object-oriented movement. Unfortunately, this has meant that most languages in use today were based on the ad-hoc dogmas of that movement, and not the underlying principles. Many of them do not support modular development of programs very well at all, despite being considered “object-oriented.”
Composition is only just recently coming to be understood as an important principle. I’m not sure if there has ever been a good manifesto on this, or if instead it’s just sort of slowly congealing in the community’s collective minds. It’s making its way through to practice via mantras like “favor composition over inheritance” or the SOLID principles.
The lack of support for composition in languages like Java is the reason for the existence of things like Dependency Injection frameworks. These frameworks are little more than an elaborate song-and-dance routine that tries to enable composition by reducing it to a modularity problem. (And the frameworks I’ve seen only handle some parts of the problem, partially.)
The major advantage of composition is that it’s almost a cheat code for turning poor abstractions into good ones. If an abstraction hides a detail you care about, and that abstraction is composed together from smaller ones, then you can just break it apart, replace the part that doesn’t work for you, and re-compose it back together again and have a new great abstraction to use. (The state of the art for this, besides dependency injection, is inheritance, which is anti-modular and anti-composable. Elaborating on this might make a good post, later…)
I could go on, but I think this is sufficient to get my point across. Our programs are fragile glued together horrors because they’re built on poor abstractions. Abstractions that have to be poor because our languages lack support for modularity and especially composition, and often foist just plain no-excuses poor abstractions on us besides. (e.g. String in Haskell.)
If and when we start working in a world with languages that properly support these principles, and libraries that take advantage of them, will the woes of the world’s glue factories be solved? I don’t know for certain. For all I know, next decade we’ll have discovered all-important principle #4.
But I am very skeptical of any proposal to radically remake how programming is done. I think we have a very clear set of principles for building good software that our programming languages do not enable, our libraries do not follow, and our tools to not support. I’m not convinced the problem with programming is anything truly deeper than “well, let’s just do that already. Sheesh.”