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