При программировании мне трудно думать о том, как моя программа должна работать, когда может быть сгенерировано исключение.
Возьмем следующий пример:
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);
}
Обратите внимание, что приведенный выше код будет использоваться людьми, создающими игру на моем движке.
Это кажется скорее основанным на мнении. С точки зрения мой как разработчика: если я использую библиотеку, я хочу получать обратную связь о том, что произошло / происходит. Если я вызову AddComponent
с нулевой ссылкой и не увижу никакой разницы в вызове его с правильным значением, я буду сбит с толку. Обычно бросок, когда что-то не так, как должно быть, - это хорошая вещь. Если вы хотите избежать этого, возможно, у вас может быть bool TryAddComponent
, который возвращает false вместо того, чтобы бросать ... но опять же, мнения по этому поводу, вероятно, разойдутся.
Вы должны генерировать исключение только тогда, когда ваш код не может продолжаться осмысленно. Это ответ «подними руки вверх». Это как если бы вас попросили разделить на ноль или у вас закончилось место на жестком диске. Если кто-то пытается добавить null
, вероятно, это исключение. Если кто-то спросит, есть ли в коллекции null
, то это просто false
. Вы решаете это, но будьте последовательны. Просто помните, что исключения стоят дорого, поэтому по возможности избегайте затрат.
Между прочим: components.Any(c => c.GetType() == componentType)
- это O (n). Я не знаю, для чего еще вы используете components
, но если порядок элементов не имеет значения, вы можете иметь его как Dictionary<Type, IEntityComponent>
. Проверка типа будет components.ContainsKey(componentType)
, что в основном O (1).
Было бы полезно, если бы вы могли описать Почему, с которым вам трудно решить. В чем конкретно заключаются плюсы и минусы, с которыми вы боретесь? Поскольку ваш вопрос стоит сейчас, я считаю, что он слишком общий / основанный на мнении. Но очень часто просто генерируют исключение, если клиент передает вам недопустимые аргументы. Методы, которые пытаются «поступать правильно» с недопустимыми аргументами, часто в конечном итоге приводят к ошибкам, которые очень трудно отследить.
Это зависит от того, что вы разрабатываете и как это следует использовать и кто этим пользуется ...
В вашем случае, когда разрабатывается игровой движок, лучшая практика - это выбросить исключения и передать тему конечному пользователю (разработчику, который использует ваш движок для разработки игры) и позволить разработчику обрабатывать исключения и выполнять надлежащую работу, которую он / она хочет.
Всегда старайтесь проверить все возможные исключения и предоставить полезную информацию об исключении и способах исправления ошибки, вызывающей исключение. общая обработка исключений предназначена для неизвестных и нежелательных исключений, о которых вы ничего не знаете.
Это зависит от того, что является для вас ошибкой и что такое исключение, просто потому, что ваш код обнаруживает, что что-то не так, как должно, и не может продолжаться, вы должны выбросить исключение.
Исключение, как следует из названия, происходит, когда происходит что-то исключительное и не контролируемое или не управляемое вашим кодом (например, обрывается сетевое соединение, не удалось сохранить файл, потому что он заблокирован другой программой, пустая ссылка, которую вы не контролировали, и т. д. ).
Создание исключений обычно обходится компьютеру дорого, так как он должен включать стек того, что произошло, где и информацию о состоянии стека вызовов, только из-за этого вы хотите, насколько это возможно, избегать исключений.
Поскольку ожидается, что ваш код может быть не в состоянии выполняться от начала до конца в наборе контролируемых условий, которые вы принимаете во внимание, рекомендуется контролировать поток и возвращаемые значения, которые указывают на то, что процесс не выполняется, как он ожидалось, но в этом случае произошла некоторая ожидаемая ошибка, которую вы контролировали, и немедленно сообщите об этой ошибке.
Подводить итоги:
Политика может отличаться от нулевой терпимости (выброс исключений в любой момент, где они могут быть выброшены) до снисходительного (где мы прощаем вызывающего, насколько это возможно).
Обычная практика - 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
}
Это довольно хороший ответ с примером того, как добиться хорошего контроля исключений, но не учитывает, когда обрабатывать, а когда бросать, не могли бы вы расширить свой ответ по этой теме? Я считаю, что вы можете дать очень твердый ответ
В данном случае я думаю, что вам стоит бросить. Потребители вашего API наверняка совершили ошибку, передав здесь
null
, и, просто вернувшись, вы скроете от них эту ошибку. Но в настоящее время вопрос действительно основан на мнении, что делает его вне темы на StackOverflow.