Когда так много вещей может пойти не так, как надо, попробуйте, попробуйте, попробуйте

Серьезно, как вы можете справиться со всеми этими исключениями, не обойдясь? Прочитал ли я слишком много статей об обработке исключений или о чем? Я пробовал рефакторинг это несколько раз, и каждый раз, когда я, кажется, получаю что-то еще хуже. Может быть, я должен признать, что исключения случаются и просто пользуются кодировкой только счастливого пути?;) Так что же не так с этим кодом (кроме того, что я был достаточно ленив просто бросить 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);
    }
}

Btw. Так много раз я очень старался быть настолько конкретным, насколько это возможно при формировании вопросов - в результате не было ни посещений, ни ответов, ни идеи, как решить проблему. На этот раз я думал обо всех случаях, когда кто-то еще интересовался моим вниманием, думаю, это было правильное дело:)

Update:

Я попытался применить некоторые советы, и вот что я до сих пор придумал. Я решил немного изменить поведение: когда невозможно установить статус сообщения FINISHED после успешного экспорта, я рассматриваю его как задание, которое не выполнено полностью, и я откатываюсь и выдаю исключение. Если вы, ребята, еще останетесь немного терпения, пожалуйста, сообщите мне, если это будет лучше. Или бросьте на меня еще одну критику. Btw. Спасибо за все ответы, я анализирую каждый из них.

Выбрасывание экземпляра System.Exception не понравилось, поэтому я избавился от этого, как было предложено, и вместо этого решил ввести специальное исключение. Это, кстати, тоже не кажется правильным - излишним? Это выглядит хорошо с общедоступными методами, но немного переработано для частного участника, но все же я хочу знать, что возникла проблема с изменением статуса сообщения, а не с соединением с базой данных или чем-то.

Я вижу пару способов извлечения методов здесь, но все они, похоже, смешивают обязанности, которые jgauffin упоминаются в его комментарий: управление подключением к базе данных, обработка операций с базами данных, бизнес-логика (экспорт данных). Скажем, метод 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);
    }
}

Ответ 1

  • Наборы данных являются корнем всего зла;) Вместо этого используйте ORM.
  • Читайте о принцип единой ответственности. ваш код делает много разных вещей.
  • Не перехватывайте исключения только для их реорганизации.
  • Используйте с помощью оператора транзакции и подключения.
  • Не нужно улавливать все разные исключения, когда все обработчики исключений выполняют одно и то же. Сведения об исключении (тип исключения и свойство сообщения) будут содержать информацию.

Ответ 2

В дополнение к отличному ответу jgauffin.

6. не перехватывайте исключения только для их регистрации. поймать их на самом верхнем уровне и зарегистрировать все исключения там.

Изменить:

Поскольку для регистрации исключений по всему месту есть, по крайней мере, эти недостатки:

  • Такое же исключение может регистрироваться несколько раз, что излишне заполняет журнал. Трудно сказать, сколько ошибок на самом деле произошло.
  • Если исключение поймано и обработано или проигнорировано вызывающим, в файле журнала все еще есть сообщения об ошибках, которые, по меньшей мере, запутывают.

Ответ 3

Обработка исключений хорошо обсуждается и реализуется в разнообразном количестве способов. Есть некоторые правила, которые я стараюсь соблюдать при обработке исключений:

  • Никогда не бросайте System.Exception, обычно существует достаточно типов исключений для заполнения вашего требования, если нет, специализации. См.: http://www.fidelitydesign.net/?p=45
  • Только когда-либо генерирует исключение, если сам метод не может ничего сделать, кроме исключения. Если метод может восстанавливать/обрабатывать ожидаемые изменения ввода/поведения, то не бросайте исключение. Отбрасывание исключений - это ресурсоемкий процесс.
  • Никогда не поймайте исключение, просто чтобы его восстановить. Поймать и перебросить, если вам нужно выполнить дополнительную работу, например, сообщить об исключении, или обернуть исключение в другом исключении (как правило, я делаю это для работы WCF или транзакционной работы).

Это всего лишь то, что я делаю лично, многие разработчики предпочитают делать так, как им удобно...

Ответ 4

Создайте класс журнала, который обрабатывает откаты для собственных сбоев (т.е. пытается 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, где лучший вызов в вашем приложении - лучшее место для улавливания.

Часть проблемы здесь я представляю, что что-то изменено, что заставляет вас попытаться установить статус после сбоя. Если вы можете подвергнуть свой объект постоянному состоянию независимо от того, работает ли ваша транзакция, вам не нужно беспокоиться об этой части.