Debugging is a Waste of Time (and Money)
What if you could write code without any bugs?

Debugging is a Waste of Time (and Money)

As a software developer, have you ever thought about how much time you actually spend debugging your code? Or as a software manager, has your schedule been derailed due to your team addressing unexpected bugs? The words "software" and "bug" seem to go hand in hand. You can't have one without the other. I've heard developers say, "no software is truly bug free". Check out the release notes of your favorite mobile app when a new version is released. Often times you'll see the simple phrase, "bug fixes". It seems bugs are just a necessary evil that accompany the software development journey.

At least that's what I used to think.

I've been running a software development company for over a decade, and during that time, I've been "bitten" by bugs—both as a developer and a manager—enough to feel compelled to explore ways to prevent them from occurring in the first place. In this article, I'll share what I learned using an excellent case study.

In 2021, I joined a startup that was building a wellness app. They had contracted a software development firm to build the app some time earlier and by the time I joined as an individual contributor, the app had already been released to the public and their user base was growing. The company's goal was to transition all software development in house and begin implementing the next round of features. This transition would occur over the course of a few months to allow time for knowledge transfer between teams.

My first week was mostly filled with onboarding tasks. Then, I was given my first new feature to implement. It was a significant departure from the app’s existing behavior, so I decided to architect and prototype the feature in a separate, simple stand-alone app. This approach allowed me to work independently from the other team while they addressed several bugs. I continued learning about the app in our daily team meetings, so when my feature was ready, I would be well-prepared to integrate it into the main app.

As my feature neared completion, I spent more time reviewing the main app's source code, noting places where I would need to make changes or additions. The remote team was still busily working on the app, adding features and fixing bugs. With multiple developers spread across several time zones, I wondered how they ensured that one developer’s change wouldn’t break another developer’s code. During one of the daily meetings, I asked a developer if they had written any unit tests. He replied, "No, they didn't ask us to do that."

Unit tests are short little bits of test code that run every time a developer attempts to commit a change to the code base. When you write a new function, you can also write a unit test for that function. The test will call the function and look at the result. If the result is unexpected, the test will flag an error. As an example, say I wrote a function called MakeWindowBlue() to change the background color of a window blue whenever a user presses a button. The unit test for this function would call it, then check the background color of the window and make sure it is indeed blue. If it's not then some unexpected change occurred and the developer will be notified immediately, before their breaking change gets integrated into the main code base.

I was alarmed to find out there were no unit tests given the number of developers and their geographic disparity.

I proceeded to integrate my feature and faced my fair share of bugs, which I initially chalked up to my not fully understanding the codebase yet. Eventually, the feature got implemented, and I moved on to my next task. My understanding of the app continued to grow, yet I was still facing friction whenever I tried to add new functionality. Each feature I added introduced its own set of bugs. Progress felt slow—two steps forward, one step back. By now, I had gotten familiar enough with the app to realize it was not built to allow for scaling. There was no solid architecture. Each new feature was being glopped onto the existing codebase like clay in whatever manner allowed it to stick. It was like trying to build a skyscraper using 2x4s. Eventually, the foundation would crumble under the weight.

I raised my concern to leadership and proposed that the app would likely need to be rearchitected and rewritten to sustain the new features that were planned. I estimated the rewrite would take six months. While my concern was acknowledged, I believe they felt there was momentum, and while progress was slow, it was still progress. For a startup, the prospect of rewriting an app from scratch is a tough pill to swallow. Development continued for a few more months, and eventually, the original team was off-boarded. The company set out to hire a second software developer. I needed to strongly convey that we couldn’t continue this way. With the old team gone, this was the perfect time to rebuild the app. While we'd be taking a giant step back, in the long run, a properly architected, scalable app would put us far ahead of our current pace. If I could define enough of the architecture by the time the new developer started, we could begin building the new app right out of the gate.

During my time as a software consultant, I got in the habit of logging every task I completed in a database. Clients could log in daily and see up-to-date reports on exactly what was completed. Old habits die hard, and fortunately, I had been keeping a daily log up to this point. I decided to go through each day and categorize my tasks into five areas: Onboarding, Feature Implementation (developing architecture, writing code), Bug Fixing, Meetings, and Miscellaneous. The Miscellaneous category was a catch-all for everything else such as setting up new equipment, installing software, working in JIRA, Notion, Confluence, etc., making builds, and doing code reviews. I then graphed this data to show how much time each day was spent in each category.

Sep-Nov 2021

My first week or so was dominated by onboarding tasks (blue) and meetings (yellow). Then, you can see my independent work on the new feature begin (green) until November, where my time shifted primarily to bug fixing (red) on the original app. Most days were eight hours long, but toward the right side of the graph, you’ll notice several longer days, including a few that stretched beyond twelve hours.

In terms of percentages, I spent:

  • 9.83% of my time onboarding,
  • 39.9% implementing features,
  • 14.2% in meetings,
  • 11.99% on miscellaneous tasks,
  • and a staggering 24.08% fixing bugs.

Nearly a quarter of my time was dedicated solely to fixing bugs!

Continuing on, here's a snapshot of the next couple of months:


Dec 2021-Feb 2022

  • Features: 25.41%
  • Meetings: 14.79%
  • Misc: 19.75%
  • Bug Fixing: 40.05%

FORTY PERCENT of my time was spent fixing bugs!

This data made a compelling case to leadership, convincing them to green light rebuilding the app from scratch.

Software Development Schedule

As a startup, it's crucial to deliver accurate timelines to your investors and then meet those expectations you've set. When I told leadership it would take six months to rewrite the app, I had actually done some legwork ahead of time to boost confidence in that estimate. I knew the entire company and its investors would be counting on me to deliver. From my many years of estimating software projects, I've honed the process down to a few key steps that help ensure project success.

Flows

Flows are an essential software architecture tool. We had a detailed design laid out in Figma—a tool graphic designers use to depict how each screen will look and behave in a product's UI. I went through each screen in the design and built a comprehensive flow chart that mapped out not only the screens but also the logic that drives them. The flow serves as a blueprint that aligns every stakeholder on what to expect in the final product.

Often, some functionality would get overlooked in the initial Figma design. To capture these discrepancies, I marked them with red sticky notes in the flow. Then, I held meetings with the designers to resolve each of these issues, ensuring no critical feature or interaction was missed. Below is one of the very first login flows for the app. While the intricate details of the flow aren’t necessary for this example, take note of how many red sticky notes there are.

Early Login Flow

In the old app, the developers took the Figma design and immediately started coding it up. All of the areas where you see red sticky notes in the flow were present back then too, but they went unaddressed until they manifested as bugs in the code. A good rule of thumb is that fixing bugs in code is about ten times slower than resolving them in the planning stage.

This is why I make sure all red stickies are resolved in the flow before writing a single line of code. It saves time, money, and frustration down the road. After many iterations and feedback sessions with the team, the final flow ended up looking like this:

Current Login Flow

Once the flow is devoid of red stickies, it’s ready to be implemented in code. At this point, the coding typically goes very smoothly and often works on the very first try. There’s almost no need for debugging.

While this example outlines a specific function within a mobile app, flows can be used for any area of software development. For instance, an embedded firmware project could benefit from a similar approach. You could map out the main run loop, interrupt triggers, timers, serial communication paths, memory management algorithms, and more—all in advance. This will make the coding process straightforward, and bugs will be few and far between.

With the expectation of minimal bugs during development, it becomes much easier to meet the timeline set in your initial software development estimate.

Software Estimation

Many developers find software estimation to be a tedious and unreliable process. For example, when contemplating the details of a login screen in an app, a developer might rely on "story points"—a popular SCRUM technique where a number from the Fibonacci sequence is chosen to indicate the perceived level of complexity. However, different developers can have varying interpretations of how much time 3, 5, or 8 story points represent. This inconsistency can lead to misunderstandings and unrealistic expectations.

Over the years, I’ve refined my own process for estimating software projects. Here’s how I approach it:

  1. Break Each Feature into Individual Tasks This step forces you to think through all the different components that will need to be coded. It ensures that no detail is overlooked and sets a solid foundation for the estimation.
  2. Provide a Low and High Time Estimate for Each Task I usually round to the nearest quarter hour. Using concrete time estimates instead of abstract metrics like story points helps align everyone’s expectations on how long each task is likely to take.
  3. Add 14% for "Test and Debug" I’ll elaborate on this specific percentage in a moment, but it’s a crucial buffer for handling unexpected issues.
  4. Calculate the Average of Your High and Low Estimates Once you have the total low and high estimates for all tasks, average them and make that value your new "low estimate" to present to the customer. I’ll explain why I choose to present this way below.

Here’s a snippet from an estimate I did for a project several years ago. Each feature is highlighted in dark red, showing the total low and high hours for that feature. Below that, the individual tasks are broken down with their own low and high estimates. The cost range is also included. If the customer chooses to hold off on implementing a certain feature, I simply delete it from the spreadsheet, and the total project cost (not shown in the example below) is updated automatically.

Estimate Snippet

The range between the total Cost Low and total Cost High is often quite large and I’ve found that projects tend to land closer to the high end once all is said and done. To account for this, I average the total Cost Low and Cost High values to create a new Cost Low estimate that is presented to the customer. This produces a tighter range between low and high, giving the customer a more realistic expectation of what the project will cost.

Initially, I added a 10% buffer to the total estimate to cover time spent on testing and debugging. However, some projects still came in over the high estimate. To refine my process, I incrementally increased this buffer until the majority of my completed projects consistently fell within the original estimate range. I eventually settled on a 14% buffer—split evenly, this would be 7% for testing and 7% for debugging. In practice, I usually spend more time on testing than debugging, so for me, it’s more like 9% for testing and 5% for debugging.

I invest considerable effort into my estimates, and while one might argue that this time could be lost if a customer chooses another provider, I've found that such scenarios are rare. In my experience, customers generally appreciate the thoroughness of the estimates. Typically, if the estimate exceeds their budget, they tend to opt for scaling back some features rather than abandoning the project entirely.

The New App

While you were reading about how I estimate software projects, I was busy flowing out and building the new app! Here's how my time was spent:

July / August 2022

  • Features: 69.27%
  • Meetings: 15.39%
  • Miscellaneous: 11.96%
  • Bug Fixing: 3.37%

Key takeaways: the time spent fixing bugs dropped significantly, well below my 5% target! Most days were regular 8-hour days with minimal overtime, yet feature implementation soared to almost 70%. This is what healthy software development looks like!

Here's a snapshot of the next couple of months:

September / October 2022

  • Features: 68.61%
  • Meetings: 19.85%
  • Miscellaneous: 9.93%
  • Bug Fixing: 1.61%

Another healthy development period! If I could make any improvement, it would be to reduce the amount of time spent in meetings, which would allow for even more time on feature implementation. I reviewed my logs for those occasional overtime days and found there wasn’t anything urgent—I think I was just excited to be working on a smooth-running project!

The app wrapped up within the estimated six months and was successfully released to customers. It featured a silky-smooth user interface and was built with scalability in mind, making it easy to add new features moving forward.

Conclusion

Spending time up front on planning and architecture will save you time and minimize excessive debugging effort down the road. Just like a builder needs a finalized architectural plan before laying the foundation, a software developer will build a much more robust solution with a well-thought-out software architecture. Having a detailed software estimate is invaluable as it allows you to reference the low and high bounds for each task as you're working on them, helping you stay on track.

Creating a comprehensive estimate forces you to consider the implementation challenges you’ll face, while the flow chart aligns the entire team’s expectations. Coding shouldn’t begin until all red stickies have been resolved. When you’re coding against a solid architecture, development generally moves quickly and works correctly the first time. As a result, debugging time typically accounts for 5% or less of the total development effort.

If you have any questions, feel free to leave a comment below, and I’ll do my best to respond. I hope you found this information useful in your quest to make software development projects more efficient. The next time you’re browsing the App Store and you see “bug fixes” in the release notes, you can chuckle (as I do) and say, “Not in my apps.”


Chris Blanz

UX & UI Designer | Webflow Developer

1 个月

An ounce of prevention is worth a pound of cure.

要查看或添加评论,请登录

社区洞察

其他会员也浏览了