Сокращение дублирования кода обработки ошибок в С#?
Я никогда не был полностью доволен тем, как работает обработка исключений, есть много исключений и попытка/улов приносит в таблицу (разворачивание стека и т.д.), но, похоже, ломает много модели OO в процесс.
В любом случае, здесь проблема:
Скажем, у вас есть класс, который обертывает или включает операции ввода-вывода в сетевом файле (например, чтение и запись в какой-либо файл по определенному пути 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
Это выглядит как отличная возможность взглянуть на перспективное программирование. Вот хорошая статья о AOP в .NET. Общая идея состоит в том, что вы должны извлечь кросс-функциональную проблему (например, "Повторить за x часов" ) в отдельный класс, а затем вы должны аннотировать любые методы, которые должны изменить их поведение таким образом. Вот как это могло бы выглядеть (с хорошим методом расширения на Int32)
[RetryFor( 10.Hours() )]
public void DeleteArchive()
{
//.. code to just delete the archive
}
Ответ 2
Просто интересно, что вы чувствуете, что ваш метод оставляет желать лучшего? Вы можете заменить анонимного делегата на.. 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" );
}
Ответ 3
Вы также можете использовать более OO-подход:
- Создайте базовый класс, который выполняет обработку ошибок и вызывает абстрактный метод для выполнения конкретной работы. (Шаблон метода шаблона)
- Создайте конкретные классы для каждой операции.
Это имеет преимущество, заключающееся в том, чтобы называть каждый тип выполняемой вами операции и дает вам шаблон команды - операции были представлены как объекты.
Ответ 4
Вот что я сделал недавно. Вероятно, это было сделано в другом месте лучше, но оно кажется довольно чистым и многоразовым.
У меня есть метод утилиты, который выглядит так:
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 раз. В пятой попытке исходное исключение повторяется внутри исключения повтора.