Использование общего класса для выполнения с try / catch / finally? - PullRequest
5 голосов
/ 11 июня 2010

Я обнаружил, что в моем коде много разных методов:

try
{
  runABunchOfMethods();
}
catch (Exception ex)
{
  logger.Log(ex);
}

Как насчет создания этого:

public static class Executor
{

    private static ILogger logger;

    public delegate void ExecuteThis();

    static Executor()
    {
        // logger = ...GetLoggerFromIoC();
    }

    public static void Execute<T>(ExecuteThis executeThis)
        where T : Exception
    {
        try
        {
            executeThis();
        }
        catch (T ex)
        {
            // Some kind of Exception Handling Strategy...
            logger.Log(ex);
            // throw;
        }
    }

}

И просто так:

private void RunSomething()
{
  Method1(someClassVar);
  Method2(someOtherClassVar);
}

...

Executor.Execute<ApplicationException>(RunSomething);

Есть ли недостатки этого подхода? (Вы можете добавить Executor-методы и делегаты, когда вы хотите, наконец, и использовать обобщенные типы для типа Exeception, который вы хотите перехватить ...)

Редактировать: Извините за непонятность - я действительно хотел получить некоторую информацию об общей идее попытки переместить выполнение кода из рассматриваемого класса в более обобщенный класс, который делает это. Я только что сделал быстрый макет решения, но в реальной жизни вы, естественно, использовали бы такие вещи, как стратегии обработки исключений, абстрактные базовые классы выполнения с более специализированными классами выполнения для определенного слоя / части системы. Обычно я создаю один метод с помощью try ... / runABunchOfMethods-part (это выполняет обработку исключений со специализированными исключениями), который вызывает runABunchOfMethods, которые, в свою очередь, выполняют ограниченный набор других методов в стиле «чистого кода».

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

Я отредактировал Executor для включения общего T, чтобы позволить вызывающему коду указывать исключение только для того, чтобы показать, как обрабатывать специализированное исключение. В других случаях у вас может быть несколько уловок: в зависимости от того, что вы хотите сделать, но это особые случаи в конкретных подклассах, о которых я говорил.

Ответы [ 9 ]

12 голосов
/ 11 июня 2010

Недостатком является ваш подход к общей обработке исключений. Вы не должны ловить базовый класс Exception без веской причины. Это может скрыть различные проблемы; хорошо, вы регистрируете их, но ваш код не знает, что ему просто не хватило нехватки памяти , или файл не существует и продолжается, как будто не было никаких проблем
Метод, который вы описываете здесь, будет стимулировать общую обработку исключений.

Связанный:
Неужели так сложно поймать общее исключение?
Это плохая практика для отлова неспецифического исключения, такого как System.Exception? Почему?

РЕДАКТИРОВАТЬ (ответ на редактирование и уточнение ОП):
Я не уверен, чего ты хочешь достичь. В основном вы скрываете исключение (общее или указанное - оно просто проглочено). Назовите свой метод Executor.Hide<ApplicationException>( RunSomething );, и станет ясно, что здесь происходит. Но есть ли преимущество в проглатывании исключений? Я так не думаю. Опять же, могут быть места, где это нужно, но они редки и должны выбираться намеренно. Предоставленный вами метод побуждает глотать исключения, не задумываясь об этом.
Вы закомментировали строку переброса (throw ex или лучше просто throw, которая сохраняет стек). Что вы получаете с этой линией? В основном просто логирование. Вы ловите исключение, регистрируете его и перебрасываете его, чтобы ... поймать снова? Почему вы не можете поставить свои записи в этом последнем месте?
Попробуйте поймать исключение только тогда, когда вы сможете его обработать. Там вы тоже можете войти. Любой хороший регистратор сможет показать вам трассировку стека, чтобы у вас не было потери информации.

Связанный:
Где поставить попробовать поймать?

3 голосов
/ 11 июня 2010

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

2 голосов
/ 11 июня 2010

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

РЕДАКТИРОВАТЬ:

Можно удалить блоки try / catch, используя postsharp - здесьПример общего обработчика исключений, который может быть создан в PostSharp:

[Serializable]
public class CommonExceptionHandling : OnExceptionAspect
{
    public override void OnException(MethodExecutionEventArgs eventArgs)
    {
        // can do some logging here
        // ...

        // throw the exception (out of postsharp) to where it occurred:
        eventArgs.FlowBehavior = FlowBehavior.RethrowException;                       

        // If you want to ignore the exception, you can do this:
        //eventArgs.FlowBehavior = FlowBehavior.Return;

        base.OnException(eventArgs);
    }
}

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

2 голосов
/ 11 июня 2010

Вы на самом деле ничего не выигрываете, но вы теряете некоторые вещи:

  • читаемость
  • Простота (вы вводите другой класс, другого делегата, которого должен понять читатель)
  • Вы не можете просто перешагнуть Executor.Execute, чтобы добраться до Method1 (...). Если вы перешагнете, пропустите весь блок кода в вашем делегате.
  • ...
1 голос
/ 11 июня 2010

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

  • В консольном приложении вы должны ловить и регистрировать исключения в Main.
  • В приложении WinForms вы должны перехватывать и регистрировать исключения в ваших обработчиках событий.
  • В службе вы должны перехватывать и регистрировать исключения, а затем ThreadProcs ваших рабочих потоков.
  • Etc.

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

Такая структура вашего приложения приведет лишь к нескольким местам, где вы будете ловить, регистрировать и «проглатывать» исключения.

1 голос
/ 11 июня 2010

Я прыгну на «это заставляет вас злоупотреблять исключениями, только ловя на общем уровне». Однако в более специализированных случаях вам может быть полезна какая-то «оболочка делегата для обработки исключений». Например, если вы много работали с классами, связанными с БД, которые всегда выдают одни и те же исключения, у вас может быть выделенная обертка для исключений БД.

В конечном счете, тот факт, что исключения «загромождают» ваш код, является обратной стороной для обеспечения безопасности вашего кода.

1 голос
/ 11 июня 2010

Если вы видите этот код во многих классах, то вы делаете что-то очень-очень неправильное в моей книге :-) При как минимум вы должны повторно сгенерировать исключение (просто " throw ", а не" throw (ex) "), но сама идея перехвата базового исключения все время только для его регистрации (или даже в любое время, за исключением нескольких крайних случаев) не является правильный подход для меня.

Я понятия не имею, какое приложение вы пишете, но не можете ли вы перехватить такие события, как AppDomain.UnhandledException , который срабатывает всякий раз, когда есть необработанное исключение (которое вы не перехватили) в какую нить, а логи те? Возможно, вы захотите объединить это с более конкретными операторами catch, которые также записывают в журнал, если вы хотите также регистрировать восстанавливаемые исключения.

1 голос
/ 11 июня 2010

Недостатком было бы отсутствие гибкости. Что делать, если вы хотите восстановиться после определенного исключения в одном из вызываемых методов - например, при неудачном соединении с SMTP-сервером вы можете сохранить содержимое исходящей почты в вашей базе данных для повторной попытки позже? По этой причине вам следует избегать перехвата общего типа исключения, когда это возможно.

Кроме того, вы теряете некоторую ясность своего кода (на мой взгляд, в любом случае), так как трудно понять, где происходит обработка исключений.

0 голосов
/ 11 июня 2010

В самом начале ....

В вашей базе кода НЕ должно быть кода, подобного приведенному ниже.

try 
{ 
  runABunchOfMethods(); 
} 
catch (Exception ex) 
{ 
  logger.Log(ex); 
} 

Я бы посоветовал вам сначала взглянуть на фактическую необходимость кода, подобного этому. Вы должны ловить очень специфическое исключение во всех случаях (в отличие от универсального catch all).

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

...