Not long ago, a prospective client came to us to ask that we write up a proposal for designing the software components of a new consumer-grade medical diagnostic product that they were bringing to market. This device was going to be a simplified version of an existing system that had been in production for some time, so surely the requirements for the new product would reflect that pedigree and be relatively straightforward.
Given the scenario above, the astute reader will reflect on the title of this blog post and wonder why the word “uncertainty” appears.
Surely, this is a story about how mature, market-proven technology and well-understood software requirements lead to a straightforward design, pain-free development, and a win-win-win scenario. You would be right to wonder, because this is not that story.
This is not that story because the new medical device the software had to communicate with didn’t actually exist yet. This is also not that story because the way the system was going to arrive at its diagnostic result hadn’t been decided, despite thousands of the original device already being in use in the field. And this is not that story because the cloud-based service the device and the software were both going to talk to was just a gleam in the client’s Marketing department’s eyes.
So, this really is a story about dealing with uncertainty and being comfortable in ambiguity even when the unknowns represent a rather significant portion of the software design and development work, and risk, that lie ahead.
The speed at which businesses need to move today demands that we software architects and developers handle unknowns with aplomb and agility, filling in the gaps with sensible approaches until we get more of the answers.
Let’s start from the beginning.
The device the client was bringing to market was still in the early prototype phase, but they knew a few things about it. First, it was going to be significantly cheaper than the hospital-grade device that came before it, so it couldn’t have a screen or even many buttons on it. Second, it would be battery powered so it could be small and again, be less costly. Third, it would need to communicate with a mobile app so that the user could control the device and interact with it. And fourth, it couldn’t use a cable to connect to the mobile device.
No cable, low power, controlled by an app on a mobile device. These types of hardware specifications tend to back-drive software requirements, and in this case, it led to the decision to use Bluetooth Low Energy (BLE) as the primary communications medium between the device and the mobile app. BLE is a mature and well-documented protocol, ably supported by both major mobile operating systems, so where’s the uncertainty here?
How do you develop software to communicate with something that doesn’t exist yet? How do you define an interface to control something when that something and its interface are still being designed?
You make it up, that’s what you do. You create a software facsimile of the thing you expect to eventually exist so that you’re not sitting around waiting for the actual thing before you can start. Done right, this can even offer both the hardware and software developers a design capability that will serve them well going forward.
In this case, it was fairly straightforward to design a BLE simulator in software with which the mobile app could communicate. This would allow us to not only move forward with app development without the actual diagnostic device, it also provided an outstanding way to design and test the BLE interface that could then be ported to the hardware when it came available.
In the airplane design and test business (my previous life’s work), you might call this the “iron bird” approach. Long before a new jet takes flight or is even fully off the drawing board, engineers build out many of the aircraft’s components and their connections, and lay them out all over the concrete floor of a massive hangar. This helps the aircraft designers tweak and refine the design and experiment with different components. It also lets the software folks interact with a reasonable approximation of the airplane and gives the entire team a ready-built test harness for future design and testing.
We used that same approach here, mocking up a BLE interface in software that the mobile apps could talk to, in a way that would allow the client’s engineers to help refine the interface once the actual device was available. By sharing the software BLE device simulator via source code control, we would create something that both teams could reference when making changes to the communication interface while simultaneously helping accelerate development of the mobile apps.
Another area of ambiguity was how the actual diagnostic result would be computed. The original device had a built-in sensor and algorithm that did all the work, but to produce the new device at the desired price point, that algorithm and the CPU power needed to run it would reside on the mobile device.
Great, says the mobile dev team, just send us the code and we’ll add it to the mobile device. Ah, if only things were so simple.
The client hadn’t yet determined what form the key algorithm would take or even if it could be hosted directly in the native mobile OS code. Computing the diagnostic result for the user was the key thing this product was going to do, but how that was going to be done in code hadn’t been figured out yet.
Ambiguity to the rescue!
When faced with significant unknowns in software design, it’s often useful to simply ignore them, to treat them as a “black box” that simply takes input and provides some output. To paraphrase my favorite scene from the movie Cannonball Run, “Whatsa inside is notta important!”
We can treat this ambiguity as a feature. Instead of being concerned about how the algorithm will work, we just assume that it will and build a place for it to exist. In this particular case, a decent approach is to expect that the code that performs the diagnostics will be a native library, and both iOS and Android have no problem interfacing with native code modules. A quick check with the client indicated we could make this reasonable assumption.
Much the same way the BLE software simulator can be used to help both the hardware and software teams define and refine the communication interface, so too can a mocked-up version of the diagnostics module. All of the ceremony of building and integrating with native code, calling functions in it, providing the necessary inputs and getting back results as output, could be simulated with a very small C++ DLL, easily compiled for each of the platforms.
An additional advantage of using a mock library is that we can build in test- and development-only features. For example, there are several responses the diagnostic code can return, and the mobile app has to respond to each of them appropriately, including modifying the user experience (UX) based on the response. We designed the mock module to allow us to tell it what sort of response to give based on an input parameter, sort of a “dependency injection”-lite approach. This would allow the mobile developers to develop all of the UX use cases without being blocked by needing the actual diagnostic code in place.
Done right, when the real diagnostic code became available it could be built using the same tooling and plopped down right into the mobile app. If necessary, small tweaks to the function interfaces could easily be made and verified, and we’d be off to the races. The mocked-up diagnostic library, seemingly just a placeholder, now becomes a communication mechanism and a visible, verifiable contract between the developers of the mobile app and the hardware. The client’s engineers can easily pull down the source code to inspect and modify it, helping to refine the expectations of the module’s interface, and sharing that back with the mobile dev team.
Designing for uncertainty, and even reveling in it a bit, has its rewards.
The final bit of gray area to explore was the cloud environment backing the client’s entire system. The medical information generated by the combined diagnostic hardware and the mobile apps contained both personal health information (PHI) and HIPAA data. Given the sensitivity of the data, the client wanted to move cautiously with the implementation of their cloud and in giving our development team access to it. We also proposed using the cloud to host the API that would back the mobile apps, so the cloud was a key piece of the complete system.
Rather than hold up mobile development or complicate the current-day effort with concerns about sensitive data, which we could address later, we proposed building a development version of the cloud that the mobile app developers could run locally and that we could easily push to a cloud-hosting environment as the effort matured.
With some high-level requirements from the client on what and how diagnostic and medical data needed to be stored in the cloud, and what information was required to configure the mobile app to perform a particular diagnostic test, we could design a reasonable proxy for the cloud, again with the intention of making it as simple as possible to cutover to the real thing when it became available.
And much the same way the BLE simulator and mocked diagnostic algorithm module also served as a medium for us to collaborate with the client’s engineers, so, too, was the cloud application. Data flows, API endpoints, and the back-end database could all be designed and implemented in a reference implementation that was easily shared and updated. And as the data privacy and protection requirements were made clearer, we could layer on the necessary protections and mechanisms, checking for breakage or regressions along the way.
As software architects and developers, having clear, unambiguous requirements from which to design and build a new system is always a desire, but like a rainbow-colored unicorn, it’s not something we are likely to ever actually encounter. Even in the face of uncertain requirements, though, we aren’t often tasked to design software that interfaces with other systems that simply don’t exist yet.
But it does happen, and if you embrace that uncertainty with a friendly bear hug, you can move development forward, design for flexibility and change, and create tools to foster collaboration. Let’s recap what we proposed for this client:
- In the absence of the physical device the mobile app would control, we designed a Bluetooth Low Energy software simulator to define and test the BLE interface and support mobile app development
- Without having the actual diagnostic code module, we instead used a placeholder native library as a drop-in replacement to implement the function interface
- Instead of waiting on the client to deploy their full-up, HIPAA-compliant cloud solution, we proposed a locally-runnable, scaled-down, and deployable development cloud instance
At Fairway Technologies, we encounter clients and projects of all shapes and sizes, so being able to handle ambiguity and uncertainty is par for the course (pun intended). Taking a proactive, elegant approach to managing uncertainty can provide additional rewards in code quality, agility, and collaboration. Give it a shot in your next project when things are murky or missing, and you will most likely find it very empowering. Of that, I’m certain!