Я очень давно занимаюсь программированием на Java. Я узнал, что нужно для написания и поддержки большого (как в миллион строк кода) программного обеспечения на Java, и я стал свидетелем общеотраслевой борьбы за то, чтобы избежать и сдержать ужасный NullPointerException (NPE), который, казалось, изводил любого разумно- размер базы кода Java. Это осознание опасности ссылки null появилось в отрасли еще до того, как ее изобретатель Тони Хоар признал в 2009 году, что пустая ссылка была его ошибкой на миллиард долларов.

Это было не так очевидно еще в 1996 году, когда была выпущена Java 1.0. Давайте взглянем только на один, теперь уже известный, пример типичного Java API той эпохи: File.list() метод. Его можно использовать для вывода списка содержимого каталога следующим образом:

for (String name : new File("directory").list()) {
    System.out.println(name);
}

Работает, только если каталог существует. В противном случае он выдает NPE, потому что list возвращает null. Но кто бы мог писать такой код? Не только в list документации четко указано, что она возвращает null, когда каталог отсутствует, но современные IDE сразу же помечают этот конкретный код предупреждением.

Однако люди все время совершают подобные ошибки при программировании на Java. К настоящему времени проведено большое количество исследований, показывающих, как это происходит. Оказывается, что большую часть времени наши функции API не предполагают и не ожидают, что другие разработчики будут возвращать значение null. В крайних случаях, таких как отсутствие чего-либо, в Java существует соглашение о возврате некоторого нулевого объекта (пустая коллекция, незаполненный объект домена и т. Д.) Или, в качестве меньшего зла, чем возврат нулевого значения, генерировать исключение. Так устроена Files.newDirectoryStream - современная версия File.list - нигде нет нулей.

Поэтому, когда значение null действительно появляется в результате выполнения функции в каком-то особом случае, например, при оптимизации производительности, неинициализированном ссылочном поле и т. Д., Он часто застает другой код врасплох, не готовый справиться с ним. Не только редко приходится иметь дело с нулями, но и код, который вы должны написать для работы с нулями в Java, длинный и многословный:

String[] list = new File("directory").list();
if (list != null) {
    for (String name : list) {
        System.out.println(name);
    }
}

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

Боязнь нуля приводит к некоторым крайним практикам. Существуют стили кодирования Java, которые полностью запрещают null, что навязывает разработчикам гнусные обходные пути. Вы видели кодовую базу, где каждый объект домена реализует специальный Null интерфейс и должен предоставлять вручную закодированный экземпляр «нулевого объекта»? К счастью, если вы этого не сделали, но я уверен, что вы видели Optional<T> оболочки, которые загрязняют код Java только ради того, чтобы избежать нулей.

Существуют API-интерфейсы, подобные коллекциям, которые запрещают пустые элементы из-за особой осторожности, и есть члены основной группы Java, которые считают поддержку нулей в структуре коллекций Java ошибкой. Это очень печально.

На самом деле концепция null не является ошибкой, но система типов Java, которая считает, что null является членом каждого типа, ошибочна. Видите ли, "abc" является допустимым String в Java, а null также является допустимым String, но вы можете использовать все строковые методы, такие как substring, в первом случае, но попытки использовать их во втором универсально терпят неудачу во время выполнения. Это типобезопасно? Не совсем. Это нормально, когда некоторые операции завершаются сбоем во время выполнения для определенных значений типа (например, деление на ноль), но когда значение приводит к сбою во время выполнения всех операций, это показывает, что значение не должно принадлежать к этому типу в первую очередь. Все эти NPE в программах Java указывают на очевидный недостаток в системе типов Java.

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

Правильная поддержка null в системе типов меняет правила игры в дизайне API. Больше нет никаких причин бояться null. Наличие некоторых функций, которые возвращают тип, допускающий значение NULL, String? бок о бок с другими, которые возвращают ненулевое значение String, так же нормально, как наличие некоторых функций, которые возвращают String рядом с другими, возвращающими Int. Это просто разные типы с разными доступными для них безопасными наборами операций.

Типобезопасный null - лучшая, более эффективная и менее подробная альтернатива для представления всевозможных «недостающих результатов». Взгляните, например, на стандартную библиотеку Kotlin для вдохновения. Существует String.toIntOrNull() функция, которая анализирует строку до целого числа, если это возможно, или возвращает null, если нет. Приятно пользоваться. Написать приложение командной строки, которое принимает целочисленный параметр и должным образом жалуется на его отсутствие, легко:

fun main(args: Array<String>) {
    val id = args.getOrNull(0)?.toIntOrNull() 
        ?: error("id expected")
    // ...
}

Включите null в свой дизайн API. Null - ваш друг с Kotlin. Нет причин бояться этого и нет причин обходить это с помощью шаблона или оболочки «нулевой объект», не говоря уже об исключениях. Правильное использование нулей в ваших API приводит к более читаемому и безопасному коду, свободному от шаблонов.

дальнейшее чтение

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