Skip to main content

What is Poor Exception Strategy?

Poor Exception Strategy (also known as “Anti-Pattern Exception Handling” or “Exception Smells”) refers to practices that undermine effective error management by creating code that’s difficult to debug, maintain, or extend. It’s not a specific technique but rather a category of bad practices that developers should avoid. The core problem it addresses is the tendency to treat exception handling as an afterthought rather than a strategic design consideration.

How it works in C#

Custom Exceptions

Explanation: Poor strategy with custom exceptions involves creating exception classes that don’t follow established conventions, lack meaningful information, or violate the principle of specificity. This includes generic exceptions that don’t help identify the root cause, exceptions without proper constructors, or creating exceptions for scenarios that should use built-in types.
// POOR STRATEGY EXAMPLE - Generic custom exception
public class MyAppException : Exception
{
    // Problem: Too generic, doesn't convey specific error
    public MyAppException(string message) : base(message) { }
}

// BETTER APPROACH - Specific custom exception
public class InvalidOrderStateException : Exception
{
    public int OrderId { get; }
    public string CurrentState { get; }
    public string RequiredState { get; }
    
    // Provides context-rich information
    public InvalidOrderStateException(int orderId, string currentState, string requiredState) 
        : base($"Order {orderId} is in {currentState} state but requires {requiredState}")
    {
        OrderId = orderId;
        CurrentState = currentState;
        RequiredState = requiredState;
    }
    
    // Proper serialization support
    protected InvalidOrderStateException(
        System.Runtime.Serialization.SerializationInfo info,
        System.Runtime.Serialization.StreamingContext context) : base(info, context) 
    {
        OrderId = info.GetInt32(nameof(OrderId));
        CurrentState = info.GetString(nameof(CurrentState));
        RequiredState = info.GetString(nameof(RequiredState));
    }
}

Filter Usage

Explanation: Poor exception filtering involves catching overly broad exceptions, using exception filters incorrectly, or creating filters that mask underlying problems. This leads to catching exceptions that should bubble up or handling different error types identically.
// POOR STRATEGY - Overly broad catch with incorrect filtering
try
{
    await ProcessOrderAsync(order);
}
catch (Exception ex) when (ex is ArgumentException || ex is InvalidOperationException)
{
    // Problem: Different exception types handled identically
    // This masks the distinct nature of each error
    logger.LogError("Something went wrong: {Message}", ex.Message);
    return Result.Failure("Processing failed");
}

// BETTER APPROACH - Specific filtering with distinct handling
try
{
    await ProcessOrderAsync(order);
}
catch (InvalidOrderException ex) when (ex.OrderId > 0)
{
    // Specific handling for invalid orders with valid IDs
    logger.LogWarning("Invalid order {OrderId}: {Issue}", ex.OrderId, ex.Message);
    return Result.InvalidInput(ex.Message);
}
catch (DatabaseConnectionException ex) when (ex.IsTransient)
{
    // Retry logic for transient database errors
    await RetryOperationAsync(() => ProcessOrderAsync(order));
    return Result.Success();
}
catch (Exception ex)
{
    // Generic catch only for truly unexpected errors
    logger.LogError(ex, "Unexpected error processing order");
    throw; // Preserve stack trace
}

Global Error Handling

Explanation: Poor global error handling involves creating error handlers that swallow exceptions, leak sensitive information, or fail to provide adequate context for debugging. This includes empty catch blocks, exposing internal implementation details, or not logging sufficient information.
// POOR STRATEGY - Global handler that swallows exceptions
public class PoorGlobalHandler
{
    public static void HandleException(Exception ex)
    {
        // Problem: No logging, no re-throwing, just silent failure
        MessageBox.Show("An error occurred");
    }
}

// BETTER APPROACH - Comprehensive global error handling
public class GlobalExceptionHandler
{
    private readonly ILogger _logger;
    private readonly bool _isDevelopment;
    
    public GlobalExceptionHandler(ILogger logger, IHostEnvironment environment)
    {
        _logger = logger;
        _isDevelopment = environment.IsDevelopment();
    }
    
    public async Task HandleExceptionAsync(HttpContext context, Exception ex)
    {
        _logger.LogError(ex, "Unhandled exception occurred");
        
        // Don't expose internal details in production
        var response = _isDevelopment 
            ? new { error = ex.Message, stackTrace = ex.StackTrace }
            : new { error = "An error occurred. Please try again." };
        
        context.Response.StatusCode = ex switch
        {
            UnauthorizedAccessException => 401,
            ValidationException => 400,
            _ => 500
        };
        
        await context.Response.WriteAsJsonAsync(response);
    }
}

// Registration in Program.cs
builder.Services.AddSingleton<GlobalExceptionHandler>();
app.UseMiddleware<GlobalExceptionHandler>();

Why is Poor Exception Strategy Important?

Understanding poor exception strategy provides three key benefits:
  1. Maintainability (SOLID Principle): Proper exception strategy adheres to the Single Responsibility Principle by ensuring each exception handler has a clear, specific purpose rather than catching everything indiscriminately.
  2. Debugging Efficiency (Observability Pattern): Strategic exception handling provides rich contextual information that accelerates root cause analysis and reduces mean time to resolution during incidents.
  3. System Resilience (Circuit Breaker Pattern): Well-designed exception strategies enable graceful degradation and recovery patterns, preventing cascading failures and maintaining system stability under error conditions.

Advanced Nuances

1. Exception Filter Performance Considerations

Exception filters in C# execute before the stack is unwound, which has performance implications. Poor strategy might involve expensive operations in filters:
// POOR - Expensive operation in filter
catch (Exception ex) when (CheckDatabaseForErrorDetails(ex.Message) == true)
{
    // Filter queries database before handling
}

// BETTER - Lightweight filter with heavy logic in handler
catch (Exception ex) when (ex is DatabaseException dbEx && dbEx.IsTransient)
{
    var shouldRetry = await CheckRetryPolicyAsync(dbEx);
    if (shouldRetry) await RetryOperationAsync();
}

2. Async Exception Handling Patterns

Poor strategy often mishandles exceptions in async contexts, particularly with async void methods or improper Task exception handling:
// POOR - Async void with lost exceptions
public async void ProcessDataAsync()
{
    // Exceptions here may crash the process
    await SomeOperationAsync();
}

// BETTER - Proper async exception handling
public async Task<bool> ProcessDataAsync()
{
    try
    {
        await SomeOperationAsync();
        return true;
    }
    catch (OperationCanceledException)
    {
        // Specific handling for cancellation
        return false;
    }
}

3. Exception Shielding in Distributed Systems

In microservices architectures, poor strategy might leak internal exceptions across service boundaries:
// POOR - Leaking internal exceptions
public async Task<Order> GetOrderAsync(int id)
{
    try
    {
        return await _repository.GetOrderAsync(id);
    }
    catch (SqlException ex) // Internal detail exposed
    {
        throw new HttpResponseException(500, ex.Message);
    }
}

// BETTER - Proper exception shielding
public async Task<Order> GetOrderAsync(int id)
{
    try
    {
        return await _repository.GetOrderAsync(id);
    }
    catch (DataAccessException ex)
    {
        // Shield internal details, provide consumer-friendly error
        throw new ServiceUnavailableException("Order service temporarily unavailable");
    }
}

How This Fits the Roadmap

Within the “Exception Handling” section of the Advanced C# Mastery roadmap, understanding Poor Exception Strategy serves as a critical foundation. It’s a prerequisite for mastering:
  • Advanced Exception Patterns: Understanding what not to do clarifies the value of patterns like Exception Shielding, Retry Patterns, and Circuit Breakers
  • Performance Optimization: Recognizing poor exception handling helps identify performance bottlenecks in error management
  • Observability Implementation: Sets the stage for implementing structured logging, distributed tracing, and monitoring
This concept unlocks more advanced topics like defensive programming techniques, resilience patterns (Polly library usage), and enterprise-grade error management strategies that are essential for senior developers building robust, maintainable systems.

Build docs developers (and LLMs) love