Think you know what your customer wants? Can you afford to be wrong? Based on the concept of tracer ammunition, which allows a shooter to follow the path of a bullet toward its target and adjust his aim as needed, tracer bullet software development can help you better understand your users’ wants so you can build a product that hits the mark.
Tracer bullet software development (TBSD) is a method based on the concept of tracer ammunition, which allows a shooter to follow the path of a bullet toward a target and adjust his aim as needed. Software tracer bullets combined with canned data (hard-coded data returned from an API that simulates production data) allow you to begin product demonstrations very early in the development cycle. TBSD is also an effective way to build scalable software systems that are easily refactored and tested, and it decouples your teams’ efforts, allowing them to work in parallel.
I once heard a story about a large retailer and how it discovers what sells. The retailer buys anything that might sell, puts it on the shelves, and waits to see what happens. The idea is similar to the World War II battleships. They didn’t spend hours aiming their first shot from the big guns. They fired a shot, studied how much it missed the target, adjusted, and fired again. Usually the third shot hit the target. That’s the same idea as TBSD.
TBSD consists of six steps:
- Identify system objects
- Propose interfaces
- Implement interfaces
- Connect interfaces
- Add functionality
- Refactor, refine, repeat
Let's consider each step.
1. Identify System Objects
When we identify system objects, we’re trying to define the major parts of our system. We want these objects to be fairly large, but not enormous. They're usually some variant of client, server, data analysis, data access, and database, which maps loosely to the well-known Model-View-Controller paradigm. Our goal is to identify the large, granular system objects that encapsulate major system functions and can be cleanly isolated from one another.
We don't want to get overly specific at this stage, which is why these are large objects. On the other hand, getting too large and putting "data analysis" in the same system object as "login" can result in clunky designs and poor performance, so I usually put intensive data analysis into its own object.
Most system objects share a collection of well-defined attributes:
System Objects Can Stand Alone
I prefer to consider each system object its own box and then add an interface and networking layer to enable the objects to communicate with each other. Why? It enforces developer discipline. On a Friday night when we want to go home but we need some data from the database, most of us will go straight to the database instead of adding the proper code to our data access layer. Sure, we know it's a hack, but it gets us home more quickly. Over time, these quick "fixes" create a messy code base that is very brittle. However, if we do things properly, when the database code is rewritten or the schema changes, only the data access layer will require modification. But in a brittle system where developers have bypassed the database encapsulation layer, changing the database schema will break the entire system. We used to call this "spaghetti code." Today's term is "big ball of mud." Either way, we want to avoid this maintenance nightmare at all costs.
When each system object is a separate object, we can't cheat. If the only way to the database is through the data access layer, then we’ll ensure that it will always be used.
System Objects Have Recognizable, Cohesive Functionality
Just like traditional object-oriented code, system objects encapsulate a set of cohesive functionality. Some system objects handle data analysis; others handle database storage; others handle the user interface. Some are wrappers around pieces of complex functionality.
System Objects Have A Clean Boundary
System objects are defined by their interfaces, called APIs.