One recurring issue in designing software systems is how to negotiate precedence when higher-level and lower-level components interact. I'm increasinly convinved that this is in fact the key issue in systems design, just because it comes up over and over again, and I think it's the central design problem in creating genuinely extensible systems. Simply put, whose extensibility wins, and whose extensibility must be constrained by needing to defer to someone else's extensibility?
Suppose, for example, that I'm making an extensible programming language, not just extensible in the sense that I can plug in new modules and class libraries, but extensible down to the syntactic level. On the one hand, maybe I decide to add a throw-catch mechanism to the language; on the other, someone else decides to write a coroutine package with termination handlers. What happens if someone calls throw from inside one of these new coroutines? Does the other coroutine just silently die? Or does it get a chance to finish up before control returns to the throwing side of things? The answer basically depends on which one of our extensions got handled first, if we wrote them without knowing about the other's work. If we wrote them with reference to some common lower-level framework, of explicit control-passing and execution contexts, things may work "as intended," but I'd argue that it's not necessarily always a priori clear what ought to happen. And besides, what's happened in this situation is really that we've slapped some strong constraints on the kinds of extensibility allowed in the system -- now we're implementing things on top of some lower substrate, rather than actually extending the system itself. We've traded away some of our right to arbitrary behavior in order to make sure that things will interoperate.
Extensible event mechanisms and notification-firing objects are another one of these tarpits. What happens when two people come in and decide to hook into the events to override the default behavior in two radically different ways? Protocols for being allowed to change the behavior and control the result may keep us from destroying the known universe in our confused collision, but again, the same objection applies: one of us is going to need to live with not getting our way. It's the Highlander problem: when it comes to arbitrarily extensible components in a system, there can be only one.
Where this is most complicated and most tangled is when high-level extensibility interacts with low-level extensibility. Skinnable applications and desktop themes are both ways of making your computer experience more customizable, but they're mortal enemies when it comes to their dealings with each other. That wonderful consistent app-independent experience where the close button is always in the same place and the menu layout is predictable in apps you've never seen before is fundamentally at odds with your ability to make your MP3 player looks like a constantly erupting supernova and to have your development environment gently pulsate and change colors like a lava lamp. If I want to run a specified filtering script over every document I open, but I also want to let each document specify its own custom logic, which code gets run first and which gets last licks in? There is an eternal tension between the idea that every fragment of data should be fully self-describing and fully standalone (so it can be used in any context, sent anywhere, visualized on its own terms and manipulated using appropriate logic) and the idea that we should be able to apply uniform rules to all our data, to be able to emphasize the common elements to them and treat them identically. It's the difference between the view that shows you your whole inbox and the view that shows you a single piece of mail. Operating system hackers think of the individual items as the bosses -- they're in the business of supporting fully autonomous objects (processes, or components, or users) that must respect certain broad boundaries but are otherwise free to do whatever they please. Database hackers think of the collection as the master -- some huge amount of database theory is concerned with how to cram your dataset into a bunch of tables in a way that makes them all perfectly linear, perfectly predictable. The data isn't supposed to do anything but sit there quietly until someone comes and picks it up, but that someone -- the query-writer, the transaction-maker -- can work their complicated magic on the whole damn database, in all its complexity and glory.
There are compromises required, tricky ones that aren't easy to negotiate. The issue is how much flexibility one needs to trade away in order to get a predictable, usable system, and whether, by judicious choice of what goes overboard and what goes in the lifeboat, those kinds of extensibility that are most helpful in writing reusable and customizable software can be retained.