2008-08-04 6 views
32

Я никогда не был полностью доволен тем, как работает обработка исключений, есть много исключений и попытка/улов приносит в таблицу (разворачивание стека и т. Д.), Но, похоже, это ломает много модели OO в процессе ,Сокращение дублирования кода обработки ошибок в C#?

Во всяком случае, вот проблема:

Допустим, у вас есть некоторый класс, который оборачивает или включает в себя сетевые файловые операции ввода-вывода (чтение и запись например в какой-то файл на каком-то конкретном пути UNC где-то). По разным причинам вы не хотите, чтобы эти операции ввода-вывода терпели неудачу, поэтому, если вы обнаружите, что им не удается выполнить повторную попытку, и вы продолжаете повторять их до тех пор, пока они не добьются успеха или не достигнут тайм-аута. У меня уже есть удобный класс RetryTimer, который я могу создать и использовать для спячки текущего потока между повторами и определить, когда истек период ожидания и т. Д.

Проблема в том, что у вас есть куча операций ввода-вывода в нескольких методах этот класс, и вам нужно обернуть каждую из них в логике try-catch/retry.

Вот пример фрагмента кода:

RetryTimer fileIORetryTimer = new RetryTimer(TimeSpan.FromHours(10)); 
bool success = false; 
while (!success) 
{ 
    try 
    { 
     // do some file IO which may succeed or fail 
     success = true; 
    } 
    catch (IOException e) 
    { 
     if (fileIORetryTimer.HasExceededRetryTimeout) 
     { 
      throw e; 
     } 
     fileIORetryTimer.SleepUntilNextRetry(); 
    } 
} 

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

this.RetryFileIO(delegate() 
    { 
     // some code block 
    }); 

Мне нравится это несколько, но она оставляет желать лучшего. Я хотел бы услышать, как другие люди решат такую ​​проблему.

+1

Просто общий FYI: [почти * всегда * лучше] (http://philosopherdeveloper.wordpress.com/2010/05/05/re-throwing-caught-exceptions/) просто «бросить», вместо этого `throw e;` – 2010-09-13 03:34:47

ответ

13

Это похоже на прекрасную возможность взглянуть на перспективное программирование. Вот хорошая статья о AOP in .NET. Общая идея состоит в том, что вы должны извлечь кросс-функциональную проблему (например, «Повторить за x часов») в отдельный класс, а затем вы должны аннотировать любые методы, которые должны изменить их поведение таким образом. Вот как это может выглядеть (с хорошим методом расширения на Int32)

[RetryFor(10.Hours())] 
public void DeleteArchive() 
{ 
    //.. code to just delete the archive 
} 
4

Просто интересно, что вы чувствуете, что ваш метод оставляет желать лучшего? Вы можете заменить анонимного делегата на .. named? делегат, что-то вроде

public delegate void IoOperation(params string[] parameters); 

    public void FileDeleteOperation(params string[] fileName) 
    { 
     File.Delete(fileName[0]); 
    } 

    public void FileCopyOperation(params string[] fileNames) 
    { 
     File.Copy(fileNames[0], fileNames[1]); 
    } 

    public void RetryFileIO(IoOperation operation, params string[] parameters) 
    { 
     RetryTimer fileIORetryTimer = new RetryTimer(TimeSpan.FromHours(10)); 
     bool success = false; 
     while (!success) 
     { 
      try 
      { 
       operation(parameters); 
       success = true; 
      } 
      catch (IOException e) 
      { 
       if (fileIORetryTimer.HasExceededRetryTimeout) 
       { 
        throw; 
       } 
       fileIORetryTimer.SleepUntilNextRetry(); 
      } 
     } 
    } 

    public void Foo() 
    { 
     this.RetryFileIO(FileDeleteOperation, "L:\file.to.delete"); 
     this.RetryFileIO(FileCopyOperation, "L:\file.to.copy.source", "L:\file.to.copy.destination"); 
    } 
2

Вы также могли бы использовать более ОО подход:

  • Создать базовый класс, который делает обработку ошибок и вызывает абстрактный метод для выполнения конкретной работы. (Шаблон метода шаблона)
  • Создайте конкретные классы для каждой операции.

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

2

Вот что я сделал недавно. Вероятно, это было сделано в другом месте лучше, но кажется довольно чистым и многоразовым.

У меня есть служебный метод, который выглядит следующим образом:

public delegate void WorkMethod(); 

    static public void DoAndRetry(WorkMethod wm, int maxRetries) 
    { 
     int curRetries = 0; 
     do 
     { 
      try 
      { 
       wm.Invoke(); 
       return; 
      } 
      catch (Exception e) 
      { 
       curRetries++; 
       if (curRetries > maxRetries) 
       { 
        throw new Exception("Maximum retries reached", e); 
       } 
      } 
     } while (true); 
    } 

Тогда в моем приложении я использую синтаксис Lamda выражение С #, чтобы держать вещи аккуратными:

Utility.DoAndRetry(() => ie.GoTo(url), 5); 

Это вызывает мой метод и повторные попытки до 5 раз. В пятой попытке исходное исключение повторяется внутри исключения повтора.

+0

Но почему пользовательский делегат `WorkMethod` вместо` Action`? – 2010-09-13 03:32:26