Top defensive programming principles with examples

Explore the Top 20 Defensive Programming Principles in .NET and elevate your coding skills. Discover techniques for enhancing software security, reliability, and maintainability with practical examples. Perfect for .NET developers seeking to refine their defensive programming strategies.

What is defensive programming?

Defensive programming is a design method used in software development to ensure the program continues functioning under unforeseen circumstances.

This approach involves writing code that is resilient to erroneous input and user misuse.

Defensive programming in .NET and Umbraco

As we explore the essence of defensive programming, it's crucial to understand how each principle can dramatically improve the security and robustness of your .NET applications.

From validating inputs to minimize the risks of erroneous data affecting your application to using strong typing to prevent bugs, each strategy is designed to protect and optimize your coding environment.

For those interested in extending their expertise beyond .NET, becoming proficient with Umbraco CMS is another valuable skill set that complements advanced defensive programming techniques.

Umbraco developers, in particular, must understand how to apply these principles within the CMS's framework to enhance security and performance.

For an in-depth look at the key skills every Umbraco developer should master, check out our detailed guide here: Umbraco Developer Key Skills.

Top defensive programming principles in .NET Framework

What are defensive programming examples?

Defensive programming is a proactive approach to improving software quality and reliability by anticipating and handling potential errors and misuses before they become a problem.

Below are detailed examples and practices that exemplify defensive programming in .NET, each illustrating a specific principle designed to prevent common pitfalls in software development:

Validate Your Inputs to Enhance Security

Always ensure input is valid before use to prevent incorrect data from affecting your application.

Not Recommended:

public void SetUsername(string username)
{
    // No validation
    Console.WriteLine("Username set to " + username);
}

Recommended:

public void SetUsername(string username)
{
    if (string.IsNullOrWhiteSpace(username))
    {
        throw new ArgumentException("Username cannot be empty.");
    }
    Console.WriteLine("Username set to " + username);
}

Embrace Failing Fast to Catch Errors Early

Catch and handle errors at the point of occurrence to prevent issues from propagating further in the system.

Not Recommended:

public void AddItem(Item item)
{
    // Assuming item is not null
    inventory.Add(item);
}

Recommended:

public void AddItem(Item item)
{
    if (item == null)
    {
        throw new ArgumentNullException(nameof(item), "Added item cannot be null.");
    }
    inventory.Add(item);
}

Assert Conditions to Ensure Code Correctness

Use assertions to verify that a condition always holds true; they are used to catch programming errors.

Not Recommended:

public double CalculateArea(double width, double height)
{
    // No assertion
    return width * height;
}

Recommended:

public double CalculateArea(double width, double height)
{
    Debug.Assert(width > 0 && height > 0, "Width and height must be positive numbers.");
    return width * height;
}

Handle Exceptions Strategically to Maintain Control

Handle exceptions to maintain control flow and provide error processing without crashing the application.

Not Recommended:

try
{
    ProcessFile("data.txt");
}
catch
{
    // Generic catch, does nothing
    Console.WriteLine("An error occurred.");
}

Recommended:

try
{
    ProcessFile("data.txt");
}
catch (IOException ex)
{
    Log.Error("Failed to process the file.", ex);
    NotifyUser("An error occurred. Please try again later.");
}

Limit Your Variable Scope for Cleaner Code

Minimize the scope of variables to reduce complexity and the chance of misusing variables outside their intended use.

Not Recommended:

int i;
for (i = 0; i < users.Count; i++)
{
    Console.WriteLine(users[i]);
}
// i is still accessible here

Recommended:

for (int i = 0; i < users.Count; i++)
{
    Console.WriteLine(users[i]);
    // 'i' is limited to the scope of this loop
}

Use Immutable Objects to Avoid Unwanted Changes

Use immutable objects to reduce runtime errors due to accidental change of state.

Not Recommended:

public class UserInfo
{
    public string Username { get; set; }
    public DateTime BirthDate { get; set; }
}

Recommended:

public class UserInfo
{
    public string Username { get; }
    public DateTime BirthDate { get; }

    public UserInfo(string username, DateTime birthDate)
    {
        Username = username;
        BirthDate = birthDate;
    }
}

Always Check Return Values to Avoid Errors

Always check the return values of methods to handle possible errors or unexpected values.

Not Recommended:

int number;
int.TryParse(input, out number);
// Uses number without checking if the parsing was successful

Recommended:

if (int.TryParse(input, out int number))
{
    Console.WriteLine($"Parsed number: {number}");
}
else
{
    Console.WriteLine("Invalid number entered.");
}

Default to Secure Settings to Protect Your Applications

Default to the most secure settings in your applications to prevent security vulnerabilities.

Not Recommended:

public class ApiClient
{
    public bool UseEncryption = false;  // Insecure default

    public void SendData(string data)
    {
        if (UseEncryption)
        {
            data = Encrypt(data);
        }
        // Send data
    }
}

Recommended:

public class ApiClient
{
    public bool UseEncryption { get; set; } = true;  // Secure default

    public void SendData(string data)
    {
        if (UseEncryption)
        {
            data = Encrypt(data);
        }
        // Send data
    }
}

Practice the Principle of Least Privilege for Better Security

Always operate with the least amount of privilege necessary to minimize the impact of potential security breaches.

Not Recommended:

// This method unnecessarily requires administrative privileges
public void DeleteUser(int userId)
{
    // Requires higher privileges
    // Code that deletes user
}

Recommended:

public void DeleteUser(int userId)
{
    // Properly checks and uses minimal privileges necessary
    if (UserHasPermission("DeleteUser"))
    {
        // Code that deletes user
    }
    else
    {
        throw new UnauthorizedAccessException("User does not have delete permissions.");
    }
}

Implement Effective Logging and Monitoring

Implement detailed logging and monitoring to detect, understand, and respond to failures or malicious activities quickly.

Not Recommended:

public void ProcessPayment(decimal amount)
{
    try
    {
        // No logging of payment process steps
        Console.WriteLine("Processing payment.");
    }
    catch (Exception ex)
    {
        Console.WriteLine("Payment failed.");
    }
}

Recommended:

public void ProcessPayment(decimal amount)
{
    try
    {
        Log.Info($"Starting payment processing for amount: {amount}");
        // Payment processing logic
        Log.Info("Payment processed successfully.");
    }
    catch (Exception ex)
    {
        Log.Error("Payment processing failed.", ex);
    }
}

Sanitize Your Data to Prevent Injection Attacks

Cleanse data to eliminate unwanted scripts or SQL elements that could be used in injection attacks.

Not Recommended:

public string CreateGreeting(string name)
{
    // Direct use of user input
    return $"Hello, {name}!";
}

Recommended:

public string CreateGreeting(string name)
{
    name = name.Replace("<script>", "");  // Basic sanitization example
    return $"Hello, {name}!";
}

Avoid Magic Numbers to Improve Code Clarity

Use named constants instead of hard-coded values to improve code clarity and maintainability.

Not Recommended:

if (userType == 1) // Magic number
{
    // Grant admin access
}

Recommended:

const int AdminUserType = 1;
if (userType == AdminUserType)
{
    // Grant admin access
}

Use Parameterized Queries to Secure Your Database

Prevent SQL injection by using parameterized queries that safely handle input data for SQL commands.

Not Recommended:

public void AddUser(string username, string password)
{
    var query = $"INSERT INTO Users (Username, Password) VALUES ('{username}', '{password}')";
    // Unsafe query construction
}

Recommended:

public void AddUser(string username, string password)
{
    using (var connection = new SqlConnection(connectionString))
    {
        var command = new SqlCommand("INSERT INTO Users (Username, Password) VALUES (@Username, @Password)", connection);
        command.Parameters.AddWithValue("@Username", username);
        command.Parameters.AddWithValue("@Password", password);
        connection.Open();
        command.ExecuteNonQuery();
    }
}

Encapsulate Your Data to Protect Its Integrity

Use encapsulation to protect object integrity by exposing only necessary parts of the object to outside interaction.

Not Recommended:

public class Account
{
    public decimal Balance; // Publicly accessible
}

Recommended:

public class Account
{
    private decimal balance;

    public decimal GetBalance()
    {
        return balance;
    }
}

Regularly Update Your Dependencies to Mitigate Vulnerabilities

Keep external libraries and frameworks up-to-date to protect against known vulnerabilities.

Not Recommended:

Ignoring update alerts or failing to check for updates.

Recommended:

Regularly review and update all dependencies to the latest secure versions.

Utilize Strong Typing to Prevent Bugs

Take advantage of C#'s strong typing to prevent type mismatches and ensure more predictable code behavior.

Not Recommended:

object data = "123";
int number = (int)data; // Runtime error if unchecked

Recommended:

int number = int.Parse("123"); // Strongly typed and safe

Opt for Non-executable Stacks to Enhance Security

Use non-executable stacks where possible to prevent certain types of buffer overflow attacks.

Not Recommended:

Allowing executable stacks in environments where security is critical.

Recommended:

Ensuring the operating system and environment are configured to use non-executable stack spaces where supported.

Conduct Thorough Code Reviews for Quality Assurance

Perform regular code reviews to identify potential security issues, bugs, and vulnerabilities.

Not Recommended:

Skipping code reviews due to time constraints.

Recommended:

In the development process, ensure that at least one other developer reviews every piece of code before it is merged into the main branch.

Minimize the Use of Global Variables to Reduce Side Effects

Avoid global variables to reduce side effects and increase the modularity and testability of code.

Not Recommended:

public static string CurrentUser; // Global variable

Recommended:

public class Session
{
    public string CurrentUser { get; private set; } // Encapsulated within a class
}

Build Redundancy and Implement Fail-Safe Measures for Reliability

Implement redundancy and fail-safes in your systems to ensure reliability and availability even under failure conditions.

Not Recommended:

public void PerformBackup()
{
    // Single point of failure in backup strategy
    BackupData();
}

Recommended:

public void BackupData()
{
    try
    {
        PerformBackup();
    }
    catch
    {
        PerformBackup();  // Try again if the first attempt fails
    }
}

Final thoughts on defensive programming

Each of these principles and their examples helps reinforce good coding practices and guard against common pitfalls in software development.

🌐 Explore More: Interested in learning about .NET, Umbraco, and other web development insights?

👉Check out our blog for a wealth of information and expert advice.

↑ Top ↑