Interface Design and the Law of Leaky Abstractions
Programmers are always trying to make things simpler, usually by making them more complex. Interface too complicated? Need a bit of extra functionality? Want to work closer to the problem? Add a layer of abstraction to the code. As most programmers know, this is, ironically, usually the right solution. Abstraction is critical to breaking a problem down into managable chunks. Of course, as a friend of mine once said, in programming every problem can be solved by adding another layer of abstraction, except for the problem of too much abstraction.
Joel Spolsky once made an important observation about abstractions, which he dubbed the Law of Leaky Abstractions. As he explains, an abstraction has leaked when a user of the abstraction needs information, data, or knowledge of the underlying implementation or interface. And Spolsky's observation is an inconveniently true one.
The Law of Leaky Abstractions: Every non-trivial abstraction leaks.
So what does this mean if you're designing an interface? You've got some kind of lower-level functionality that you're exposing, so that consumers for your interface can use the functionality of your much simpler interface without regard to the ugly details of the underlying interface. Your new abstraction is so good that nobody will want to use the underlying stuff anymore. In fact, you're so confident in your new interface that you don't need to expose the old one, right? Wrong. Your abstraction will leak. No matter how careful you are in creating the abstraction, there will be a scenario where users of your interface will need knowledge of the underlying implementation or access to the underlying functionality. Your abstraction didn't fail. It just leaked. But if you've hidden the underlying functionality, then you've just disabled every scenario in which a leak occurred.
Corollary to the Law of Leaky Abstractions: An abstraction should not hide or disable that which it abstracts.
This applies to wizards, COM interfaces, API functions, communication protocols, and all other abstractions. I worked on an IRC bot a while back which could take third-party plugins. We abstracted away the IRC protocol into an object-oriented interface that would track channel state information locally and present modes and information through accessors in the interface. We implemented channel.IsModerated to wrap mode 'm', channel.IsTopicLocked to wrap mode 't', and so on with all of the standard modes. We couldn't think of any reason to need the IRC protocol directly, so we didn't expose it.
One network, however, had implemented a custom mode 'U' to block URLs in channel. Obviously we hadn't include an accessor to this mode (we weren't psychic), so our abstraction leaked, and our bot's plugins couldn't use this new mode. The solution was not to add a channel.IsBlockingURLs accessor to our class. That would lead to interface bloat (and a waste of our time) as we have to add a new accessor for each custom one-off mode or protocol extension. No, the solution was to enable plugins to program through our interface and write directly to the protocol, which we did in our next release.
The Law of Leaky Abstractions says that you can never completely abstract away the whole protocol/interface/function/etc. So don't try. Abstractions are still very useful. They make things easier for the 80% case and improve efficiency. But when designing your abstraction, be careful that you don't also disable the 20% case in the process.
Comments
- Anonymous
September 29, 2006
Which bot was this? I don't recall ever really releasing NSB. :) - Anonymous
September 29, 2006
This was NSB. The network that had it was our own; we had upgraded our servers.
Vorn - Anonymous
September 29, 2006
Indeed, the network I recognized. - Anonymous
September 29, 2006
(for those of you playing along at home, Ryan, Colin, and I share an IRC network.)
Vorn - Anonymous
September 29, 2006
We never did officially release it, but it is still in use and still has a publicly accessable subversion repository. :)
I believe the concept of leaving that which you are abstracting open is fairly common in the code even outside the IrcMsg module. I know it is also used in EventPlugin. The older Plugin methods are still available with slight modifications. (If you defined any Plugin methods, you just had to call your parent methods properly.)
The only downside to this is that if you change the underlying implementation of the abstracted object, you often break code that dug down into the root objects. Not a problem with the IRC protocol in most cases, but if we adapted the bot to work on some other room chat system, those modules would fail. Our abstractions are fairly tied to IRC anyway though, so even ones that used the abstractions would likely fail in that case.