Object Orientation

I'm not a fan.

This (OO) has been claimed to be the greatest thing since sliced bread. I think it... sucks. OO claims to provide encapsulation and isolation, so that separate pieces of code don't meddle with others' internal implementation, theoretically reducing bugs. This it accomplishes, though often escape mechanisms are necessary in order to actually get the job done, puncturing the veil. So much for elegance...

OO claims to foster code re-use, but does it? Is code getting re-used, or do object libraries become so ossified that separate re-implementations are necessary anyway?

Regardless, let us wave our hands and state that OO has fulfilled its objectives. Great, use it and you're done. You've written a bunch of code, all of it responsible for only the published I/O specifications, and don't you dare look under the covers to see how it's getting the job done, assuming you even can, because that's officially None Of Your Business.

If an acceptable answer to any failure is the IT Crowd 'solution' ("Have you tried turning it off and on again?") then go ahead and use OO, if you want to.

But what if you do care how the implementation operates? What if the 'IT Solution' is no solution at all? What if the end product has:

  1. Difficult-to-meet performance objectives, including real-time responsiveness?
  2. Environmental complexity, where harmful races are possible?
  3. High-scale requirements?
  4. High-availability (reliability) requirements? (Fault tolerance, five nines, hot-swapping, etc.)
  5. Legal/contractual obligations, like restricted algorithms? (Thou shalt/shalt-not...) Or failure penalties?
  6. Death or injury as a result of failure?
Any of the above? All of the above? All of a sudden opaque encapsulation is not your friend, and you must end up with your nose deep in everybody's business. If it isn't, you simply can't meet the product's objectives, much less guarantee the product's objectives. Is your OO approach helping you to reach the final objectives, or is it encouraging you to go down paths that could never reach the end successfully? (By the time you recognize the architectural mistakes you're making it might be too late to correct them. In other words, a quick prototype is quite possibly inimical to a successful outcome, if the task is non-trivial. The landscape is littered with the remains of companies whose 'successful' prototype failed utterly under real-world loads.)

In projects where timing, state, and scale are critical, the kind that interest me most, the abstraction and encapsulation offered/required by OO can be fatal.

If I use garden-variety OO for most projects, how do I become adept enough using non-OO tools and techniques to successfully tackle the more difficult projects? Instead, if I always use tools and techniques that can handle the most difficult tasks, even when I don't need to, I become more proficient in them, and more likely to succeed at the most difficult tasks. So, no OO for me is the inescapable conclusion.

Example

Consider something like a network core switch/router, something that must operate, without fail, for years at a time. Something that must perform at a high level, indefinitely. (No time off.) Something supporting thousands, millions of connections. Something that must get software upgrades, while operating, without noticeably disrupting traffic. Something that can experience a hardware failure, and seamlessly switch to an alternate resource within a very small window of time. Something that if it fails to handle the situation correctly it exposes you to legal and financial repercussions. (Service-level agreements, etc.)

I've worked on these. It's not easy. It cannot be done using OO languages. It can barely be done using a restricted C subset, fraught with architectural peculiarities. In this particular class of product an event-driven state-machine orientation is the only possible path to success, and even then only if the basic design is good and you stick to a long list of rules. (The state-holding structures are objects, in a sense, but nothing to do with modern OO-ness. Much more primitive, right out of Wirth's Algorithms + Data Structures = Programs. But... it works, it works well.)

Pesky little rules such as (for example):

  1. You cannot use malloc/free once the startup of the system is complete, either directly or indirectly.
  2. You cannot start any new threads or processes once the startup of the system is complete, either directly or indirectly.
  3. You cannot use any resource that is garbage-collected, either directly or indirectly.
  4. You cannot use algorithms that have O(N) (or worse) complexity, either directly or indirectly.
  5. You cannot call any subroutine that itself has any blocking point (e.g. printf(3), connect(2)) anywhere down its call tree.
  6. You cannot use streams for inter-entity communication, you must use (atomic) datagrams. (That either arrive, intact, as events, or do not, and you must handle either case.)
  7. In the service of one event you cannot yourself require any event service, directly or indirectly. (Such service must be handled by introducing intermediate states and additional events to progress between them, and all that this implies.) Servicing an event must run to completion, without pause or fail. (Completion being defined as waiting for the next independent event, in the same exact place in the code as the first one.)
  8. Any configuration change (for example) must not affect anything but what is changed. (E.g. if I tear down an existing connection, or create a new connection, any other connections must remain unaffected by this.)
  9. Any/every state change in an event handler must induce the same state change in a backup event handler, more-or-less synchronously (modulo Schrödinger's Heisenbug), if said backup handler is part of a system fail-over strategy. (Usually on different hardware.) If I, as a backup, must remain ready to take over from a failed primary at any and every point, I must have an exact and up-to-date copy of all the primary's state, at all times. True failures are, by their very definition, unpredictable; you have to be able to handle anything, at any time.
  10. You cannot log anything routinely. (Because logging potentially violates one or more of the preceding rules, and using non-volatile media introduces long and highly-variable delays. Fun Fact: modern networks are faster than non-volatile storage, and so that means...)
  11. Any event being waited for (i.e. all of them) must also have a timeout, representing some kind of failure, which must also be correctly handled.
  12. Every state change, including timeouts all along the way, must be tested, else the system is not actually functional, fault-tolerant, etc. Your mantra is: "If you didn't test it, it doesn't work."
This is just a start. Sound fun? Sound like there's a lot of code libraries out there that you can exploit that follow all the necessary rules? How do you ensure (and prove) that all the new code you're writing (or importing) does follow all the rules? Don't forget that real systems are much more complex than this little example.

On the plus side, most of these restrictions are not required of administration session programs, although the actions of said sessions cannot ever cause any of the rest of the system to violate these rules, directly or indirectly, even transiently. Beware of indirect influence, such as memory pressure or caching effects.

For success the architects must have a thorough and correct design, and be guiding and supervising all the code that goes into the product. Nothing that violates any of the necessary rules can be allowed in, lest it cause failure in the field. 'Glue-gun' programming must be prohibited. Exceptions cannot be made for political or scheduling reasons. Corporate reputations, once lost, are difficult to regain, and the more expensive and/or spectacular the failure the worse the fallout.

Return to Site Home