Search This Blog

Navigating The Unknown

There are two basic types of software projects: those that have no existing code where you'll be starting with a blank slate and those that already have working code that you'll be modifying in some way. There are also two basic types of engineers that work on software projects: those that have no prior experience with the domain and those that have already built something similar in the project's domain before. Of course, both of these pairs of categories are extremes on a continuum, but I'm a software engineer with roots as a hardware engineer so I'm a little too comfortable with binary. Bear with me.

These types of software projects and engineers can potentially be combined in four different ways. Imagine you're the engineer starting work on a project. You could be starting a brand new project in a domain that you know a lot about. You've written other programs in this domain, and you have plenty of experience with the constraints and pitfalls that you'll likely encounter. Things should go pretty well.

Or you could be dropping into a project with a significant code base, maybe it's even gone through a few releases, but for for one reason or another you don't know a thing about the domain you've found yourself working in. Maybe you're a new team member on a mature project, but you came from a completely different area of expertise. Maybe you've inherited the project from someone else with a specialized skill set that doesn't overlap with your own. Even though you'll have to learn the domain and orient yourself, you have a code base to learn from and work on. Changing existing code is much easier than writing new code because you can iterate over a working foundation instead of building the entire architecture from nothing.

The easiest of these situations is working in a domain where you've already done a lot of work and have plenty of working code available to draw from. Depending on what you're doing, this could also be the most boring situation to be in. Adding cool new features to a product that you previously built can be exciting, but reimplementing something you've done before with different constraints but nothing really new can be a real drag. In either case, lack of knowledge is not likely to be a problem.

The real unknowns happen in the last case, and that is what I want to focus on here. When you're taking on a brand new project in an area that you've never worked in before, how do you even get started? How do you design the architecture? How do you cope with the immensity of the blank slate before you?

Programming a Piano Concerto

I was poking around on the Pragmatic Programmer's site, and I came across a fascinating article about a team of software engineers that designed a program to convert old piano recordings into high-definition MIDI files that could be played back live on a special Disklavier Pro piano. They had to deal with plenty of unknowns including zero documentation of the piano's MIDI file format, no supporting libraries, and incomplete data.

The technology was so new that almost no one had programmed with it before, meaning domain experience was hard to come by. They didn't even know initially how they were going to test their software to make sure it was reproducing the recordings accurately. How did the team deal with all of these unknowns?
Some teams would want to start testing with a Rachmaninoff piano concerto or some such, and that’s a huge mistake. You always need to start with small, isolated unit tests before moving on to more advanced functional or acceptance testing.

In this case, the first six months or so of unit tests comprised some beautiful piano solos made of just one note. Just one single note at a time, mapping out the full range of the instrument. It was quite a while before the unit tests got the software to the point where they could try “Mary Had A Little Lamb,” and quite a while after that before poor Mary got any rhythm.
They went exploring to learn everything they could about the domain they were working in and took careful notes of their excursions by writing tests. It may seem like they spent a lot of time on the basics and that there would still be a lot of work ahead of them, but in reality, they were laying a stable foundation that they could quickly and efficiently built on later. Once they had a good test structure in place, had mapped out all of the variations of every note, and had figured out how to produce the right rhythm, they were all set up to rip through a real concert piece.

The team had built the primitives they needed for their domain through exploration and made a map of what they had found by writing tests. This process got me thinking about how often I have done similar things on projects I've worked on.

Drawing A Map In The Wilderness

One of the first projects I worked on as an engineer fell squarely into the 'no domain knowledge' and 'no existing code' category of our little classification system. The team I was on had to design an ASIC that would read an array of magnetic field sensors and calculate the position of a target magnet. The sensors had a complex non-linear response to the magnet, so the equations were complicated. To make things even harder, only two sensors could be measured at a time, the magnet needed to be between those two sensors to calculate a position, and the system needed to produce results as fast as possible. The resulting algorithm was going to be difficult to say the least.

I started in on the project the only way I could think of. I built a model of the system and the calculations in C++ and proceeded to explore it. The software model turned out to be an excellent map that we used constantly throughout the development process. We used it as a communication device when interfacing with the customer. We used it to experiment and try out new ideas. We used it to test and verify that the ASIC design was correct before we released it for prototypes. And we used it to validate that the ASIC was operating correctly when we got first silicon back. Having an executable model of our system was so invaluable that we continued building software models of every ASIC project after that and used them to validate the designs through tests.

High-level models and tests are maps of the wilderness of design. They are extremely effective at helping you find your way and remembering where things are. When you're dropped into a new domain and need to figure out the lay of the land, your best bet is to draw a map. You can start exploring in little bits and pieces, making a record of where you've been and what you've learned.

At first it may seem like slow going, like you're not making much progress and there's an overwhelming amount of territory to cover. But as your map gets more fleshed out, you'll have a much better idea of where you are and making further progress will get easier and easier. Drawing a map is one of the quickest and most reliable ways to go from not knowing what's around you to knowing your environment in great detail.

We have been making and using maps for millennia to find our way in the world. We can do the same on software projects to equally great effect. The tools we use are not called maps, but models and tests, although they work the same way. The next time you find yourself in unknown territory on a software project, don't forget to draw a map.