- App Projects Are Not Small and Easy
- Apps Are Not Easy to Program
- Poor Skill Set Fit
- If You Get a Good Developer, You Still Have to Worry
- The Idea Is Not More Important Than the Execution
- Unwillingness to Delegate: Micromanaging
- Bikeshedding
- Poorly Defined Requirements
- Out-of-Date Requirements Documentation
- Constantly Changing Requirements
- Leaving the Worst for Last
- Cost Overruns
- That Last 10%
- The Whack-a-Mole Problem
- Poor Communication
- Abdication of the Management Process
- Wrapping Up
The Whack-a-Mole Problem
There’s a specific situation that causes cost overruns and stretches out the last 10% of a project. It sometimes happens earlier in the project, but most often it shows up at the end. I call it the whack-a-mole problem, after the carnival game where a player with a hammer tries to hit mechanical creatures as they pop up out of holes.
The whack-a-mole problem occurs when it seems that the fix for any given bug causes a new bug to pop up. This is often an indication of a poor architecture (or poor developers). Unfortunately, if you’ve reached whack-a-mole, it’s usually too late to change out the architecture (or the development team) without a lot of effort. There are many ways to end up in this situation, but let me take you through a common scenario.
Mobile apps are largely driven by events—things like the user pressing a button or new information being received from the network. Somewhere in the source code, a set of instructions gets called when a particular button gets pressed. When that set of instructions becomes too complicated, you get the whack-a-mole problem.
Imagine a banking app with a button that says Account History on the main screen. The user taps the button, and the account history screen appears. Everything works fine.
Then someone asks, “What should that button do if the user isn’t logged in?” So the programmer is tasked with writing code so that if the user is logged in, that button shows the account history screen but otherwise shows the login screen. Then the login screen gets programmed to return to one place if it was shown from the login button and another place if it was called by the Account History button.
Then later in the project, someone says, “We shouldn’t open the login screen if the network isn’t available since the user won’t be able to log in; that would be confusing.” Now the programmer goes through the code and finds all the places that the login screen is called and puts a check at each one to see if the network is available, and if it isn’t, it shows the screen that says the network isn’t connected and asks the user to connect to Wi-Fi.
At this point, the code that is executed when the button is pressed depends on two states: whether the user is logged in and whether the network is available. The programmer isn’t thinking about all the different possibilities, only the case where the user isn’t logged in and the case where the network isn’t available. The programmer makes the change to show the screen that asks the user to connect to Wi-Fi and checks in the change.
If the programmer wasn’t careful, when the user taps the Account History button when the network isn’t available, the app will ask the user to connect to Wi-Fi, even if the user was already logged in, which wasn’t part of the requirement.
As time passes, more and more states can get thrown into the mix. There could be a special screen that needs to be shown on login when the users have overdrawn their accounts. There could be a special screen that’s shown on the user’s birthday. There could be an alert that needs to be shown when fraud has been detected on the account. Maybe an interstitial ad needs to be shown, and so on and so on. Each time a new set of behaviors is added to that button, all the previous behaviors are at risk of becoming broken, and the amount of time it takes to do a thorough test gets longer and longer because each combination of states needs to be tested. Multiply that by the number of buttons in the app, and you see how big the whack-a-mole problem can be.
Some application architectures and programming techniques do a good job of handling this complexity (assuming that the programmer is experienced with such techniques). The trick is that they have to be put in place at the right time. If they’re put in place too early, they are more trouble than they are worth, but if the programmer waits too long, it becomes tedious and time-consuming to move all the existing behavior to the new architecture.