The closest thing I have encountered to Brook’s silver bullet is programming with asynchronous message-based design. I’m not the man to know who invented anything in terms of who should get the credit, but Alan Kay is the man who has most influenced me with his work in this area. Among many other things, he coined the term, “Object-Oriented Programming” and worked with the team that developed SmallTalk, a from-the-ground-up message-based system, and a modern implementation of it, Squeak. My favorite quote of his is this:
“OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things. It can be done in Smalltalk and in LISP. There are possibly other systems in which this is possible, but I’m not aware of them.” An interesting article that explores his thinking is at Phil Windley’s Technometria.
The reason I chose to point out the above is that C++ does not really fit the bill by that definition. It is good and useful, and I use it as my basic tool. But it not only enforces early binding, it actually (due to the fragile base class problem) leads to prehistoric binding – binding to last year’s code. Note that he does not include inheritance in his definition.
What leads me to point this out is that the problems I tackle include the need to allow after-release integration of new plug-in modules. I have products that need to support many such extensions. This has become common in today’s world. The gotcha is that when I change my code and release a new version, and if I have made any changes to a class from which 3rd parties have derived their own classes, all of those plug-ins also need to be upgraded. That is a real headache for many people.
Another problem with the C++ implementation of OOP is that we tend to use it to build a rigid hierarchy of objects. If we have vehicles, and then cars, and then a Lexus, and we have code to turn a heater on or off, we’re good to go. Until we decide we need to heat the garage. Then it’s back to duplicating function. (See OOP Component-structure fragility).
Consider an alternative: modules that link up and can dynamically be brought into play during the execution, and changed around or replaced during execution. Delegation, callbacks, agents: all of those complications completely disappear. The goal is the elimination of glue code. In our unobtainable nirvana, we would be writing 100% application-specific code.
I’ve been doing object-oriented programming in assembler for 20 years now. I don’t need a specific language to be able to use a specific design approach. I do need discipline. I liked assembler because it was definitely an “enough rope” approach. I stopped using assembler because compilers finally got good enough to suit me. It was never a religion. The important point is this: It is more how we think about our design than about the choice of language we use to implement it.
What I find in using a message-based design is that I can have near-perfect data encapsulation and implementation hiding, fluid structure, and very late binding (linking). I also find that it greatly simplifies multi-threading and network coding. I implemented my message-based approach in C++ because I don’t like interpretive environments – I want my CPU cycles, thank you very much.
The other part of my lead-in was the word asynchronous. It’s what frees us of user-interface modal locks, like blocking dialog boxes. Here is an example: We have a file-name picking dialog box. We start it with a message, and we tell the dialog object the object with which it should communicate. When a file name is picked, or the dialog is canceled, a message is sent to that notification target object. Nothing really needs to be suspended, because the resulting message is what triggers the next step. If we use it to load images into an image cache object, implementing multiple selection does not require extra callbacks or other weirdness – the file name dialog simply sends several file name picked messages.
This seems simple, and it is, but it has enabling results. If we have a worker thread process, we use a thread control object, which queues work for the thread, starting it when there is a message in the queue, and stopping it when there are none. We write one of these classes. If we want to make some function run in a threaded mode, we only need to communicate with it via this threading queue object, and ensure data locking (only one thread can be changing data at a time). Not easy, but much better organized. If we don’t create such a queue object, but communicate directly, then we are working synchronously. Dynamic run time choice.
We want to distribute some of a heavy work load – send the message over a network. Our code already knows how to wait on the results due to the asynchronous messaging design. We don’t need to rework our code to network-enable it. A network is just a slower message pipe.
The kicker is that these techniques have been around for something like 30 years or more. The only reason I can think of to explain why they are not current accepted best practice is that you lose most of the benefit if you graft the approach on to an existing system. It needs to be designed in at the core foundation level.
A good way to understand messaging is to play with Squeak. You will likely not use it to do your “real” applications, but it will change how you think. Also, Apple’s OS-X preferred language is objective-C, a messaging-based system. The GCC suite supports objective C and is available for all platforms.
This entry (Permalink) was posted on Thursday, October 1st, 2009 at 6:10 pm and is filed under Uncategorized. You can follow any responses to this entry through the RSS 2.0 feed. Both comments and pings are currently closed.