Сокращение количества дублирующихся кодов обработки ошибок в C #?
-
08-06-2019 - |
Вопрос
Я никогда не был полностью доволен тем, как работает обработка исключений, есть много исключений, и try / catch выводит их в таблицу (разматывание стека и т.д.), Но, похоже, это сильно нарушает модель 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
} );
Мне это в какой-то степени нравится, но оставляет желать лучшего.Я хотел бы услышать, как другие люди решили бы подобную проблему.
Решение
Похоже, это отличная возможность взглянуть на Аспектно-ориентированное программирование.Вот хорошая статья о AOP в .NET.Общая идея заключается в том, что вы бы извлекли кросс-функциональную проблему (т.е.Повторите попытку в течение x часов) в отдельный класс, а затем вы бы аннотировали любые методы, которым необходимо изменить свое поведение таким образом.Вот как это могло бы выглядеть (с хорошим методом расширения в Int32)
[RetryFor( 10.Hours() )]
public void DeleteArchive()
{
//.. code to just delete the archive
}
Другие советы
Просто интересно, что, по вашему мнению, ваш метод оставляет желать лучшего?Вы могли бы заменить анонимный делегат на a..по имени?делегировать, что-то вроде
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" );
}
Вы также могли бы использовать более ОО-подход:
- Создайте базовый класс, который выполняет обработку ошибок и вызывает абстрактный метод для выполнения конкретной работы.(Шаблон метода pattern)
- Создайте конкретные классы для каждой операции.
Преимущество этого заключается в присвоении имен каждому типу выполняемой вами операции и дает вам шаблон команды - операции были представлены в виде объектов.
Вот что я недавно сделал.Вероятно, это было сделано в другом месте лучше, но оно кажется довольно чистым и пригодным для повторного использования.
У меня есть служебный метод, который выглядит следующим образом:
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 c #, чтобы поддерживать порядок:
Utility.DoAndRetry( () => ie.GoTo(url), 5);
Это вызывает мой метод и повторяет попытку до 5 раз.При пятой попытке исходное исключение повторно помещается внутрь исключения повторной попытки.