DAVE'S LIFE ON HOLD

Events and Messages

Pet peeve time here! When designing an OO interface for your platform/system/language, the first and foremost concern should be robustness. If sending an arbitrary or unexpected message to your interface causes your application to fatal and crash, you have designed "to fail" not "for failure". Designing to fail is an acceptable behavior in some cases. Transactional systems can rely on failure as a mechanism to prevent corruption or loss of integrity. Failure with rapid recovery can be beneficial when dealing with flaky hardware or unreliable transport streams. When the cost of correction or recovery is greater than the cost of restarting, designing to fail is good trade off.

But when you are talking about an operating system, or a streaming server, or a motorized device's controller, these sorts of designs are fatal. Even internal interfaces within an app can suffer from these problems. Look at Android's Message class and you'll find a fundamentally flawed model of message passing. Since Java is not really an OO language, android relies on passing Message objects around between class instances. Since message passing is implemented at the system library level, and not the language level, all message sends involve the explicit creation, sending, and handling based on manually constructed messaging queues. This means you can not send a message to any object, and it also means there is no way to dynamically discover what messages an object may or may not respond to. Since all message handling is done in user space, reflection provides no clues to an object's messaging behavior. As a result, attempting to send a message itself presents an opportunity for a fatal exception, and places a great deal of work on the programmer to define and implement a messaging interface for all of his or her components.

Worse still, since the Message object itself is marked final, a programmer has no safe method for defining and implementing any convience methods that would allow for the safe inspection of the Message's payload. Since the Message may have a Bundle of serializeable data attached, or a reference to an arbitrary object, there are no guarantees that the contents of any given method can be safely extracted or inspected. Reflection allows for some limited ability to inspect some aspects of the Message's payload, but as there is no consistent definition for what types of messages mean, there are no non-reflective interfaces for ensuring that a message can be queried without fear of crashing. This is because a message itself can not be sent a message, and relies entirely on Java's method dispatch mechanisms for interaction. Exception handling provides limited protection against application failure at this point, as the only option is to catch all possible exceptions and discard the message. As a result, it is severly difficult to rely on failure as a means for handling messages, as all receivers must each implement their own error handling to insure the proper operation of he entire application. Shifting this burden onto the application programmer is a utter failure of design at both the language and system levels. This is because the basic building blocks were designed to fail, not for failure.

In contrast, look at Smalltalk. Any object can send any other object a message. The recipient may or may not understand how to respond to that message. It the object does not, the object's doesNotUnderstand: method is called with a message object representing the sent message is invoked. The programmer may then handle the behavior of delegating, or resenting, or erroring out, or letting the default behavior of invoking the debug interface take over. This is a design for failure that treats failure as a normal, not exceptional, mode of operation and allows the application programmer the flexibility and capability of managing the response as they best see fit. This concept is critical if you value both discoverability and robustness in your interfaces. It is also critical for ensuring that, as time passes and requirements change, your abstractions and encapsulations remain viable.