Когда мне следует обрабатывать исключения, а когда их бросать?

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

Возьмем следующий пример:

    public void AddComponent(IEntityComponent component)
    {
        if (component == null)
        {
            // Should I throw an ArgumentNullException or should I just return?
        }

        if (ContainsComponentOfType(component.GetType()))
        {
            // Should I return here? Or should I throw an ArgumentException?
        }

        // Finally, we know we can add the component to the entity
        components.Add(component);
    }

    public bool ContainsComponentOfType(Type componentType)
    {
        if (componentType == null)
        {
            // Should I throw an exception here? Should I return false?
        }

        return components.Any(c => c.GetType() == componentType);
    }

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

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

René Vogt 10.09.2018 08:19

Это кажется скорее основанным на мнении. С точки зрения мой как разработчика: если я использую библиотеку, я хочу получать обратную связь о том, что произошло / происходит. Если я вызову AddComponent с нулевой ссылкой и не увижу никакой разницы в вызове его с правильным значением, я буду сбит с толку. Обычно бросок, когда что-то не так, как должно быть, - это хорошая вещь. Если вы хотите избежать этого, возможно, у вас может быть bool TryAddComponent, который возвращает false вместо того, чтобы бросать ... но опять же, мнения по этому поводу, вероятно, разойдутся.

Corak 10.09.2018 08:20

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

Enigmativity 10.09.2018 08:29

Между прочим: components.Any(c => c.GetType() == componentType) - это O (n). Я не знаю, для чего еще вы используете components, но если порядок элементов не имеет значения, вы можете иметь его как Dictionary<Type, IEntityComponent>. Проверка типа будет components.ContainsKey(componentType), что в основном O (1).

Corak 10.09.2018 08:42

Было бы полезно, если бы вы могли описать Почему, с которым вам трудно решить. В чем конкретно заключаются плюсы и минусы, с которыми вы боретесь? Поскольку ваш вопрос стоит сейчас, я считаю, что он слишком общий / основанный на мнении. Но очень часто просто генерируют исключение, если клиент передает вам недопустимые аргументы. Методы, которые пытаются «поступать правильно» с недопустимыми аргументами, часто в конечном итоге приводят к ошибкам, которые очень трудно отследить.

Rufus L 10.09.2018 09:20
2
5
95
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

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

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

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

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

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

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

Подводить итоги:

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

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

Версия с нулевым допуском:

// Strict: we are quite sure in the arguments; 
// that's why it's an exceptional case (error in the code!) if arguments are invalid
// and we fail to add 
public void AddComponent(IEntityComponent component) {
  // Contract: here we validate the input arguments
  // Since we sure in them we apply zero tolerance policy:
  // if contract is't met throw corresponding exception

  // we want not null component  
  if (component == null)
    throw new ArgumentException(nameof(component));

  // which contains at least one item of the required type
  if (ContainsComponentOfType(component.GetType()))
    throw new ArgumentException("Component must contain...",  nameof(component)); 

  // Finally, we know we can add the component to the entity
  components.Add(component);
}

Мягкая реализация

// Lenient: we have arguments from the (unknown) source in which we are not sure
// we want just to try adding (and get true / false) if we succeed or not
public bool TryAddComponent(IEntityComponent component) {
  // Contract: we validate the input arguments from unknown source
  // if validation fails we should not throw any exception (it's not an
  // exceptional case to get incorrect data from unreliable source) but
  // let the caller know that we don't succeed

  // We can't add if component is null
  if (component == null)
    return false;

  // We have nothing to add if component doesn't contain required items 
  if (ContainsComponentOfType(component.GetType()))
    return false;

  // Finally, we know we can add the component to the entity
  components.Add(component);

  return true;
}

// Do we really want to expose it?
// ContainsComponentOfType is an implementation detail which we keep private
private bool ContainsComponentOfType(Type componentType) {
  // Since it's a private method we can omit the check
  return components.Any(c => c.GetType() == componentType);
}

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

MyClass loader = new MyClass();

...

// Cloud Database must not be corrupted; if it is, we want to know it immediatly
loader.AddComponent(componentFromCloud);

...

// Local file can contain any data: it can be corrupted, user can try cheating etc.
if (!loader.TryAddComponent(componentFromLocalFile)) {
  // Let user know that saved data failed to be loaded 
}

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

Oscar Guillamon 10.09.2018 09:19

Другие вопросы по теме