Assertions
You can prevent and catch bugs by using tools your programming language and ecosystem provide. Strongly-typed languages allow the compiler to help you ensure values are what you specify. API contracts and function signatures are more ways to be specific so you get the outputs you expect. Tests verify behaviour.
Programming languages also have assertions. For example in C/C++, assert(x < 20);
or assert(somePointer != nullptr);
.
If the expression evaluates to false, it will cause a crash. If you have a debugger attached, it will break and allow you to continue if you want to.
These are useful because they can catch bad-state situations sooner. Sometimes a crash will occur and the root cause was much earlier in the program execution, making it hard to debug from a crash dump. Another benefit is that you can remove all assertions for specific build configurations. You can have all the checks in place for debug builds and gain performance back when you release.
Today asserts are used in tests but less in the main codebase. In my career only one workplace heavily used asserts in the code, which was at Relic Entertainment. They had a custom game engine written in C++. Assertions were all over the place, checking that values were what was expected. There was a library of assertion functions that allowed you to be more specific and descriptive. They eventually added functionality so that asserts would display a pop-up window with details, would send notifications to developers, and could be skipped and ignored. This changed the asserts into something else, which sparked a big internal debate. That’s a story for another day…