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 doesn
t conform to it
s 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 aDog
and you call it with aCat
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
andfree
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 putsDog
objects into a bag, and takes them out again. Another developer putsCat
objects into the bag which do not have abark
method. If you pull out an object from the bag and callbark
on it, it will fail. Generics type theDog
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 withNull
turns into aNullPointerException
. Another way is to call a method with some data where the method expects the data to exist, e.g.lastname
ofcustomer
. Often if a very small percentage of customers don\'t havelastname
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 onNull
. 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 ScalaInt @@ 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 aCustomer
with a lastname:Customer & HasLastName
andCustomer { lastnamename: string | undefined}
prevents giving the method a customer that has anundefined
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.