Preventing Classes of Bugs

Reducing bugs is a good thing. Bugs demotivate developers, alienate developers from their product and give a bad brand perception. So preventing bugs from being made has interested many people in the past. There are technologies and techniques that can prevent whole bug classes.

  • Typed References Prevents bugs where some code is passed data that doesnt conform to its expectations about attributes or methods. There is this famous slide that says "38% Airbnb bugs preventable with TypeScript according to postmortem analysis". If a method expects a Dog and you call it with a Cat then the method will crash. With typed references every reference of local variables or method parameters is typed and the compiler prevents calls with the wrong variable type. Languages: e.g. Scala, Haskell, Java, Typescript

  • Automatic Testing Prevents unfullfilled requirements. Automatic Tests match the code against specifications and detect code that doesn\'t conform to specifications. Automatic testing also helps with regression bugs, where a bug is fixed but is introduced later again or where something worked and is broken with subsequent code changes. Tools: e.g. Django Testing, Rails Testing, Mocha, Jest, JUnit

  • Exploratory Testing Finds missing or incomplete requirements. When testers with the goal of improving customer perceived quality explorative test an application they find missing requirements and quality improving features. Lately I had a form where I had to enter a tax ID which was 13 digits long. The UI told me they expect a 11 digit tax ID. A good UI would tell the customer "You\'ve entered a 13 digit tax ID. Often this is the case when the first two digits represent the area. These are not needed, do you want to to use the last 11 digits which are a valid tax ID?"

  • Garbage Collection Prevents memory leaks from unfreed memory and prevents bugs from double freeing memory. Manual memory management with malloc and free has the problem of forgetting to free some used memory. This will lead up to memory leaks which will crash the application. When two or more code paths free the same memory this leads to an undefined state and beside security implications this can crash the application. Luckily nearly every programming language today has automatic memory management in one way or the other. Languages: e.g. JavaScript, Ruby, Python, Kotlin, Java

  • Generics Prevents ClassCastExceptions when developers put one type into a container while another developers expects other types in the container. One developer puts Dog objects into a bag, and takes them out again. Another developer puts Cat objects into the bag which do not have a bark method. If you pull out an object from the bag and call bark on it, it will fail. Generics type the Dog bag in a way that you can\'t put inCat objects, so whatever you pull out of the bag, it will be a dog. Languages: e.g. Java, C++, Haskell, Scala

  • Option Monads Prevents uninitialized or non existing data from being accessed. Often with different code paths data can be uninitialized and being Null. Calling a method on a variable with Null turns into a NullPointerException. Another way is to call a method with some data where the method expects the data to exist, e.g. lastname of customer. Often if a very small percentage of customers don\'t have lastname the data might be null. Working with lastname then leads to an exception. Option prevents this because you need to first check if the data is there before you can use it. Declaring the lastname optional prevents calling methods on Null. Languages: e.g. Haskell, Scala

  • Tag Types Prevent a method from getting data that it doesn\'t expect and prevents IllegalArgumentException. For example an integer could be tagged positive with - in Typescript - int & Positive or in Scala Int @@ Positive. Then the method can\'t be called with negative numbers if it expects only positive integers. Tag types can also help with optional data, for example when a method expects a Customer with a lastname: Customer & HasLastName and Customer { lastnamename: string | undefined} prevents giving the method a customer that has an undefined lastname. Tools: e.g. Scala refined, Typescript taghiro

  • Immutable Data Prevents data corruption and inconsistent data with persistent data structures. With persistent data structures if the data in it is changed, a copy of the changed data is created and only the code that changed the data sees the changes. If your concurrent code gets some data the code can be sure that no other code manipulates it. This prevents data corruption where some code changes one part of the data and other code changes other parts of the shared data. Immutable data also prevents ConcurrentModificationException. And in the case of wrong synchronisation of concurrent code you only get lost updates where the changes of some code to the data are overwritten by other changes, instead of much worse inconsistent data. Languages: e.g. Haskell, Scala, Elm

  • Code Review Prevents missunderstood requirements and API usage. When more people look over written code, requirements are interpreted by more people and missunderstood requirements are found. If the developer is unfamiliar with some (internal) API he might use the wrong way. Code reviews by a more experienced developers will detect wrong API usage.

Using technologies and techniques to prevent whole classes of bugs has the highest impact on the quality of products. It\'s good to choose processes, organizations and programming languages to implement them.

If you have more techniques and technologies that prevent classes of bugs, I\'d wish to here them on Twitter @Codemonkeyism.