Что такое null safety
Null безопасность
Nullable типы и Non-Null типы
Система типов в языке Kotlin нацелена на то, чтобы искоренить опасность обращения к null значениям, более известную как «Ошибка на миллиард».
Самым распространённым подводным камнем многих языков программирования, в том числе Java, является попытка произвести доступ к null значению. Это приводит к ошибке. В Java такая ошибка называется NullPointerException (сокр. «NPE»).
Kotlin призван исключить ошибки подобного рода из нашего кода. NPE могут возникать только в случае:
Система типов Kotlin различает ссылки на те, которые могут иметь значение null (nullable ссылки) и те, которые таковыми быть не могут (non-null ссылки). К примеру, переменная часто используемого типа String не может быть null:
Для того, чтобы разрешить null значение, мы можем объявить эту строковую переменную как String? :
Но нам по-прежнему надо получить доступ к этому свойству/значению, так? Есть несколько способов этого достичь.
Проверка на null
Первый способ. Вы можете явно проверить b на null значение и обработать два варианта по отдельности:
Компилятор отслеживает информацию о проведённой вами проверке и позволяет вызывать length внутри блока if. Также поддерживаются более сложные конструкции:
Обратите внимание: это работает только в том случае, если b является неизменной переменной (ориг.: immutable). Например, если это локальная переменная, значение которой не изменяется в период между его проверкой и использованием. Также такой переменной может служить val. В противном случае может так оказаться, что переменная b изменила своё значение на null после проверки.
Безопасные вызовы
Такие безопасные вызовы полезны в цепочках. К примеру, если Bob, Employee (работник), может быть прикреплён (или нет) к отделу Department, и у отдела может быть управляющий, другой Employee. Для того, чтобы обратиться к имени этого управляющего (если такой есть), напишем:
Такая цепочка вернёт null в случае, если одно из свойств имеет значение null.
Для проведения каких-либо операций исключительно над non-null значениями вы можете использовать let оператор вместе с оператором безопасного вызова:
Элвис-оператор
Если выражение, стоящее слева от Элвис-оператора, не является null, то элвис-оператор его вернёт. В противном случае, в качестве возвращаемого значения послужит то, что стоит справа. Обращаем ваше внимание на то, что часть кода, расположенная справа, выполняется ТОЛЬКО в случае, если слева получается null.
Так как throw и return тоже являются выражениями в Kotlin, их также можно использовать справа от Элвис-оператора. Это может быть крайне полезным для проверки аргументов функции:
Для любителей NPE существует ещё один способ. Мы можем написать b!! и это вернёт нам либо non-null значение b (в нашем примере вернётся String ), либо выкинет NPE:
В случае, если вам нужен NPE, вы можете заполучить её только путём явного указания.
Безопасные приведения типов (ориг.: Safe Casts)
Обычное приведение типа может вызвать ClassCastException в случае, если объект имеет другой тип. Можно использовать безопасное приведение, которое вернёт null, если попытка не удалась:
Коллекции nullable типов
Null safety of Kotlin. Мысль про киллер фичу
Как это проявлялось:
Не удобно работать с переменными и филдами, у которых не может быть null. Ну просто даже не понимал, как что-то не может быть null.
В общем шло время, потихоньку начинал привыкать к тому, что null может не существовать, даже пытался сделать что-то на мой тот взгляд идиоматичное: дефолтные значения в виде объекта. В общем все это меня вгоняло в тоску и я очень хотел опять писать на Java, так как привык жить с null. Прошло время я уже нормально начал жить с котлиновским not nullable, как в друг в один прекрасный день я вернулся в Java. И через время осознал одну вещь:
Когда я работаю с легаси кодом (ну это весь код, который вообще пишется), то я не понимаю, где может быть null, а где нет, что заставляет меня делать переключение контекста от реализации бизнес логики на выяснение: а может ли тут быть null?
Рассмотрим синтетику: У нас есть сущность номер телефона с филдами: номер, международный код города и префикс (ну +7, +3 и т.д.), написана она до нас, есть мэппинг в базу, в общем все по канонам кровавого. По бизнесу все 3 поля номера телефона обязаны быть.
Если я в Java, то при работе с этой сущностью у меня есть несколько вариантов как ее использовать:
Использовать как есть, не думая о том, что там с null-ом (продакшен разберется, если что баг пофиксим)
Дойти до базы данных и посмотреть на констрейнт, тем самым убедившись, что null там не может быть и можно спокойно юзать эту сущность без проверок.
Если расставлены аннотации @NotNull, нажать Ctrl+Q, чтобы увидеть описание.
Обработать все поля, предполагая, что везде может быть null.
Написать свой код, потом разобраться с потенциальным null.
Можно еще придумать варианты.
Все, кроме первого варианта, заставляют меня приложить усилия на переключение контекста и отвлечься от основной мысли над которой я работаю, ну да пункт 5 может сохранить мое внимание какое-то время, но потом придется потратить еще кучу времени на переключение контекста. Первый в принципе неплох, но потом будет весело разбираться с NPE.
Какие у нас есть варианты в Котлин:
Значение филда может быть null и я могу сразу обработать такой кейс.
Значение не может быть null.
То есть оба варианта не сбивают меня, не заставляют меня переключить свое внимание.
Ну и еще раз про само свойство null safety: это не совсем техническая штука, скорее это про бизнес, если у меня в бизнесе какое-либо значение не может быть null, так пусть оно упадет на ранних этапах, чем с каким-то null жить.
Релиз Dart 2.10: на шаг ближе к null-safety
» data-src=»https://habrastorage.org/webt/bx/6z/zo/bx6zzobmc1fbzqkkzocurdk7gsg.jpeg» data-blurred=»true»/>
Команда языка Dart постепенно приближается к одному из самых мажорных релизов — null-safety. Эта фича есть у многих лидеров рынка, включая Kotlin и TypeScript. По понятным причинам этот релиз нельзя сделать в виде рубильника: одним днём и простым апдейтом. Набравшее скорость сообщество, выпустившее огромное количество пакетов, уже не сможет перейти на мажорную версию по щелчку пальцев. Поэтому процесс этот поступательный и не такой быстрый, как хотелось бы. Тем не менее сомневаться в том, что уже довольно скоро язык станет значительно лучше и удобнее, не приходится!
Мы в Wrike не смогли обойти стороной обновление релиз Dart 2.10 и переводим статью из официального блога Dartlang.
Новый, унифицированный dart для всех ключевых задач. А также апдейт по таймлайну null-safety и принципах миграции.
Авторы: Майкл Томсен и Кевин Мур
Сегодня мы анонсируем новый Dart, версия 2.10 (two-dot-ten). В этом релизе представлен новый унифицированный Dart: единый инструмент для всех возможных задач разработчика, таких как создание проектов, анализ и форматирование кода, тестирование и компиляция приложений. У нас есть обновленная информация об этапах работ по null-safety, а также принципы миграции существующего кода.
Новый унифицированный инструмент разработчика Dart
О null-safety
Мы хорошо поработали с null-safety с тех пор, как несколько месяцев назад запустили первую техническую превью. В полной null-safety мы видим инструмент предотвращения трудно обнаруживаемых null-ошибок, а также дополнительный бонус к улучшению производительности. Если хотите узнать больше, рекомендуем нашу новую страницу Understanding null safety. Если предпочитаете короткое видео, посмотрите the null safety video с мероприятия Flutter Day, прошедшего несколько месяцев назад.
Когда null-safety будет готова к использованию? Вот наш прогноз:
Работа Flutter с техническим превью 2: Мы успешно перенесли большую часть Flutter. Ожидаем, что скоро — вероятно, в течение следующего месяца — среда Flutter будет полностью перенесена, и, таким образом, мы будем готовы к экспериментальному использованию Flutter. Вы сможете попробовать null-safety во Flutter и выполнить пробную миграцию ваших приложений и пакетов Flutter. Вам нужно будет использовать экспериментальный флаг. Не используйте экспериментальные функции в продакшне и не публикуйте перенесенные пакеты.
Ранняя миграция пакетов с помощью бета-версии: позже в этом году мы завершим улучшение производительности и обеспечим достаточное количество тестов, чтобы быть уверенными, что функция работает должным образом и что обратная совместимость надежна. К тому времени мы опубликуем бета-версию этой функции, и вам не нужно будет передавать экспериментальный флаг. Надеемся, что владельцы пакетов начнут их миграцию в null-safety, тем самым проведя последний раунд проверки, насколько эта функция готова к стабильному выпуску.
Использование в продакшне stable: в зависимости от обратной связи на запуск бета-версии мы исправим все оставшиеся проблемы, а затем опубликуем их в stable. Трудно назвать конкретные сроки, но мы думаем о начале следующего года. Как только эта функция станет стабильной, мы надеемся на широкое внедрение null safety приложениями, опубликованными в сторах, и многими и пакетами, опубликованными в pub.dev в стабильных версиях.
Принципы перехода к null-safety
Мы хотели бы поделиться нашими главными принципами для миграции на null-safety.
Переходите, когда будете готовы
Null-safety — это фундаментальное изменение в системе Dart. Это меняет основы объявления переменных, потому что мы решили сделать переменные ненулевыми по умолчанию:
Такое фундаментальное изменение было бы разрушительным, если бы мы настаивали на принудительном переходе. Мы хотим, чтобы вы сами выбрали подходящее время, поэтому null-safety — это опциональная функция: вы можете использовать последние версии Dart и Flutter без необходимости включать null-safety. Вы даже можете иметь зависимость между пакетами с уже включенной null-safety и приложениями или пакетами, которые ее еще не подключили.
Переходите постепенно, по порядку
Мы настоятельно рекомендуем переносить код по порядку, начиная с “листьев” графа зависимостей. Например, если C зависит от B, который зависит от A, сначала перенесите в null-safety A, затем B, затем C. Этот порядок применяется независимо от того, являются ли A, B и C библиотеками, пакетами или приложениями.
Почему порядок так важен? Вы можете добиться некоторого успеха в переносе кода до миграции ваших зависимостей, но вы рискуете столкнуться с необходимостью повторной миграции, если ваши зависимости изменят свои API в процессе переноса. Мы предоставим инструменты, которые помогут вам узнать, какие из зависимостей были успешно перенесены. Если вы являетесь автором пакета, то, чтобы избежать риска нарушения API, подождите, пока все ваши зависимости не будут перенесены, прежде чем публиковать null-safe версию.
Используйте автоматизированные инструменты для снижения затрат на миграцию
Инструмент миграции интерактивен, поэтому вы можете просмотреть свойства, которые он определил. Если вы не согласны с каким-либо из выводов инструмента, можно добавить подсказки о допустимости пустых значений, чтобы изменить вывод. Например, если вы хотите сделать API не допускающим null, даже если потребуется некоторый рефакторинг, можно сообщить об этом инструменту и повторно запустить анализ миграции. Добавление всего нескольких подсказок по миграции может оказать огромное влияние на качество перехода.
Получите полную выгоду при полном использовании
Как только весь ваш код — и пакеты, от которых он зависит — будет перенесен, он может выполняться с гарантированной null-safety. До этого момента ваш код будет работать и компилироваться так же, как сейчас, но абсолютная null-safety обеспечит полную верификацию времени выполнения и оптимизацию компилятора. Выполнение тестов с полной null-safety поможет избежать проблем с присваиванием null во время выполнения, а компиляция приложений с null-safety гарантирует, что вы получите оптимизации сразу и в будущем, например, меньший по размеру скомпилированный вывод и более быстрое выполнение.
Дальнейшие действия
Скоро у нас будет больше новостей о null-safety. Скорее всего, в течение следующего месяца, когда наши друзья в команде Flutter будут иметь фреймворк Flutter с поддержкой null-safety, готовый к экспериментам. Следите за обновлениями в блоге Flutter. А пока вы можете поэкспериментировать с null-safe кодом Dart с помощью DartPad с null-safety и узнать больше о дизайне функции в документации по null-safety.
Null safety в Dart
Привет, Хабр! Представляю вашему вниманию перевод статьи «Announcing sound null safety» автора Filip Hracek с моими комментариями:
Null safety — безопасная работа с пустыми ссылками. Далее по тексту для краткости и по причине устойчивости термина будет использоваться английское наименование null, null safety. Да и перевод «нулевая безопасность» наводит совсем на противоположные мысли.
sound — в данном контексте (sound null safety) можно перевести как «надежный».
Если есть предложения по улучшению перевода или нашли ошибки — пишите в личку, постараемся исправиться.
Наступил важный этап для команды Dart с их представлением технического превью наработок по null safety. Null safety позволяет избежать целого класса ошибок, которые часто трудно обнаружить, а в качестве бонуса обеспечивает ряд улучшений производительности. На данный момент мы выпустили предварительное техническое превью и ждем от вас обратной связи.
В этой статье мы раскроем планы команды Dart по развертыванию null safety, а также объясним, что скрывается за термином Sound null safety, и чем этот подход отличается от других языков программирования.
Описываемая версия была представлена 10-го июня 2020 года.
Зачем нужна null safety?
Dart — типобезопасный (type-safe) язык. Это означает, что когда вы получаете переменную некоторого типа, компилятор может гарантировать, что она принадлежит ему. Но безопасность типов сама по себе не гарантирует, что переменная не равна null.
Null-ошибки встречаются часто. Поиск на GitHub находит тысячи репортов (issue), вызванных нулевыми значениями в Dart-коде, и еще больше коммитов, пытающихся решить эти проблемы.
Попробуйте обнаружить проблему обнуления ссылки в следующем примере:
Эта функция, безусловно, завершится ошибкой, если вызывается с обнуленным параметром, но есть и второй случай, который следует рассмотреть:
Null safety устраняет эту проблему:
С null safety вы можете с большей уверенностью опираться на свой код. Не будет больше надоедливых ошибок обращения к обнуленной переменной во время выполнения. Только статические ошибки в момент компиляции кода.
Если быть совсем честными, то текущая реализация все же оставляет несколько возможностей словить null-ошибки в момент выполнения, о них чуть позже.
Sound (надежная) null safety
Немного спорное заявление, которое заставило меня копнуть в эту тему немного глубже, чтобы понять, как реализуется null safety в разных языках. В итоге я сравнил эти реализации в Swift, Kotlin и Dart. Результат этих изысканий можно посмотреть на записи нашего внутрикомандного доклада.
Подобная надежная реализация null safety в Dart имеет еще одно приятное следствие: это означает, что ваши программы могут быть меньше и быстрее. Поскольку Dart действительно уверен, что переменные никогда не могут быть обнулены, Dart может оптимизировать результат компиляции. Например, AOT-компилятор может создавать меньший и более быстрый нативный код, поскольку ему не нужно добавлять проверки на пустые ссылки.
Мы видели некоторые очень многообещающие предварительные результаты. Например, мы увидели улучшение производительности на 19% в микробенчмарке, который эмулирует типичные шаблоны рендеринга в инфраструктуре Flutter.
Основные принципы
Прежде чем приступить к детальному проектированию null safety, команда Dart определила три основных принципа:
Необнуляемость по-умолчанию. /** Часто можно увидеть в виде абревиатуры NNBD в документации **/ Если вы явно не скажете Dart’у, что переменная может быть обнулена, он сочтет ее необнуляемой. Мы выбрали это как значение по умолчанию, потому что обнаружили, что в API ненулевое значение на текущий момент является наиболее распространенным. /** Вероятно, речь идет о переработке текущего API Flutter **/.
Поэтапная применимость. Мы понимаем, что должна существовать возможность постепенного перехода к null safety шаг за шагом. На самом деле, нужно иметь обнуляемый и null safety код в одном проекте. Для этого мы планируем предоставить инструменты, помогающие с миграцией кода.
Полная надежность (sound). Как упоминалось выше, null safety в Dart надежна. Как только вы преобразуете весь свой проект и ваши зависимости на использование null safety, вы получите все преимущества надежности.
Объявление переменных с null safety
Основной синтаксис достаточно прост. Ниже приведен пример объявления различных переменных. Обратите внимание, что по умолчанию используются необнуляемые переменные, поэтому они выглядят как прежде, но их значение не может быть обнулено.
Dart позаботится о том, чтобы вы никогда не присваивали значение null ни одной из перечисленных выше переменных. Если вы попытаетесь выполнить i = null даже тысячу строк спустя, вы получите ошибку статического анализа и красные волнистые линии — ваша программа откажется компилироваться.
Если вы хотите, чтобы ваша переменная могла обнуляться, вы можете использовать ‘?’ вот так:
Перечисленные выше переменные ведут себя точно так же, как и все переменные в актуальной версии Dart.
‘?» можно также использовать в прочих местах:
Но, опять же, хотелось бы, чтобы вам почти никогда не придется использовать ‘?’. Подавляющее большинство ваших переменных будут необнуляемыми.
Упрощение использования null safety
Команда Dart изо всех сил старается сделать null safety максимально простой в использовании. Например, посмотрите на этот код, который использует if, чтобы проверить нулевое значение:
Обратите внимание, что Dart достаточно умен, чтобы понять, что к тому времени, когда мы пройдем оператор if, переменная loudness не может иметь значение null. И поэтому Dart позволяет нам вызывать метод clamp() без лишних танцев с бубном. Это удобство обеспечивается так называемым анализом потока выполнения (flow analysis): анализатор Dart просматривает ваш код, как если бы он выполнял его, автоматически выясняя дополнительную информацию о вашем коде.
Flow analysis, уже существующая фича языка Dart, используется, например, при проверке соответствия типа. В данном случае они переиспользовали ее для null safety, что позволяет смотреть на отношения типа и опционального типа как на наследование:
Вот еще один пример, когда Dart может быть уверен, что переменная не равна null, так как мы всегда присваиваем ей значение:
Анализ потока выполнения работает только внутри функций. Если у вас есть глобальная переменная или поле класса, то Dart не может гарантировать, что ему будет присвоено значение. Dart не может моделировать поток выполнения всего вашего приложения. По этой причине вы можете использовать новое ключевое слово late, если знаете, что переменная будет инициализирована на момент первого к ней обращения, но не можете инициализировать ее в момент объявления.
Обратите внимание, что v не может быть обнулена, хотя изначально не имеет значения. Dart считает, что вы не будете пытаться прочитать v, пока ему не будет присвоено ненулевое значение, и ваш код компилируется без ошибок.
А вот и он — момент для выстрела себе в ногу.
Опасный костыль, который будет главным признаком пахнущего кода. Ситуацию усугубляет еще и то, что нет возможности проверить состояние переменной. Команда Dart обсуждала такую возможность, но пришла к выводу, что на данном этапе проще не реализовывать проверки, как, например, в Kotlin через рефлексию.
Второе спорное решение — это интеграция знакомого всем Swift-программистам восклицательного знака, то есть, теперь можно будет делать force unwrap в Dart.
В обоих случаях (с late и ‘!’) мы сможем при неправильном использовании получить ошибку в момент выполнения программы.
Также стоит упомянуть в контексте late еще одно нововведение, которое почему-то скрывается в статьях и видео от команды Dart. Добавлено новое ключевое слово ‘required’ для именованных параметров конструктора. Это уже знакомый ‘@required’, только не из отдельного пакета и без собачки.
Обратная совместимость
Команда Dart работала больше года, чтобы довести null safety до уровня технического превью. Это самое большое изменение языка со времен релиза второй версии. Тем не менее, это изменение, не ломающее обратную совместимость. Существующий код может вызывать код с null safety и наоборот. Даже после полноценного релиза null safety будет дополнительной опцией, которую вы сможете использовать, когда будете готовы. Ваш существующий код продолжит работать без изменений.
Эти бы слова, да разработчикам Swift в уши, особенно 3-й версии…
Но даже тут не так все радужно, разработчики сами говорят, что при совмещении null safety кода и «старого» кода в одном проекте они не могут гарантировать надежность (soundness) системы типов.
Дальнейший план действий
Мы планируем развернуть null safety постепенно в три этапа:
Начни уже сейчас
Самый быстрый способ попробовать null safety уже сегодня — это nullsafety.dartpad.dev — версия DartPad с включенной функцией null safety. Откройте раскрывающийся список «Learn with Snippets», чтобы найти серию обучающих упражнений, описывающих новый синтаксис и основы null safety.
Также можно поэкспериментировать с null safety в небольших консольных приложениях (мы еще не обновили более крупные фреймворки, такие как Flutter). Для начала нужно загрузить Dart SDK с dev-ветки, затем можно скачать этот пример консольного приложения. В README файле есть инструкции по запуску приложения с включением экспериментальной функции null safety. Другие файлы в примере предоставляют конфигурации запуска, которые позволят выполнять отладку в VS Code и Android Studio.
Также можно ознакомиться с документацией (в дальнейшем появится еще):
Спасибо за то, что дочитали до конца. Перевод немного запоздал, но, надеюсь, комментарии привнесли пользы материалу и это не стало просто переводом один в один. От себя хочется добавить, что эта фича — действительно полезный шаг вперед для всей экосистемы Flutter. Жду с нетерпением, чтобы начать использовать уже на живых приложениях. А пока, как говорится на забугорском, stay tuned!
Kotlin Null-Safety vs ClassLoader
Недавно я проходил собеседование и одним из вопросов, стал такой загадочный экземпляр:
«А какое главное преимущество системы типов Kotlin перед Java»?
Честно говоря, выделить какое преимущество считалось главным, оказалось неразрешимой для меня задачей: Nothing, отсутсвие Wildcard и First-Class Functions вместо Java-костыля с Functional Interface (имеется ввиду 8я версия Java) не заняли первых мест в личном топе интервьюера, который мне предложили угадать.
Но речь пойдет не о странных вопросах, связанных со вкусовыми предпочтениями интервьюеров относительно синтаксического сахара.
Дело в том, что Null Safety в Kotlin можно сломать, притом не выходя из под его безопасного купола в суровый дикий мир Java и Null-Referrences.
Long story short: ClassLoader ведет себя интересным образом при попытке загрузить статические поля классов рекурсивно ссылающиеся на классы друг-друга.
Не буду тянуть кота за хвост. Вот код:
Ни одной строчки кода на Java, ни одного предупреждения и NPE в результате выполнения.
Роковое стечение нескольких обстоятельств:
Рекурсивная ссылка, статическая инициализация, и не самый явный контракт поведения ClassLoader в JVM в таких случаяx.
//Рекурсивные ссылки
Давайте отсечем от нашего примера все, кроме рекурсивной ссылки классов на самих себя.
Зачем вообще так делать?
Любая другая ссылочная структура данных, например Node в Tree, тоже будет ссылаться на себя саму. И если вы очень любите писать графы, вы возможно столкнетесь с таким кодом напрямую.
Интересно будет так-же немного изменить пример:
Но будет ли у нас предупреждение, если мы попробуем создать рекурсивную связку из двух классов?
Такой кейс warning уже не выдает.
Issue на эту тему уже создан:
И это первая ступенька на пути к нашему NPE.
//Статическая инициализация
Что происходит в Kotlin когда вы создаете Companion Object?
Если попробовать транслировать Kotlin в Java, получится что-то подобное:
Опытные Java разработчики уже поняли к чему все идет, (если конечно не поняли с самого начала), а для широкой аудитории оставлю пояснение:
Это означает 2 вещи:
1. Объект в static будет жить пока жива его JVM.
2. Он будет проинициализирован (положен в PermGen) 1 раз. (Да, да есть кейсы когда это не правда, это на данный момент Out of Scope)
Для тех, кто вспомнил про синглтоны:
Возможно вы сейчас подумали что-то вроде «А зачем тогда нужен сложный синглтон с Syncronized если static’a достаточно?».
Ответ: синхронизация, и прочие техники нужны когда вам нужно инициализировать синглтон отложенно.
Нас интересует второй нюанс. Инициализация объекта идет 1 раз, а мы тут создали 2 объекта с перекрестными ссылками друг на друга.
Как поведет себя JVM в таком случае?
//Classloader
Что происходит в случае рекурсивной статической ссылки?
Примерно такая цепочка:
И в результате мы получим Class1, в котором есть Class2, в котором находится ссылка на Null.
//Sidenote: Заранее думайте о контрактах, если хотите написать что-то обратно совместимое.
//Big Picture
Итак, вся цепочка событий:
Kotlin компилируется в java bytecode и переходит в чудесный мир JVM и Null.
Благодаря статической инициализации классы будут загружены 1 раз, и мы не получим StackOverflow в Runtime, как только до них дойдем.
Бинго, мы провели анализатор и получили Null в контексте, где Kotlin его совершенно не ждет.
Наслаждаемся нашим NPE
Ну, действительно. Критиковать все могут, а решение то какое?
Такой код в свифте просто не скомпилируется, вам нужно пометить его отдельным ключевым словом indirect:
Я подумал некоторое время об этой проблеме (целых 45 минут), и возможно придумал решение.
Примерно таким образом в Java 1.1 были добавлены final, и таким же образом работает val в Kotlin: Мы не заставляем компилятор бросать exception, если мы модифицируем любое поле. Но мы можем пометить поле, и попросить компилятор бросить exception, тогда и только тогда когда оно помечено. На мой вкус, это даже более элегантное решение, чем то, что используется в Swift.
В коде это будет выглядеть так:
Warning без keyword:
Добавление keyword, теперь компилятор чекает иерархию и находит проблему:
Но, я не претендую на идеальные знания Kotlin и Java, JVM и компиляторов и тут будет интересен комментарий от JetBrains. Было бы здорово, если бы вы дополнили эту статью. Или помогли поправить, если я где-то ошибся в объяснении.
Поменьше доверяйте тулингу и embedded фичам, и всегда запускайте тесты.