Table of contents
- 1. Input Validation
- 2. Error Handling
- 3. Boundary Checks
- 4. Invariants and Assertions
- 5. Fail-safe Defaults
Defensive code refers to a programming practice where a developer writes code that anticipates and handles potential issues or errors that might occur during the execution of a TypeScript application.
The goal of defensive coding is to improve the reliability, robustness, and maintainability of TypeScript applications by ensuring it can handle unexpected or incorrect input, recover gracefully from failures, and provide meaningful error messages or responses.
Here are the most straightforward defensive coding principles along with their examples, enabling you to seamlessly integrate them into your codebase.
Input Validation
Checking input data for correctness, completeness, and sanity before processing it. This helps prevent unexpected behavior due to invalid or malicious input.
Example:
1 |
|
In this example, the greet
function checks if the provided name is empty or consists only of whitespace characters. If so, it throws an error with a clear message.
Error Handling
Using exception handling or error codes to deal with errors that may occur during the execution of the program. This includes providing clear, helpful error messages to users and logging relevant information for debugging purposes.
Example:
1 |
|
Here, the getResult
uses a try-catch block to handle any errors that might occur, logging the error message and returning a default value of null
.
Boundary Checks
Ensuring that array indices, buffer sizes, and other data boundaries are respected to avoid buffer overflows, index out-of-bounds errors, and other issues related to memory access.
Example:
1 |
|
In code above, the getArrayElement
function checks if the provided index is within the valid range for the arr
array. If the index is out of bounds, it logs an error message and returns null
.
Invariants and Assertions
Invariants are conditions or assumptions that should always hold true at certain points in a program’s execution. They serve as a way to ensure that the program’s state remains consistent and predictable. Using assertion functions can enforce assumptions about the state of the program at various points in its execution. These help catch programming errors early and can serve as a form of documentation for the expected behavior of the code.
Example:
1 |
|
The squareRoot
function asserts that the input x
is non-negative. If the input is negative or zero, it throws an error with a clear message.
Fail-safe Defaults
Providing default values or behaviors for situations where the program cannot determine the appropriate action to take due to an error or unexpected input.
Example:
1 |
|
In the createUser
function, a fail-safe default age
is set if no value is provided. This ensures that the function will always return a valid User
object.
By incorporating these principles, defensive coding helps to minimize the risk of software bugs, security vulnerabilities, and other issues that can lead to crashes, data loss, or incorrect program behavior. Remember, these are just simple examples to illustrate various defensive coding practices.