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

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

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

    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
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
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

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