Использование событий, а не исключений для реализации обработки ошибок - PullRequest
10 голосов
/ 29 сентября 2008

Я работаю над кодом, который использует шаблон в своем бизнес-уровне и уровне данных, который использует события для сигнализации об ошибках, например

resource = AllocateLotsOfMemory();
if (SomeCondition())
{    
    OnOddError(new OddErrorEventArgs(resource.StatusProperty));
    resource.FreeLotsOfMemory();    
    return;
}

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

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

Что имеет какой-то смысл.

Альтернативой может быть что-то вроде

resource = AllocateLotsOfMemory();
if (SomeCondition())
{   
    BigObject temporary = resource.StatusProperty;
    resource.FreeLotsOfMemory();
    throw new OddException(temporary);
}

Мои вопросы:

  1. Поскольку этот "BigObject" освобождается при освобождении объекта исключения, нужен ли нам этот шаблон?

  2. Кто-нибудь еще испытывал этот паттерн? Если да, то какие подводные камни вы нашли? Какие преимущества есть?

Спасибо!

Ответы [ 9 ]

8 голосов
/ 29 сентября 2008

Мне это тоже кажется странным. Есть несколько преимуществ - например, использование нескольких «обработчиков», но семантика значительно отличается от обычной обработки ошибок. В частности, меня беспокоит тот факт, что он не распространяется автоматически вверх по стеку - если только обработчики ошибок сами не сгенерируют исключение, логика будет продолжать работать так, как если бы все было в порядке, когда, вероятно, следует прерывать текущую операцию .

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

4 голосов
/ 29 сентября 2008

Это выглядит очень странно для меня, во-первых, IDisposable ваш друг, используйте его.

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

Так и должно быть

using(var resource = AllocateLotsOfMemory())
{
   if(something_bad_happened) 
   {
     throw new SomeThingBadException();
   }
}
4 голосов
/ 29 сентября 2008

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

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

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

Как дополнение, ничто не мешает поднять событие в дополнение к исключению, но снова: если у вас есть подлинная ошибка, вы хотите, чтобы что-то заставило разработчика клиента ее обработать. *

3 голосов
/ 29 сентября 2008

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

public class DomainEventStorage<ActionType>
{
    public List<ActionType> Actions
    {
        get
        {
            var k = string.Format("Domain.Event.DomainEvent.{0}.{1}",
                                  GetType().Name,
                                  GetType().GetGenericArguments()[0]);
            if (Local.Data[k] == null)
                Local.Data[k] = new List<ActionType>();

            return (List<ActionType>) Local.Data[k];
        }
    }

    public IDisposable Register(ActionType callback)
    {
        Actions.Add(callback);
        return new DomainEventRegistrationRemover(() => Actions.Remove(callback)
            );
    }
}

public class DomainEvent<T1> : IDomainEvent where T1 : class
{
    private readonly DomainEventStorage<Action<T1>> _impl = new DomainEventStorage<Action<T1>>();

    internal List<Action<T1>> Actions { get { return _impl.Actions; } }

    public IDisposable Register(Action<T1> callback)
    {
        return _impl.Register(callback);
    }

    public void Raise(T1 args)
    {
        foreach (var action in Actions)
        {
            action.Invoke(args);
        }
    }
}

И потреблять:

var fail = false;
using(var ev = DomainErrors.SomethingHappened.Register(c => fail = true) 
{
   //Do something with your domain here
}
3 голосов
/ 29 сентября 2008

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

Существует разногласие между лагерями по поводу возвращения кодов статуса и выдачи исключений. Чтобы упростить (значительно): Лагерь кода состояния говорит, что создание исключений приводит к обнаружению и обработке ошибки слишком далеко от кода, вызывающего ошибку. Ограничение на исключение говорит о том, что пользователи забывают проверять коды состояния, а исключения применяют обработку ошибок.

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

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

3 голосов
/ 29 сентября 2008

1) это нужно? никакой шаблон не является абсолютно необходимым

2) Windows Workflow Foundation делает это со всеми результатами из экземпляров Workflow, работающих в размещенной среде выполнения. Просто помните, что могут возникать исключения при попытке вызвать это событие, и вы можете захотеть выполнить свой код очистки в Dispose или блоке finally в зависимости от ситуации, чтобы обеспечить его выполнение.

1 голос
/ 17 ноября 2009

Первый фрагмент, вероятно, должен быть

resource = AllocateLotsOfMemory();
if (SomeCondition())
{
    try
    {
        OnOddError(new OddErrorEventArgs(resource.StatusProperty));
        return;
    }
    finally
    {
        resource.FreeLotsOfMemory();
    }
}

в противном случае вы не освободите свои ресурсы, когда обработчик событий сгенерирует исключение.

Как сказал Майк Браун, второй фрагмент также имеет проблему, если resource.FreeLotsOfMemory() портится с содержимым resource.StatusProperty вместо того, чтобы установить его на null.

0 голосов
/ 29 сентября 2008

Другая серьезная проблема с этим подходом - проблемы параллелизма.

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

0 голосов
/ 29 сентября 2008

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

Также есть существенная разница между вашими фрагментами.

если resource.FreeLotsOfMemory () очищает значение StatusProperty, а не просто устанавливает его в null, ваша временная переменная будет содержать недопустимый объект, когда создается и генерируется OddException.

Эмпирическое правило гласит, что исключения должны создаваться только в невосстановимых ситуациях. Мне бы очень хотелось, чтобы C # поддерживал предложение Throws, это единственное, что мне действительно не хватает в Java.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...