如果有这么多事情可能会出错,你所做的就是试试,试试吧

时间:2010-06-30 10:55:39

标签: c# exception-handling

说真的,你如何处理所有这些例外而不会疯狂?我是否读过太多关于异常处理的文章或者什么?我尝试重构这几次,每次我似乎最终都会遇到更糟糕的事情。也许我应该承认异常确实发生,只是喜欢编码快乐路径? ;)那么这段代码有什么问题(除了我懒得扔掉Exception而不是更具体的东西)?无论如何,不​​要轻易对我说。

public void Export(Database dstDb)
{
    try
    {
        using (DbConnection connection = dstDb.CreateConnection())
        {
            connection.Open();
            DbTransaction transaction = connection.BeginTransaction();
            try
            {
                // Export all data here (insert into dstDb)
                transaction.Commit();
            }
            catch (SqlException sqlex)
            {
                ExceptionHelper.LogException(sqlex);
                try
                {
                    transaction.Rollback();
                }
                catch (Exception rollbackEx)
                {
                    logger.Error("An exception of type " + rollbackEx.GetType() +
                                      " was encountered while attempting to roll back the transaction.");
                }
                throw new Exception("Error exporting message " + Type + " #" + Id + ": [" + sqlex.GetType() + "] " + sqlex.Message, sqlex);
            }
            catch (Exception ex)
            {
                try
                {
                    transaction.Rollback();
                }
                catch (Exception rollbackEx)
                {
                    logger.Error("An exception of type " + rollbackEx.GetType() +
                                      " was encountered while attempting to roll back the transaction.");
                }
                throw new Exception("Error exporting message " + Type + " #" + Id + ": [" + ex.GetType() + "] " + ex.Message, ex);
            }
        }

        try
        {
            Status = MessageStatus.FINISHED;
            srcDb.UpdateDataSet(drHeader.Table.DataSet, HEADERS,
                CreateHeaderInsertCommand(), CreateHeaderUpdateCommand(), null);
        }
        catch (Exception statusEx)
        {
            logger.ErrorException("Failed to change message status to FINISHED: " +
                                    Type + " #" + Id + ": " + statusEx.Message, statusEx);
        }
    }
    catch (Exception importEx)
    {
        try
        {
            Status = MessageStatus.ERROR;
            srcDb.UpdateDataSet(drHeader.Table.DataSet, HEADERS,
                    CreateHeaderInsertCommand(), CreateHeaderUpdateCommand(), null);
        }
        catch (Exception statusEx)
        {
            logger.ErrorException("Failed to change message status to ERROR: " +
                                    Type + " #" + Id + ": " + statusEx.Message, statusEx);
        }
        AddErrorDescription(importEx.Message);
        throw new Exception("Couldn't export message " + Type + " #" + Id + ", exception: " + importEx.Message, importEx);
    }
}

顺便说一下。很多次我在形成问题时努力做到尽可能具体 - 结果是没有访问,没有答案,也不知道如何解决问题。这一次,当别人的问题引起我的注意时,我一直在想,猜猜这是正确的做法:)

更新

我已经尝试将一些技巧付诸实践,这就是我到目前为止所提出的建议。我决定稍微改变一下这个行为:当成功导出后无法将消息状态设置为FINISHED时,我认为它没有完全完成,我回滚并抛出异常。如果你们还有一些耐心,请让我知道它是否更好。或者抛出对我的批评。顺便说一句。感谢所有答案,我分析了每一个答案。

抛出System.Exception的实例感觉不对,所以我按照建议摆脱了这一点,而是决定引入自定义异常。顺便说一句,这似乎也不对 - 矫枉过正?对于私有成员来说,这似乎没什么问题,但对于私有成员来说有点过度设计,但我仍然想知道更改消息状态而不是数据库连接问题或其他问题。

我可以在这里看到几种提取方法的方法,但所有这些方法似乎都混合了jgauffinhis comment提到的职责:管理数据库连接,处理数据库操作,业务逻辑(导出数据) 。说ChangeStatus方法 - 它是一定程度的抽象 - 你改变了一个消息的状态,你对这个事情是如何发生的,消息是如何保持不感兴趣的等等。也许我应该使用Data Mapper模式进一步分离责任,但在这个仍然非常简单的情况下,我以为我会逃避Active Record。也许现在整个设计都是如此复杂,以至于我看不到在哪里做削减?

public void Export(Database dstDb)
{
    try
    {
        using (DbConnection connection = dstDb.CreateConnection())
        {
            connection.Open();
            using (DbTransaction transaction = connection.BeginTransaction())
            {
                // Export all data here (insert into dstDb)
                ChangeStatus(MessageStatus.FINISHED);
                transaction.Commit();
            }
        }
    }
    catch (Exception exportEx)
    {
        try
        {
            ChangeStatus(MessageStatus.ERROR);
            AddErrorDescription(exportEx.Message);
        }
        catch (Exception statusEx)
        {
            throw new MessageException("Couldn't export message and set its status to ERROR: " +
                    exportExt.Message + "; " + statusEx.Message, Type, Id, statusEx);
        }
        throw new MessageException("Couldn't export message, exception: " + exportEx.Message, Type, Id, exportEx);
    }
}

private void ChangeStatus(MessageStatus status)
{
    try
    {
        Status = status;
        srcDb.UpdateDataSet(drHeader.Table.DataSet, HEADERS,
            CreateHeaderInsertCommand(), CreateHeaderUpdateCommand(), null);
    }
    catch (Exception statusEx)
    {
        throw new MessageException("Failed to change message status to " + status + ":" + statusEx.Message, statusEx);
    }
}

4 个答案:

答案 0 :(得分:14)

  1. 数据集是万恶之源;)请尝试使用ORM。
  2. 了解single responsibility principle。你的代码做了很多不同的事情。
  3. 不要抓住例外只是为了重新抛出它们
  4. 在事务和连接上使用使用语句。
  5. 当所有异常处理程序执行相同操作时,无需捕获所有不同的异常。异常详细信息(异常类型和消息属性)将提供信息。

答案 1 :(得分:9)

此外还有great answer of jgauffin

6。不要仅仅为了记录它们而捕获异常。抓住他们在最顶层并记录那里的所有例外。

修改:

因为遍布整个地方的异常记录至少具有以下缺点:

  1. 可能会多次记录同一个异常,这会不必要地填充日志。很难说实际发生了多少错误。
  2. 如果调用者捕获并处理或忽略了异常,则日志文件中仍然存在错误消息,这至少令人困惑。

答案 2 :(得分:4)

异常处理是一个很好的讨论,并以多种方式实现。在处理异常时,我尝试遵守一些规则:

  1. 永远不会抛出System.Exception,通常有足够的异常类型来填充您的需求,如果没有,则专门化。请参阅:http://www.fidelitydesign.net/?p=45
  2. 如果方法本身除了抛出异常之外不能执行任何操作,则只抛出异常。如果方法可以恢复/处理输入/行为的预期变化,那么不要抛出异常。抛出异常是一个资源密集型过程。
  3. 永远不要抓住异常只是为了重新抛出它。如果您需要执行一些额外的工作,例如报告异常,或将异常包装在另一个异常中(通常我为WCF工作或事务工作执行此操作),请捕获并重新抛出。
  4. 这些只是我个人所做的事情,许多开发人员更喜欢这样做的方式让他们感到舒服....

答案 3 :(得分:0)

创建一个日志类来处理自身失败的后备(即尝试SQL,如果失败写入事件日志,如果失败,写入本地日志文件等)。

此外,我不建议您捕获错误并抛出另一个错误。每次执行此操作都会丢失有关原始异常源的有价值的跟踪/调试信息。

public void Export(Database dstDb)
{
  try
  {
    using (DbConnection connection = dstDb.CreateConnection())
    {
        connection.Open();
        using (DbTransaction transaction = connection.BeginTransaction())
        {
            // Export all data here (insert into dstDb)
            ChangeStatus(MessageStatus.FINISHED);
            transaction.Commit();
        }
    }
  }
  catch (Exception exportEx)
  {
    LogError(exportEx);// create a log class for cross-cutting concerns 
        ChangeStatus(MessageStatus.ERROR);
        AddErrorDescription(exportEx.Message);

    throw; // throw preserves original call stack for debugging/logging
  }
}

private void ChangeStatus(MessageStatus status)
{
  try
  {
    Status = status;
    srcDb.UpdateDataSet(drHeader.Table.DataSet, HEADERS,
        CreateHeaderInsertCommand(), CreateHeaderUpdateCommand(), null);
  }
  catch (Exception statusEx)
  {
   Log.Error(statusEx);
   throw;
  }
}

对于您认为需要额外的try / catch块的任何情况,如果它们太难看,请将它们设置为自己的方法。我喜欢Stefan Steinegger的答案,你的应用程序中的顶级电话是最好的选择。

我想到的部分问题是,某些东西是可变的,会导致您尝试在失败后设置状态。如果您可以将对象分解为一致状态,无论您的交易是否有效,您都不必担心该部分。