← Back to Kinda Random

Kinda Random No. 39 ·

Bugs

A recipe for having fewer bugs (+ fixing them faster)

By Alireza Ahmadi

Software bugs can be costly. It’s almost impossible to ship defectless software, but by following a few (or more than a few) steps in different stages of the product development lifecycle, we can ship the product with fewer bugs. Here are these steps:

1️⃣ Before writing code

It would help if you started fighting bugs before writing any lines of code. You must always avoid accidental complexity in your system. Every team member (UX Designer, Product Manager, Software Developer, etc.) should develop a discipline for designing smaller and simpler systems to prevent accidental complexity. Below are a few practices that might help you create simpler systems.

Before adding a feature, ask a few questions:

  • What problem are we trying to solve by adding this feature?
  • Can we solve this problem with an alternative, simpler approach?
  • Can we use prototypes, demos, surveys, or similar approaches to test our hypothesis before adding a new source of complexity to our software?
  • If our solution to this problem didn’t work as expected, what are the costs of pivoting to another solution? How can we reduce the costs of pivoting?

If you must add that new feature, try to avoid accidental complexity when architecting the software solution:

  • Look at analytical data and shape usage estimation for the feature with the help of your team. Design your system based on the possible usage and avoid over-optimizing. Not every system has to be perfect.
  • Fewer parts, less complexity. Using five languages to build a dozen services that communicate through three methods (RPC, Event bus, etc.) usually is not the right solution.
  • Document your critical architectural choices and analyze your previous decisions every once in a while.
  • Never settle on the first solution that comes to mind. Try to think and compare at least two or three solutions before choosing one.
  • For most problems, you can find various solutions on the internet. Make sure to check them out and follow best practices when possible.
  • It sounds trivial, but you have to ensure your architecture is low in coupling and high in cohesion; highly coupled architectures are prone to bugs and hard to debug.
  • You’ve tried your best, but the complexity of your solution is still high. Now what? Communicate the possible risks of adding this source of complexity to non-technical team members. Product development is the game of trade-offs, and costs directly impact priorities.

2️⃣ When implementing the solution

Assuming you’ve tried to reach the simplest solution, you should continue fighting bugs when implementing the solution.

  • A few minutes of thinking about possible implementation methods and scenarios (and probably writing them down) before starting to code can go a long way.
  • Unless you’re working on a product with cutting-edge technology, someone on the internet probably implemented your solution before. So, before coding, do a quick search on Google or Github for other people’s implementations; you might find a few scenarios you’ve missed. In other words, follow best practices whenever possible.
  • Use linters and static code analysis tools to find the common problems early on. Having these tools enabled in your editor, hooking them to your git commits, and running them using the CI/CD tool can reduce the risk of running into various accidental issues.
  • Invite one of your colleagues and try peer programming. You can’t imagine how many bugs can be avoided when two people fix each other mistakes in the early steps.
  • If you’re working on the front end (browser or mobile), frequently check your implementation with the team’s designer. Send a video or screenshot of your implementation before starting to optimize your solution. Many front-end bugs happen because of miscommunication between designers and developers.
  • Try Defensive Programming. Handle all errors, don’t trust inputs, and be a little paranoid 😅
  • Write enough (not much) logs and export required metrics (number of requests, DB transaction, etc.). Your future self will thank you when this information helps them fix possible bugs.
  • ALWAYS test your code. Run your code, test it manually, and never assume it works. Try different scenarios. Preferably, write automated tests and run them frequently if the time and context allow you.

3️⃣ Before shipping the changes

Fixing bugs before getting to production is much cheaper and easier. So, now that you’ve successfully implemented what you had in mind:

  • Before committing your code, read the changes line by line. Make sure everything is OK, and you’ve handled all possible scenarios.
  • Ask someone else to review your code. Preferably link the related issue in your issue tracker software (Jira, Linear, Clickup, etc.) in your PR/MR description. If you’re the reviewer, test the functionality of changes in addition to reviewing the code (in an ideal situation, your CI/CD creates a separate build for each PR/MR).
  • Does your company can afford to have a separate QA team? Let the QA team test your changes. However, don’t forget in an ideal situation, the QA team must not find any bugs because going back and forth between dev and QA teams can be expensive and decrease delivery frequency.
  • Dogfooding your changes with users in your company can help you find some critical bugs. If possible, set up a staging or early testing environment for your product.
  • If your user base is large enough and your changes are risky, consider releasing the changes for a smaller segment of users before making them available for all users.
  • If your product has a large user base and you’re not sure about the possible performance impacts of your changes, consider stress testing. However, please consider running stress tests is always costly, so only use them when necessary.
  • Integrating an error monitoring tool (Sentry, Rollbar, etc.) and an observability tool (Grafana, Kibana, etc.) with your software can help you find and fix bugs much easier. Also, you might want to use distributed tracing tools if you have a distributed system.
  • Inform your team and also colleagues who are in contact with users (e.g., support team or account managers) that you will deploy new changes (in other words, have a changelog). Don’t let your team (or other stakeholders) be caught off guard.

4️⃣ When shipping the changes

You’ve tried your best to ship defectless software, but everything might change once your code reaches production. Have a simple and short post-release checklist to make sure you can detect and fix bugs before they overwhelm your users (or before they report them). Here is what this checklist might look like:

  • Do a functionality test in production. Make sure that everything works as expected.
  • Check your error monitoring tool for new errors. If the error rate is surging, try to find out why.
  • Monitor your observability tool for a few minutes. Make sure everything is stable.

Note: this checklist might vary based on the context and the kind of system you’re working on. For example, for the native mobile app, you might want to read reviews after the release.

5️⃣ After shipping the changes

Now that everything is stable and you have found and fixed the majority of the bugs, you must make sure that users (or colleagues) can report bugs if there are any bugs left. Here is what you can do to facilitate this process:

  • Have a single place for all people in your company to report bugs. It can be a Trello board, an issue tracker, a Slack channel, or similar.
  • Periodically check the list of reported bugs, and if it’s related to your changes (or your team in general), try to validate if it’s a bug or not. If you’re positive there is no bug, communicate your reasoning to the bug reporter. If the bug exists, create a task for fixing it, and inform the reporter when the bug has been fixed (if you’re using an issue tracking tool, this might be as simple as adding them as one of the followers of that task).
  • Encourage the bug reporter to give you the required pieces of information when reporting a bug. The rule of thumb here is the more data provided by the reporter, the better. Encourage reporters to send details, screenshots, or even videos. To facilitate this process, you can have a template with a few critical questions, ask reporters to fill them; keep in mind that the goal here is to simplify communication and collaboration, so filling this template should be as easy as possible, and you must not punish the reporter for missing a couple of fields.
  • Allow your users to have a line of communication with you. Share an email, social network account, or support phone number to contact you (or the support team) and report the bugs. You can even integrate a feedback tool with your product. Whatever you do, don’t let your users feel abandoned.

6️⃣ When trying to fix a bug

It’s inevitable that even if you follow the above practices, there will be some bugs to fix. Debugging is hard, and there is no single method that can be used for all bugs, but here are my basic suggestions when it comes to fixing bugs:

  • Start with reproducing the bug. If you can’t reproduce the bug, fixing it is near impossible. If you’ve managed to get enough information from the reporter (or error monitoring tool) about the bug, reproduction should not be that hard.
  • Read your code line by line and search for obvious mistakes.
  • Don’t fall into the trap of your assumptions. If you don’t validate your assumptions before making other assumptions based on them, you will waste hours of your time fixing problems that don’t exist.
  • Describing the bug to yourself (rubber ducking) or writing down your info, assumptions, and findings can be incredibly helpful.
  • It’s normal if you can’t fix a bug after a period of trying. If that is the case, go for a walk or ask one of your colleagues to join you in the debugging process.
  • Learn to use debuggers. Printing “here” throughout the code can work sometimes, but debuggers are more effective for difficult bugs. (Bonus: do you want a debugger that can be attached to your production backend application? try Sidekick.)

7️⃣ After fixing the bug

Congrats on fixing the bug. Now:

  • If your codebase has automated tests, write a test case for the scenario that caused the bug. This would allow future maintainers (which might be you) to remember why there is a random code block in the codebase which doesn’t make sense at first sight.
  • Check related parts of the codebase to find regression bugs your fix might cause. (Hopefully, you have a set of automated regression tests for this)
  • Write a clear commit message explaining the problem and how you’ve fixed it. Please avoid writing “fix” as the commit message. Let the future maintainers know about this bug.
  • Check your company issue tracking system, find the original functional requirements of what you’ve fixed, and make sure your fix doesn’t break those requirements.
  • Inform the reporter that you’ve fixed the bug.
  • Spend a couple of minutes and ensure there is no similar bug in the codebase.

❓ FAQ (possibly)

  1. Should I follow all of the above activities? Of course not. What I’ve written here is the suggested method that works for me (at the time of writing this article), and I thought it might be helpful to share them with others. It would help to choose what works for you based on the context, requirements, resources, and many other factors. Remember, the goal is fixing fewer bugs, not following a particular process.
  2. What’s your suggested method for getting better at finding and debugging bugs? Debugging requires practice. Use other products as your testing ground. Try to find a bug, and if you managed to do it, think about the possible cause of it. Or, search Github for open bug issues and spend some time fixing a few of them.