Как помогает выброс ArgumentNullException?

27

Допустим, у меня есть метод:

public void DoSomething(ISomeInterface someObject)
{
    if(someObject == null) throw new ArgumentNullException("someObject");
    someObject.DoThisOrThat();
}

Меня научили считать, что выбрасывание ArgumentNullExceptionявляется «правильным», но ошибка «Ссылка на объект не установлена ​​на экземпляр объекта» означает, что у меня есть ошибка.

Зачем?

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

Редактировать :

Это просто пришло мне в голову ... исходит ли страх перед разыменованным нулем от языка, подобного C ++, который не проверяет вас (то есть он просто пытается выполнить какой-то метод в ячейке памяти + смещение метода)?

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

Ответы:

34

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

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

Мэтт Н
источник
Если говорить о расширении, то если это был не публичный метод или публичный метод в непубличном классе, вы не думаете, что есть веская причина для нулевой проверки?
Скотт Уитлок
Обычно я не добавляю эти проверки к закрытым методам, но все же могу добавить их к открытым методам закрытых классов, чтобы они были согласованными. Что касается частных методов, я склонен делать предположение, что аргументы проверяются ранее на некотором уровне публичных вызовов.
Мэтт Х
54

Меня научили верить, что выбрасывать ArgumentNullException «правильно», но ошибка «Ссылка на объект не установлена ​​на экземпляр объекта» означает, что у меня ошибка. Зачем?

Предположим, я вызываю метод, M(x)который вы написали. Я передаю ноль. Я получаю ArgumentNullException с именем, установленным на «х». Это исключение однозначно означает, что у меня есть ошибка; Я не должен был передать ноль для х.

Предположим, я вызываю метод M, который вы написали. Я передаю ноль. Я получаю исключение nure deref. Как, черт возьми, я должен знать, есть ли у меня ошибка, или у вас есть ошибка, или в какой-то сторонней библиотеке, которую вы вызывали от моего имени, есть ошибка? Я ничего не знаю о том, нужно ли мне исправить свой код или позвонить вам, чтобы сказать, как исправить ваш код.

Оба исключения указывают на ошибки; ни один никогда не должен быть брошен в производственный код. Вопрос в том, какое исключение сообщает, у кого ошибка лучше человеку, выполняющему отладку? Исключение Null deref почти ничего вам не говорит; Аргумент null исключение говорит вам многое. Будьте добры к своим пользователям; бросать исключения, которые позволяют им учиться на своих ошибках.

Эрик Липперт
источник
Я не уверен, что понимаю, "neither should ever be thrown in production code"какие другие типы кода / ситуаций они будут использовать? Должны ли они быть составлены как Debug.Assert? Или ты имеешь в виду не выбрасывать их из API?
StuperUser
6
@StuperUser: Я думаю, вы не поняли, что я имел в виду, потому что я написал это смущающим образом. Вы должны иметь throw new ArgumentNullExceptionв своем рабочем коде, и он никогда не должен запускаться вне тестового случая . Исключение на самом деле никогда не должно вызываться, потому что вызывающая сторона никогда не должна иметь эту ошибку.
Эрик Липперт
1
Он не только сообщает, кто имеет ошибку, но и сообщает, где находится ошибка. Даже если обе области мои, не всегда легко понять, какая именно переменная была нулевой.
Охад Шнайдер
5

Дело в том, что ваш код должен делать что-то значимое.

А NullReferenceExceptionне имеет особого значения, потому что это может значить обо всем. Не смотря на то, где было сгенерировано исключение (и код может быть недоступен для меня), я не могу понять, что пошло не так.

Значение ArgumentNullExceptionимеет значение, потому что оно говорит мне: операция завершилась неудачно, потому что аргумент someObjectбыл null. Мне не нужно смотреть на ваш код, чтобы понять это.

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

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

back2dos
источник
2

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

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

Ян Худек
источник
1

Меня научили верить, что выбрасывать ArgumentNullException «правильно», но ошибка «Ссылка на объект не установлена ​​на экземпляр объекта» означает, что у меня ошибка.

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

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

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

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

P.Brian.Mackey
источник
1

Проверка делает более ясным, что происходит, но настоящая проблема заключается в коде, подобном этому (надуманному) примеру:

public void DoSomething(Foo someOtherObject, ISomeInterface someObject)
{
    someOtherObject.DoThisOrThat(this, someObject);
}

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

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

FinnNk
источник
1

Другой причиной для рассмотрения является то, что если некоторая обработка происходит перед использованием нулевого объекта?

Скажем, у вас есть файл данных связанных идентификаторов компании (Cid) и идентификаторов людей (Pid). Он сохраняется в виде потока числовых пар:

Сид Сид Сид Сид Сид Сид Сид Сид

И эта функция VB записывает записи в файл:

Sub WriteRecord(Company As CompanyInfo, Person As PersonInfo)
    Using File As New StringWriter(DataFileName)
        File.Write(Company.ID)
        File.Write(Person.ID)
    End Using
End Sub

Если Personэто нулевая ссылка, строка File.Write(Person.ID)выдаст исключение. Программное обеспечение может поймать исключение и восстановить, но это оставляет проблему. Идентификатор компании был написан без идентификатора связанного лица. На самом деле, когда вы в следующий раз вызовете эту функцию, вы поставите идентификатор компании там, где ожидался предыдущий идентификатор. Кроме того, что эта запись неверна, каждая последующая запись будет иметь идентификатор человека, за которым следует идентификатор компании, что является неправильным способом.

Сид Сид Сид Сид Сид Сид Сид Сид Сид

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

Hand-E-Food
источник