I just finished a project that used a Kinect’s computer vision tools to gather information about people nearby, then communicate with other hardware to adjust the environment. (Vagueness due to NDA.) The Kinect’s SDK limited me to C#, C++, or Visual Basic, but the other device only had a USB HID interface (a generic human input device). Unfortunately, C# doesn’t have any native support for USB HIDs, and none of the several libraries I tried seemed ready for prime time. After digging deeper and deeper into their implementations and attempting to fix things, I decided to try another approach.
There wasn’t any reason my entire system had to be in C#, though. As long as the system agreed on a protocol, I could just use IPC (sockets) and build the controller side with whatever language had the right libraries. Python has a great Windows USB HID library, so I wrapped that in about a dozen lines of socket code, and it was good to go.
Later, the hardware vendor sent me a DLL that provided a higher-level C interface than raw byte buffers, so I replaced them with calls into the DLL. (Also from Python, via ctypes.) The C# code didn’t need to know or care about this, since its outgoing messages stayed the same. Breaking my system into small, autonomous components made me focus on what they needed to communicate to each other, and once that was established, I was free to change everything else.
While this was fairly small project, the clean breaks would have been an even bigger win as the project grew. If the hardware controller had known too much about the computer vision code (or vice versa), a short-term hack to meet a deliverable could have infected the entire system with its details. A couple months later, it might have even started to look like legacy code. (Eek!) Moving data from one process to another, or across languages, seemed to add just enough friction between the pieces that the overall system naturally resisted excessive coupling. Also, the channels between the different parts of the system were excellent interfaces for testing — I didn’t need mocks, because I could already test each piece in isolation.
People often list this modularity as a benefit of object-oriented programming, but few OOP languages have counter-pressures against passing around unnecessary context. As Joe Armstrong put it in Coders at Work, “You wanted a banana, but what you got was a gorilla holding the banana and the entire jungle.” Other parts of the system can grow to expect this baggage, making it harder to swap out the module down the line.
More subtly, passing around objects at all couples the program to the object system — the rules for looking up the objects’ data and behavior is an implicit global context. If the modules communicate by passing around self-contained textual data, or a simple structured format like JSON, then they impose fewer assumptions on each other. Different parts can be written using different platforms or languages, allowing access to far more libraries. (They can also be on physically separate computers.)
Designing around the messages, rather than the objects, brought my whole system into focus. The self-imposed requirement that my system communicate via C# objects turned out to be a liability, and once I let them go it was much easier to meet my actual goals.