Главная » Хабрахабр » Kotlin: две ложки дегтя в бочке меда

Kotlin: две ложки дегтя в бочке меда

Появление Kotlin – это важный бонус для разработчиков. Высокоуровневый язык, бесшовно интегрирующийся с Java, значительно расширяет возможности программистов. Однако в любом языке мы постоянно сталкиваемся с некоторыми неприятностями, которые, напротив, создают ограничения, и Kotlin, конечно, не стал исключением. О них мы и поговорим сегодня.

Особенно это заметно на Android, где новые версии Java появляются, мягко говоря, «со скрипом».
Kotlin был с воодушевлением воспринят сообществом, так как новый язык программирования значительно упрощает написание кода, заменяя собой Java. В лучшем случае поддержка аппарата заканчивается примерно через 2 года после его выпуска,  что сулит одно, максимум — два обновления системы. Многие аппараты обновляются с большими задержками, ещё больше – вообще не получают обновления прошивки. Для справки, сейчас суммарная доля Android 7 и 8 — тех версий, где есть поддержка Java 8 со стороны системных библиотек — составляет 42,9%, всё остальное — Java 7. Однако люди продолжают пользоваться устройствами, а значит разработчикам приходится ориентироваться как на (пока ещё) новейший Android 8, так и на Android 5, а то и более ранние версии, где уж точно нет не только самой последней, но даже актуальной реализации Java.

Но есть также и мнение, что Java – это язык, который хорош «as is», и его не нужно трогать. Кто-то может говорить, что Java устарела. Он содержит джентльменский набор функций, а для тех, кому нужны дополнительные возможности сегодня доступны специальные языки, работающие уже поверх JVM. Это как НОД (наибольший общий делитель) в математике. Однако Kotlin отличается рядом плюсов. Этих языков уже расплодилось довольно много: Scala, Clojure, JRuby, Jython, Groovy и прочие, менее известные. Этот язык оставляет больше свободы программисту: он позволяет одновременно использовать готовые фрагменты на Java, объединяя старый и новый код.

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

Скрыть нельзя показать?

Пакеты, как водится, представляют собой довольно распространённый способ организовать классы по пространству имён. Но их плюс не только в этом, в Java они ещё и выступают средством для ограничения видимости классов и их членов.

Для классов их всего два – они видны либо в рамках пакета (package private), либо полностью открыты (public). Напомню, что в Java есть 4 разных категории, которые позволяют разграничить видимость. Но метод или поле уже можно сделать private (он будет недоступен вне класса), видимым только для пакета (package private), можно сделать так, чтобы метод дополнительно был виден наследникам, как в своём пакете, так и вне пакета (protected), а также можно его сделать видимым для всех (public).

И это оказывается очень полезно для выстраивания толкового API. В Java 9 появилась возможность разбивать код на модули, и теперь есть возможность сделать некоторую часть кода видимой везде внутри модуля, но не извне.

А вот в Kotlin почему-то ограничились тем, что ввели публичную и приватную видимость, а также видимость для наследников. Одним словом, опций в Java — более чем достаточно. Модуль — это уже не конструкция самого языка, и частенько люди понимают под этим термином довольно-таки разные вещи. Кроме этого, правда, они ввели разграничение по модулям, но разграничить доступ к методам и классам по пакетам стало нельзя. В Kotlin официально определяют модуль как «набор скомпилированных вместе Kotlin-файлов».

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

Модули, как правило, делают относительно большими, чтобы они представляли собой завершенную функциональную единицу. Особенно ярко это проявляется, если, например, собирать проекты в Gradle, как обычно и собираются приложения на Android. И если мы хотим сделать видимость методов более гранулярной, возникает проблема. В рамках одного модуля метод нельзя сделать видимым одним и не видимым другим классам.

Конечно, всегда можно наделать больше модулей, но, учитывая  особенности Gradle, это просто нерационально: скорость сборки будет снижаться. Тут сразу хочется вспомнить про пакеты, ведь в Kotlin эта сущность никуда не делась, но на видимость она, увы, не влияет. Поэтому хотелось бы получить какой-то другой способ сокрытия методов, например, по образцу Java. Да, есть возможность засовывать классы в один файл, но в случае с большим проектом файл станет действительно «тяжёлым».

Не обрабатывай это… даже если и надо бы

Второй — довольно спорный (потому что некоторые считают их использование моветоном), но тем не менее минус – отсутствие проверяемых исключений. Эти средства есть в Java, но в Kotlin решили их не реализовывать. В официальной документации есть пример про интерфейс Appendable. К Appendable можно присоединять строки, и поскольку строки могут подсоединяться к объектам, связанным с вводом/выводом, например, при записи в файла или в сеть, при вызове методов интерфейса потенциально можно получить IOException. И такие случаи делают использование проверяемых исключений неудобным.

Объясняют свою аргументацию создатели языка следующим образом: если мы используем StringBuilder, обращаясь к нему через Appendable, нам, оказывается, нужно обязательно обрабатывать IOException, даже если вы уверены, что оно в принципе не может произойти:

Appendable log = new StringBuilder();
try { log.append(message);
} catch (IOException e) { // Must be safe
}

Выход из ситуации: исключение «ловят», но ничего с ним не делают, что, разумеется, не есть хорошо. Однако возникает вопрос: если мы заведомо работаем со StringBuilder через интерфейс Appendable – почему не взаимодействовать напрямую со StringBuilder? Тут есть важная разница: при работе с Appendable, если мы не знаем, какая под ним лежит конкретная реализация, исключение ввода/вывода становится действительно возможным, но вот StringBuilder точно его не выдаст, и соответствующие методы в нём так и объявлены, хотя они и реализуют интерфейс. Так что пример получается довольно натянутый…

Но вот только в соседних главах пишется, что проверяемые исключения использовать можно и нужно, если делать это с умом. Особенно интересно, что авторы документации ссылаются на главу 77 в Effective Java, где говорится, что исключения нельзя ловить и игнорировать. Если так избирательно цитировать, можно оправдать любую точку зрения.

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

Но они и на откровенно неудачных языках успешно пишутся, это ведь не аргумент. Если обратиться с вопросом к разработчикам, они просто разводят руками и говорят, что в других языках нет проверяемых исключений и ничего, на них тоже создаются большие и надёжные программы. Только вот документации может не быть, она может быть неполной или устаревшей — компилятор же её не проверяет. На StackOverflow на подобный вопрос говорят, что «нужно читать документацию» — хороший ответ, очень удобный в подобных ситуациях. Наконец, в ней можно так и не найти ответ.

Когда проверка на исключения — заведомо излишняя, чтобы компилятор «был доволен», приходится прописывать try...catch и фактически просто создавать мусорный код, ведь при обработке этих исключений не делается ничего. Да, это правда, что наличие проверяемых исключений может приводить к созданию неудобных API. Но ведь проверяемые исключения — это лишь инструмент, который точно так же можно использовать грамотно, там, где это нужно. И то, что в функциональном коде неудобно их использовать — тоже правда.

Взять те же перегружаемые операторы, которые убрали из Java, потому что они могут привести к появлению API с неочевидными действиями — это была защита программистов от самих себя. Непонятно, почему язык забрал эту возможность, если философия Kotlin заключается в доверии большего количества инструментов программисту (по крайней мере, нам так показалось), и если автор кода может грамотно расписать, где какие исключения будут, почему бы не поверить ему? Уж не сделали ли это создатели из-за того, чтобы просто было не как в Java? В Kotlin, напротив, есть возможность перегружать операторы и делать многие другие вещи – так почему же нет проверяемых исключений?

Мы ждём перемен…

Максимум, на что мы можем рассчитывать под Android – это Java 7 или Java 8 (но уже с некоторыми ограничениями и оговорками), в то время как на подходе уже Java 11. С использованием Kotlin программирование на Android становится намного проще, сокращается количество строк текста.

Может быть, в следующих версиях появятся новые инструменты для анализа исключений в IDE, а также новые категории приватности. Остаётся лишь надеяться, что разработчики дополнят этот очень полезный язык теми возможностями, которые по неочевидным причинам отсутствуют сегодня. Но, судя по всему, это дело в лучшем случае отдалённого будущего, так как никаких обещаний разработчики языка пока даже не озвучивали.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Бесплатная трансляция DotNext 2018 Moscow

Меньше недели осталось до конференции DotNext 2018 Moscow: она пройдет в конгресс-парке гостиницы «Рэдиссон Ройал Москва» 22-23 ноября. Между докладами будут вестись интервью с ключевыми спикерами конференции. По традиции, прямо на YouTube будет открыта бесплатная онлайн-трансляция первого зала (ссылка спрятана ...

Прерывания от внешних устройств в системе x86. Эволюция контроллеров прерываний

В данной статье хотелось бы рассмотреть механизмы доставки прерываний от внешних устройств в системе x86 и попытаться ответить на вопросы: что такое PIC и для чего он нужен? что такое APIC и для чего он нужен? Для чего нужны LAPIC ...