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

182

Читая исходный код JDK, я обнаружил, что автор обычно проверяет параметры, если они равны NULL, а затем вручную генерирует новый NullPointerException (). Почему они это делают? Я думаю, что нет необходимости делать это, так как он вызовет новый NullPointerException () при вызове любого метода. (Вот некоторый исходный код HashMap, например :)

public V computeIfPresent(K key,
                          BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
    if (remappingFunction == null)
        throw new NullPointerException();
    Node<K,V> e; V oldValue;
    int hash = hash(key);
    if ((e = getNode(hash, key)) != null &&
        (oldValue = e.value) != null) {
        V v = remappingFunction.apply(key, oldValue);
        if (v != null) {
            e.value = v;
            afterNodeAccess(e);
            return v;
        }
        else
            removeNode(hash, key, null, false, true);
    }
    return null;
}
LiJiaming
источник
32
ключевым моментом кодирования является намерение
Страшный Вомбат
19
Это очень хороший вопрос для вашего первого вопроса! Я сделал несколько незначительных правок; Надеюсь, ты не против. Я также удалил благодарность и записку о том, что это ваш первый вопрос, так как обычно такие вещи не являются частью вопросов SO.
Дэвид Конрад,
11
У меня C #, соглашение заключается в том, чтобы поднимать ArgumentNullExceptionв подобных случаях (а не NullReferenceException) - на самом деле это действительно хороший вопрос о том, почему вы бы подняли NullPointerExceptionздесь явно (а не другой).
EJoshuaS - Восстановить Монику
21
@EJoshuaS Это старая дискуссия о том, чтобы бросить IllegalArgumentExceptionили NullPointerExceptionдля пустого аргумента. Конвенция JDK является последней.
шмосел
33
НАСТОЯЩАЯ проблема заключается в том, что они выдают ошибку и выбрасывают всю информацию, которая привела к этой ошибке . Кажется , это ЯВЛЯЕТСЯ фактический исходный код. Даже не простое кровавое строковое сообщение. Печальный.
Мартин Ба,

Ответы:

254

Есть несколько причин, которые приходят на ум, некоторые из них тесно связаны:

Отказоустойчивый: если он потерпит неудачу, лучше всего потерпеть неудачу раньше, чем позже. Это позволяет обнаруживать проблемы ближе к их источнику, облегчая их выявление и устранение. Это также позволяет избежать бесполезной траты циклов ЦП на код, который обязательно завершится с ошибкой.

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

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

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

shmosel
источник
14
Кроме того: таким образом местоположение, из которого выдается исключение, привязано ровно к одной проверяемой переменной. Без него исключение может быть связано с тем, что одна из нескольких переменных имеет значение null.
Йенс Шаудер
45
Еще один: если вы ждете, что NPE произойдет естественным образом, некоторый промежуточный код, возможно, уже изменил состояние вашей программы через побочные эффекты.
Томас,
6
Хотя этот фрагмент не делает этого, вы можете использовать new NullPointerException(message)конструктор, чтобы уточнить, что является пустым. Хорошо для людей, которые не имеют доступа к вашему исходному коду. Они даже сделали это однострочным в JDK 8 с помощью Objects.requireNonNull(object, message)служебного метода.
Робин
3
НЕИСПРАВНОСТЬ должна быть рядом с ОТКАЗОМ. «Fail Fast» - это больше, чем практическое правило. Когда вы не хотите такого поведения? Любое другое поведение означает, что вы прячете ошибки. Есть «НЕДОСТАТКИ» и «НЕДОСТАТКИ». Сбой, когда эта программа переваривает нулевой указатель и вылетает. Но эта строка кода не там, где лежит ошибка. NULL откуда-то пришел - аргумент метода. Кто прошел этот аргумент? Из некоторой строки кода, ссылающейся на локальную переменную. Где это ... Видишь? Это отстой. Чьей обязанностью было видеть, чтобы плохая ценность хранилась? Ваша программа должна была потерпеть крах.
Ноа
4
@ Томас Хороший вопрос. Шмосел: Точка Томаса может подразумеваться в точке быстрого отказа, но она как бы похоронена. Это достаточно важное понятие, которое имеет собственное имя: атомарность неудач . См. Bloch, Effective Java , Item 46. У него более сильная семантика, чем у fail-fast. Я бы предложил назвать это в отдельном пункте. Отличный ответ в целом, кстати. +1
Стюарт Маркс