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");
}
Why Refactor?
While this code works, there are several issues with this nested if
structure:
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.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.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.Testing Complexity
Unit tests for deeply nested conditions can become cumbersome, as each condition requires setup and verification for multiple scenarios.Violation of Clean Code Principles
Nestedif
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:
Guard Clauses
Exit early to eliminate unnecessary nesting and improve readability.Switch Expressions
Use concise expressions for straightforward scenarios with multiple outcomes.Strategy Pattern
Encapsulate access rules into reusable, testable strategies.Specification Pattern
Combine multiple conditions into clear, reusable specifications.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");
}
}
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);
}
}
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");
}
}
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");
}
}
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);
}
}
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)