MST
星途 面试题库

面试题:C# 异常处理体系在复杂多层架构中的优化策略

在一个具有数据访问层、业务逻辑层和表示层的复杂多层C#架构应用程序中,异常处理不当可能导致性能问题、安全隐患和维护困难。请描述你会采取哪些策略来优化异常处理体系,以确保异常能够在合适的层次被捕获、处理和传递,同时最小化对系统性能的影响,并给出相应的代码示例或架构设计思路。
37.4万 热度难度
编程语言C#

知识考点

AI 面试

面试题答案

一键面试

1. 分层处理原则

  • 表示层:主要负责捕获用户输入相关异常,并向用户提供友好的错误提示。例如,输入格式错误等。
try
{
    // 获取用户输入并转换为合适类型
    int num = int.Parse(textBox1.Text);
}
catch (FormatException ex)
{
    MessageBox.Show("输入格式错误,请输入正确的数字。");
}
  • 业务逻辑层:捕获与业务规则相关的异常,进行业务层面的处理或日志记录,然后可以选择重新抛出或返回特定错误信息给表示层。
public void ValidateUser(string username, string password)
{
    try
    {
        // 检查用户名和密码是否符合业务规则
        if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
        {
            throw new BusinessRuleException("用户名和密码不能为空");
        }
    }
    catch (BusinessRuleException ex)
    {
        // 记录业务异常日志
        Logger.Log(ex.Message);
        throw;
    }
}
  • 数据访问层:捕获数据库相关异常,如连接错误、SQL语法错误等,进行适当处理(如关闭连接等),然后抛出包装后的异常给业务逻辑层。
public void SaveData(User user)
{
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        try
        {
            string query = "INSERT INTO Users (Username, Password) VALUES (@Username, @Password)";
            SqlCommand command = new SqlCommand(query, connection);
            command.Parameters.AddWithValue("@Username", user.Username);
            command.Parameters.AddWithValue("@Password", user.Password);
            connection.Open();
            command.ExecuteNonQuery();
        }
        catch (SqlException ex)
        {
            // 关闭连接等处理
            connection.Close();
            // 包装异常
            throw new DataAccessException("数据库操作失败", ex);
        }
    }
}

2. 避免过度捕获

  • 只捕获已知的、能处理的异常类型,避免使用过于宽泛的catch块(如catch (Exception ex))。如果使用宽泛的catch块,确保在处理后重新抛出异常,以免掩盖问题。
try
{
    // 可能抛出多种异常的代码
}
catch (SpecificException1 ex1)
{
    // 处理SpecificException1
}
catch (SpecificException2 ex2)
{
    // 处理SpecificException2
}
catch (Exception ex)
{
    // 记录日志等通用处理
    Logger.Log("未处理的异常: " + ex.Message);
    throw;
}

3. 使用自定义异常

  • 定义特定领域的自定义异常类,以便更好地区分不同类型的异常,使异常处理逻辑更清晰。
public class BusinessRuleException : Exception
{
    public BusinessRuleException(string message) : base(message)
    {
    }
}

public class DataAccessException : Exception
{
    public DataAccessException(string message, Exception innerException) : base(message, innerException)
    {
    }
}

4. 性能优化

  • 减少异常开销:异常处理机制相对开销较大,应避免在正常流程中频繁抛出异常。例如,在进行文件读取前,先检查文件是否存在,而不是直接尝试读取并捕获FileNotFoundException
if (File.Exists(filePath))
{
    // 进行文件读取操作
}
else
{
    // 处理文件不存在情况
}
  • 异步异常处理:在异步操作中,使用await时,异常会被自动传播,确保在合适的异步方法层级捕获异常。
public async Task DoAsyncWork()
{
    try
    {
        await Task.Run(() =>
        {
            // 异步工作代码,可能抛出异常
        });
    }
    catch (Exception ex)
    {
        // 处理异步操作中的异常
    }
}

5. 日志记录

  • 在各层捕获异常时,记录详细的异常信息,包括异常类型、消息、堆栈跟踪等,以便于调试和问题排查。可以使用如log4netNLog等日志框架。
using NLog;

public class SomeService
{
    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

    public void SomeMethod()
    {
        try
        {
            // 业务代码
        }
        catch (Exception ex)
        {
            Logger.Error(ex, "发生异常");
            throw;
        }
    }
}

6. 异常传递与包装

  • 当异常需要在层间传递时,可选择包装异常,添加更多上下文信息。例如,数据访问层捕获数据库异常,包装后传递给业务逻辑层,业务逻辑层再根据情况决定是否进一步包装传递给表示层。
// 数据访问层
catch (SqlException ex)
{
    throw new DataAccessException("数据库操作失败", ex);
}

// 业务逻辑层
catch (DataAccessException ex)
{
    throw new BusinessException("业务处理因数据访问失败", ex);
}