Я слышал, что рекомендуется проверять аргументы открытых методов:
- Стоит ли проверять на ноль, если он не ожидает ноль?
- Должен ли метод проверять свои параметры?
- MSDN - CA1062: проверка аргументов общедоступных методов (у меня есть .NET фон, но вопрос не специфичен для C #)
Мотивация понятна. Если модуль будет использоваться неправильно, мы хотим немедленно вызвать исключение, а не непредсказуемое поведение.
Что беспокоит меня, так это то, что неправильные аргументы - не единственная ошибка, которая может быть допущена при использовании модуля. Вот несколько сценариев ошибок, в которых нам нужно добавить логику проверки, если мы следуем рекомендациям и не хотим эскалации ошибок:
- Входящий звонок - неожиданные аргументы
- Входящий звонок - модуль находится в неверном состоянии
- Внешний вызов - неожиданные результаты возвращены
- Внешний вызов - неожиданные побочные эффекты (двойной вход в вызывающий модуль, нарушение других состояний зависимостей)
Я попытался учесть все эти условия и написать простой модуль с одним методом (извините, не-C # ребята):
public sealed class Room
{
private readonly IDoorFactory _doorFactory;
private bool _entered;
private IDoor _door;
public Room(IDoorFactory doorFactory)
{
if (doorFactory == null)
throw new ArgumentNullException("doorFactory");
_doorFactory = doorFactory;
}
public void Open()
{
if (_door != null)
throw new InvalidOperationException("Room is already opened");
if (_entered)
throw new InvalidOperationException("Double entry is not allowed");
_entered = true;
_door = _doorFactory.Create();
if (_door == null)
throw new IncompatibleDependencyException("doorFactory");
_door.Open();
_entered = false;
}
}
Теперь безопасно =)
Это довольно жутко. Но представьте, насколько это может быть жутко в реальном модуле с десятками методов, сложным состоянием и множеством внешних вызовов (привет, любители внедрения зависимостей!). Обратите внимание, что если вы вызываете модуль, поведение которого может быть переопределено (незакрытый класс в C #), то вы делаете внешний вызов, и последствия не будут предсказуемы в области действия вызывающей стороны.
Подводя итог, что это правильный путь и почему? Если вы можете выбрать один из вариантов ниже, пожалуйста, ответьте на дополнительные вопросы.
Проверьте использование всего модуля. Нужны ли нам юнит-тесты? Есть ли примеры такого кода? Следует ли ограничивать использование зависимостей в использовании (так как это приведет к большей логике проверки)? Разве не практично переносить эти проверки во время отладки (не включать в выпуск)?
Проверьте только аргументы. Исходя из моего опыта, проверка аргументов - особенно проверка на ноль - является наименее эффективной проверкой, потому что ошибка аргумента редко приводит к сложным ошибкам и эскалации ошибок. Большую часть времени вы получите NullReferenceException
на следующей линии. Так почему проверки аргументов такие особые?
Не проверяйте использование модуля. Это довольно непопулярное мнение, вы можете объяснить, почему?
Ответы:
TL; DR: проверить изменение состояния, полагаться на [действительность] текущего состояния.
Ниже я рассматриваю только проверки с поддержкой релизов. Активные утверждения только для отладки являются формой документации, которая по-своему полезна и выходит за рамки данного вопроса.
Рассмотрим следующие принципы:
Определения
Изменчивое состояние
проблема
На императивных языках симптом ошибки и ее причина могут быть разделены часами тяжелой работы. Государственная коррупция может спрятаться и мутировать, что приведет к необъяснимым сбоям, поскольку проверка текущего состояния не может выявить полный процесс коррупции и, следовательно, возникновение ошибки.
Решение
Каждое изменение состояния должно быть тщательно продумано и проверено. Один из способов справиться с изменчивым состоянием - это свести его к минимуму. Это достигается путем:
Расширяя состояние компонента, рассмотрите возможность сделать так, чтобы компилятор включал неизменность новых данных. Кроме того, применяйте все разумные ограничения времени выполнения, ограничивая потенциальные результирующие состояния минимально возможным четко определенным набором.
пример
Повторение и сплоченность ответственности
проблема
Проверка предварительных условий и постусловий операций приводит к дублированию кода проверки как на клиенте, так и на компоненте. Проверка вызова компонента часто вынуждает клиента взять на себя некоторые обязанности компонента.
Решение
Положитесь на компонент для проверки состояния, когда это возможно. Компоненты должны предоставлять API, который не требует специальной проверки использования (например, проверка аргументов или принудительное выполнение последовательности операций), чтобы состояние компонента было четко определено. Они обязуются проверять аргументы вызова API по мере необходимости, сообщать о сбоях с помощью необходимых средств и стремиться предотвратить повреждение своего состояния.
Клиенты должны полагаться на компоненты для проверки использования их API. Избегается не только повторение, клиент больше не зависит от дополнительных деталей реализации компонента. Считайте фреймворк компонентом. Пишите собственный код проверки только в том случае, если инварианты компонента недостаточно строги или для инкапсуляции исключения компонента в качестве детали реализации.
Если операция не изменяет состояние и не покрывается проверками изменения состояния, проверьте каждый аргумент на самом глубоком возможном уровне.
пример
Ответ
Когда описанные принципы применяются к рассматриваемому примеру, мы получаем:
Резюме
Состояние клиента состоит из значений собственных полей и частей состояния компонента, которые не покрыты его собственными инвариантами. Проверка должна проводиться только до фактического изменения состояния клиента.
источник
Класс отвечает за свое состояние. Так что проверяйте в той степени, в которой он сохраняет или переводит вещи в приемлемое состояние.
Нет, не бросайте исключение, а поставляйте предсказуемое поведение. Следствием государственной ответственности является сделать класс / приложение настолько терпимым, насколько это практически возможно. Например, переходя
null
наaCollection.Add()
? Только не добавляй и продолжай. Вы получаетеnull
вход для создания объекта? Создайте нулевой объект или объект по умолчанию. Вышеdoor
ужеopen
? И что, продолжай.DoorFactory
аргумент нулевой? Создать новый. Когда я создаю, уenum
меня всегда естьUndefined
член. Я свободно используюDictionary
s иenums
определяю вещи явно; и это имеет большое значение для обеспечения предсказуемого поведения.Да, хотя я иду через тень долины параметров, я не буду бояться аргументов. К предыдущему я также максимально использую параметры по умолчанию и дополнительные параметры.
Все вышеперечисленное позволяет продолжать внутреннюю обработку. В конкретном приложении у меня есть десятки методов в нескольких классах только в одном месте, где выбрасывается исключение. Даже тогда, это не из-за нулевых аргументов или из-за того, что я не смог продолжить обработку, это потому, что код в итоге создал «нефункциональный» / «нулевой» объект.
редактировать
цитируя мой комментарий полностью. Я думаю, что дизайн не должен просто «сдаваться» при встрече с «нулем». Особенно с использованием составного объекта.
конец редактирования
источник
encapsulation
&single responsibility
.null
После первого уровня, взаимодействующего с клиентом, проверки практически отсутствуют . Код <strike> терпимый </ strike> надежный. Классы спроектированы с состояниями по умолчанию и поэтому работают без написания, как если бы во взаимодействующем коде было много ошибок, мошенник. Составной родительский элемент не должен обращаться к дочерним слоям для оценки достоверности (и, как следствие, проверятьnull
все закоулки). Родитель знает, что означает состояние ребенка по умолчанию