DEV Community

mohamed Tayel
mohamed Tayel

Posted on

Refactoring Complex Conditions: Clean Code Solutions for Nested If Statements

Learn how to refactor nested if statements in C# for cleaner, more maintainable code. Discover practical techniques like guard clauses, patterns, and strategies with detailed examples to simplify complex logic.

Scenario: Nested If Statements

Suppose you have the following code to handle user access logic. At first glance, it seems straightforward:

if (user != null)
{
    if (user.IsActive)
    {
        if (user.Role == "Admin")
        {
            if (user.HasPermission("View"))
            {
                Console.WriteLine("Access granted");
            }
            else
            {
                Console.WriteLine("Permission denied");
            }
        }
        else
        {
            Console.WriteLine("Role not authorized");
        }
    }
    else
    {
        Console.WriteLine("User is not active");
    }
}
else
{
    Console.WriteLine("User not found");
}
Enter fullscreen mode Exit fullscreen mode

Why Refactor?

While this code works, there are several issues with this nested if structure:

  1. Reduced Readability

    The deeper the nesting, the harder it becomes to understand the flow of logic at a glance. This increases the mental effort required to read and comprehend the code.

  2. Increased Cognitive Overload

    With multiple layers of conditions, developers must keep track of each layer’s context, making the logic harder to follow and debug.

  3. Poor Maintainability

    If you need to modify, extend, or test the logic (e.g., adding a new condition for access), you’ll have to carefully navigate the nested structure, increasing the risk of errors.

  4. Testing Complexity

    Unit tests for deeply nested conditions can become cumbersome, as each condition requires setup and verification for multiple scenarios.

  5. Violation of Clean Code Principles

    Nested if blocks contradict clean coding practices like single responsibility and early exit, which promote clear and concise logic.


Refactoring Options

To address these challenges, there are several ways to refactor nested if blocks:

  1. Guard Clauses

    Exit early to eliminate unnecessary nesting and improve readability.

  2. Switch Expressions

    Use concise expressions for straightforward scenarios with multiple outcomes.

  3. Strategy Pattern

    Encapsulate access rules into reusable, testable strategies.

  4. Specification Pattern

    Combine multiple conditions into clear, reusable specifications.

  5. Polymorphism

    Leverage object-oriented principles to handle user-specific logic dynamically.

Each of these approaches brings unique benefits, from simplifying the logic to improving testability. Let’s explore each option with detailed examples.


1. Refactoring with Guard Clauses

Full Example:

public class User
{
    public string Name { get; set; }
    public bool IsActive { get; set; }
    public string Role { get; set; }
    public List<string> Permissions { get; set; } = new List<string>();

    public bool HasPermission(string permission) => Permissions.Contains(permission);
}

public class Program
{
    public static void Main()
    {
        User user = new User
        {
            Name = "John Doe",
            IsActive = true,
            Role = "Admin",
            Permissions = new List<string> { "View", "Edit" }
        };

        CheckAccess(user);
    }

    public static void CheckAccess(User user)
    {
        if (user == null)
        {
            Console.WriteLine("User not found");
            return;
        }

        if (!user.IsActive)
        {
            Console.WriteLine("User is not active");
            return;
        }

        if (user.Role != "Admin")
        {
            Console.WriteLine("Role not authorized");
            return;
        }

        if (!user.HasPermission("View"))
        {
            Console.WriteLine("Permission denied");
            return;
        }

        Console.WriteLine("Access granted");
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Refactoring with Switch Expression

Full Example:

public class User
{
    public string Name { get; set; }
    public bool IsActive { get; set; }
    public string Role { get; set; }
    public List<string> Permissions { get; set; } = new List<string>();

    public bool HasPermission(string permission) => Permissions.Contains(permission);
}

public class Program
{
    public static void Main()
    {
        User user = new User
        {
            Name = "John Doe",
            IsActive = true,
            Role = "Admin",
            Permissions = new List<string> { "View" }
        };

        var result = (user != null, user?.IsActive, user?.Role, user?.HasPermission("View")) switch
        {
            (false, _, _, _) => "User not found",
            (true, false, _, _) => "User is not active",
            (true, true, "Admin", false) => "Permission denied",
            (true, true, "Admin", true) => "Access granted",
            _ => "Role not authorized"
        };

        Console.WriteLine(result);
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Refactoring with Strategy Pattern

Full Example:

public class User
{
    public string Name { get; set; }
    public bool IsActive { get; set; }
    public string Role { get; set; }
    public List<string> Permissions { get; set; } = new List<string>();

    public bool HasPermission(string permission) => Permissions.Contains(permission);
}

public interface IAccessStrategy
{
    bool CanAccess(User user);
    string Message { get; }
}

public class UserNotFoundStrategy : IAccessStrategy
{
    public string Message => "User not found";
    public bool CanAccess(User user) => user == null;
}

public class UserNotActiveStrategy : IAccessStrategy
{
    public string Message => "User is not active";
    public bool CanAccess(User user) => user != null && !user.IsActive;
}

public class RoleNotAuthorizedStrategy : IAccessStrategy
{
    public string Message => "Role not authorized";
    public bool CanAccess(User user) => user != null && user.IsActive && user.Role != "Admin";
}

public class PermissionDeniedStrategy : IAccessStrategy
{
    public string Message => "Permission denied";
    public bool CanAccess(User user) => user != null && user.IsActive && user.Role == "Admin" && !user.HasPermission("View");
}

public class AccessGrantedStrategy : IAccessStrategy
{
    public string Message => "Access granted";
    public bool CanAccess(User user) => user != null && user.IsActive && user.Role == "Admin" && user.HasPermission("View");
}

public class Program
{
    public static void Main()
    {
        User user = new User
        {
            Name = "John Doe",
            IsActive = true,
            Role = "Admin",
            Permissions = new List<string> { "Edit" }
        };

        var strategies = new List<IAccessStrategy>
        {
            new UserNotFoundStrategy(),
            new UserNotActiveStrategy(),
            new RoleNotAuthorizedStrategy(),
            new PermissionDeniedStrategy(),
            new AccessGrantedStrategy()
        };

        var applicableStrategy = strategies.FirstOrDefault(s => s.CanAccess(user));
        Console.WriteLine(applicableStrategy?.Message ?? "Unknown error");
    }
}
Enter fullscreen mode Exit fullscreen mode

4. Refactoring with Specification Pattern

Full Example:

public class User
{
    public string Name { get; set; }
    public bool IsActive { get; set; }
    public string Role { get; set; }
    public List<string> Permissions { get; set; } = new List<string>();

    public bool HasPermission(string permission) => Permissions.Contains(permission);
}

public interface ISpecification<T>
{
    bool IsSatisfiedBy(T entity);
}

public class IsUserActiveSpecification : ISpecification<User>
{
    public bool IsSatisfiedBy(User user) => user != null && user.IsActive;
}

public class IsAdminSpecification : ISpecification<User>
{
    public bool IsSatisfiedBy(User user) => user != null && user.Role == "Admin";
}

public class HasPermissionSpecification : ISpecification<User>
{
    private readonly string _permission;
    public HasPermissionSpecification(string permission) => _permission = permission;

    public bool IsSatisfiedBy(User user) => user != null && user.HasPermission(_permission);
}

public class AccessSpecification : ISpecification<User>
{
    public bool IsSatisfiedBy(User user) =>
        new IsUserActiveSpecification().IsSatisfiedBy(user) &&
        new IsAdminSpecification().IsSatisfiedBy(user) &&
        new HasPermissionSpecification("View").IsSatisfiedBy(user);
}

public class Program
{
    public static void Main()
    {
        User user = new User
        {
            Name = "John Doe",
            IsActive = true,
            Role = "Admin",
            Permissions = new List<string> { "View", "Edit" }
        };

        var accessSpec = new AccessSpecification();

        if (accessSpec.IsSatisfiedBy(user))
            Console.WriteLine("Access granted");
        else
            Console.WriteLine("Access denied");
    }
}
Enter fullscreen mode Exit fullscreen mode

5. Refactoring with Polymorphism

Full Example:

public abstract class User
{
    public abstract bool CanAccess();
    public abstract string Message { get; }
}

public class GuestUser : User
{
    public override bool CanAccess() => false;
    public override string Message => "User not found";
}

public class InactiveUser : User
{
    public override bool CanAccess() => false;
    public override string Message => "User is not active";
}

public class AdminUser : User
{
    private readonly List<string> _permissions;

    public AdminUser(List<string> permissions)
    {
        _permissions = permissions;
    }

    public override bool CanAccess() => _permissions.Contains("View");
    public override string Message => CanAccess() ? "Access granted" : "Permission denied";
}

public class Program
{
    public static void Main()
    {
        User user = new AdminUser(new List<string> { "Edit" });
        Console.WriteLine(user.Message);
    }
}
Enter fullscreen mode Exit fullscreen mode

Each example demonstrates a unique way to refactor nested if blocks. These examples are self-contained, complete, and ready to run. Let me know if you'd like further clarification or enhancements!

Top comments (0)