Building Code That’s Ready for Anything
- What is Defensive Programming?
- Why is Defensive Programming Important?
- Common Defensive Programming Techniques
- When is Defensive Programming Too Much?
- Final Thoughts
What is Defensive Programming?
Defensive Programming is an approach in which developers anticipate potential issues and write code to manage unexpected inputs, system failures, or errors.
Why is Defensive Programming Important?
Defensive programming enables the programmer to view the code from several angles: a user's perspective, an attacker's viewpoint, and a careless developer's stance. Additionally, it boosts the chances of creating bug-free software.
Benefits of Defensive Programming
Handles the Unpredictable
Users don’t always interact with software as we expect. Defensive programming allows your application to handle these situations effectively.
Prevents Catastrophic Failures
An unhandled error can result in significant crashes or data loss. Defensive programming helps mitigate this.
Improves Code Maintainability
Writing defensively makes your code simpler to debug, extend, and maintain for future developers.
Increases Security
Validating and sanitizing inputs can prevent security vulnerabilities like SQL injection or cross-site scripting.
Common Defensive Programming Techniques
Applying the following guidelines can help us implement defensive programming effectively. These principles ensure that your code is robust, secure, and able to handle unexpected situations gracefully.
Validate Inputs and Outputs
It’s crucial to ensure that all incoming and outgoing data adhere to the expected formats and constraints since user input, API responses, and external data sources cannot be trusted without proper validation. Furthermore, always sanitize data before displaying it to users to prevent any potential vulnerabilities from arising.
// Validating Forms
import { useForm } from "react-hook-form";
import { z } from "zod";
const UserSchema = z.object({
email: z.string().email(),
name: z.string().min(2),
password: z.string().min(7),
});
const UserForm = () => {
const { handleSubmit, register } = useForm({
defaultValues: {
name: "",
email: "",
password: "",
},
});
return (
<form
onSubmit={handleSubmit((data) => {
const response = UserSchema.safeParse(data);
if(response.success) {
console.log("This is Sparta🛡️");
}
})}
>
<input {...register("name")} type="text" placeholder="Name" />
<input {...register("email")} type="email" placeholder="Email" />
<input {...register("password")} type="password" placeholder="Password" />
<button type="submit">Submit</button>
</form>
);
};
Validating forms is possible with vanilla javascript, but this process is unadvised.
Using a proven library like zod or yup makes your life easier.
In the example above, we are using zod to verify that the response we receive from the user aligns with the schema/requirements described. We can also apply part of this approach to validate API responses.
UserSchema.safeParse(apiJsonResponse);
Null Checks
Prevent null
exceptions by checking values before use. In JavaScript, we can utilize optional chaining to diminish the chances of these occurring.
const user = null;
user?.showHello(); // No Error
Fail Gracefully
It's important to design your software so that it can handle errors and exceptions gracefully, ensuring it doesn't crash. You can add helpful features like try-catch blocks, fallback procedures, and default values to keep everything running smoothly. It’s also a great idea to display clear and descriptive error messages when issues arise, as this can make the experience much more user-friendly!
Testing
Taking the time to write tests that ensure specific parts of your software function as expected makes a big difference.
Avoid Magic Numbers and Hard-Coded Values
Use named constants or enums instead of arbitrary numbers or strings. This method enhances the readability of your code, simplifies maintenance, and reduces the likelihood of errors during updates.
Limit the Scope of Variables
Define variables within the narrowest scope necessary to minimize unintended side effects. This approach keeps your code modular, making it easier to maintain and debug. Additionally, we must avoid all unnecessary globally scoped variables.
When is Defensive Programming Too Much?
Although defensive programming is crucial, excessive implementation may result in excessively complex code. Finding the right balance can sometimes be challenging; I sometimes find myself in this situation.
To address any confusion, I recommend you consider the following questions:
- Is the code becoming bloated and unreadable?
- Will the current validations, checks, etc., degrade the user experience?
If that’s the case, I suggest lowering the number of checks. You can move forward if user experience is a secondary concern and application security is the top priority.
Final Thoughts
Defensive programming extends beyond merely preventing crashes; it focuses on developing reliable, secure, and maintainable software. Always ask yourself: "What could go wrong?" and write your code with that in mind.
Author Of article : Wisdom Ikezi Read full article