Types are build-time guarantees, assertions are run-time guarantees.
Do we software developers perhaps emphasise the former a little too much and the latter not quite enough?
One way assertions could help is:
Better error messages
Error messages from failed assertions can be:
- More informative
- More traceable
Quite often a server response or user input will generate some value which our code was not expecting. It will fail with a cryptic message, e.g. about some value not being a valid number and a mile-long stack trace. Or worse yet, fail silently and cause downstream data corruption or other issues. It's difficult and annoying to debug these errors and trace them to their origin.
For example:
function getDepartmentCode(zoneCode: number) {
return Math.floor(zoneCode / 1000);
}
// Returns: `10`
getDepartmentCode(10023);
// Returns: `NaN`
getDepartmentCode("10023b" as Number);
We need not write poorly typed code to generate this kind of error. It might be a result of bad user input or a bad server response.
Suppose we add an assertion to our function, which reports a more developer-friendly error message.
function getDepartmentCode(zoneCode) {
if (isNaN(zoneCode)) {
throw new Error("Department code should be numeric.");
}
return Math.floor(zoneCode / 1000);
}
// Returns: `10`
getDepartmentCode("10023");
// Throws: Error: Department code should be numeric.
getDepartmentCode("10023b");
Now we can spot the error more clearly, because it throws an unambiguous exception.
Also, we can much more quickly & easily pinpoint where in the code this error happened. (For example, find-in-files for the error message will more quickly lead us to its origin.) We can then work out why it happened. For example, which server response or user input caused it and how it could be fixed.
In conclusion, while type safety is great at compile-time, run-time checks such as assertions are still valuable.
Here's a utility function I wrote, to quickly generate informative assertions. Hope you find it useful!
export function assert(
condition: unknown,
message?: string
): asserts condition {
if (!condition) {
throw new AssertionViolationError(message);
}
}
export class AssertionViolationError extends Error {
override name = "AssertionViolation";
constructor(message?: string) {
super(message);
}
}
Top comments (0)