"В айти постоянно надо учиться..." - говорили они.
Расскажу-ка я на этот счёт одну историю из Android-разработки.
Давным давно, при появлении системы Android, Google сделал формат xml приоритетным способом задания вёрстки экранов, строк, ресурсов и много другого. Во многом это было продиктовано множеством экранов, языков и конфигураций системы: этим ресурсам требовалась "своя" организация. Отчасти это было связано с тогдашней "модой" на паттерн Model-View-Controller: в коде существовали классы -"контроллеры" экранов (Activity, Fragment) - и соответствующая вёрстка для них.
Для обращения к элементам вёрстки xml из кода, при компиляции генерировался специальный файл-"контракт" с идентификаторами элементов, а также множество методов парсинга и доступа к xml-части проекта.
И если возможность задания одного и того же файла строк несколько раз, к примеру, для разных языков - была воспринята как должное, то с вот вёрсткой возникали вопросы. Как определить, какой именно тип элемента получаем из вёрстки? Что делать, если элемента с данным id нет? А если таких элементов несколько, да ещё и в одном файле? Как быть, если элемент есть, к примеру, только в вёрстке для планшетов?
Google для этого сделал возможность задать каждому элементу вёрстки тоже иметь свой идентификатор (android:id), который тоже после компиляции был доступен из проекта, а также метод findViewById - который:
- мог возвращать null, если элемента с таким идентификатором в иерархии не найдено
- требовал ручного приведения типа возвращаемого элемента к желаемому
- внутри себя представлял ни что иное, как сложный рекурсивный поиск по иерархии
Выглядело это примерно так:
Это было и неудобно, и громоздко и выглядело ужасно. Перед работой с каждым элементом вёрстки - необходимо было вызвать для него findViewById. При этом вызове, каждый раз был риск получения NullPointerException из-за отсутствия элемента в вёрстке, или некорректного его типа.
Решением стала библиотека ButterKnife, где этот утомительный процесс был переложен на кодогенерацию:
Но из-за генерации кода, увеличивалось время компиляции проекта. К тому же, все view биндились в один момент - что плохо сказывалось на скорости работы приложения.
Так же, примерно в это время, была выпущена среда Android Studio - которая тоже пыталась помогать, как могла: автоматически компилировала файл-"контракт", делала подсказки, если элемент вёрстки отсутствовал для какой-то конфигурации девайса.
Несколько лет спустя, до проблемы добрались и разработчики kotlin из JetBrains. Появился Kotlin Android Extensions - плагин для сборщика gradle, позволяющий мгновенно получать доступ к элементу из вёрстки. Там и время компиляции меньше увеличивается, и привязка проходит при первом обращении, и кэширование есть.
Только вот с этой "магией" появились другие проблемы:
- Можно импортировать не только полный файл, но и отдельный элемент вёрстки. Это позволяет по ошибке импортировать элемент вёрстки с таким-же названием из другого файла, и получить падение приложения в runtime (т.к. элемент УЖЕ будет
- Абсолютная несовместимость со старым кодом на java.
Но тут в игру включился и сам Google. Был разработан ViewBinding - который очень напоминал подход ButterKnife - но теперь всё это было и в Java и в Kotlin, с кэшированием из коробки, никак не затрагивало компиляцию. А в коде импортировался файл, сгенерированный по макету вёрстки (что исключало обращение к элементу "не из нашего" файла).
Финальной строчкой стало... внедрение Jetpack Compose. Суть этой штуки в том, что вся вёрстка теперь просто создаётся из кода!
Всё! Проблема findViewById решена! Он больше просто не нужен.
Этот пример дал мне пищу для размышлений: а как же впринципе, определять "временные" технологии в разработке?
Когда обязательно стоит мигрировать проект - а когда можно расслабиться, т.к. "замена" технологии придёт через пару лет?
1) Самое главное: всегда ищите корень проблемы!
У данной проблемы корень был именно в том, что для разработчиков вёрстка и код были в разных форматах, и требовались дополнительные усилия, чтобы их "подружить". Всё!
Проблемы NullPointerException, борьба с громоздостью кода и т.д. - это всё просто следствия, которые долго исправлялись костылями.
2) Смотрите на конференциях, и в коммьюнити: какие реальные проблемы решает данная технология?
Не то, что написано в красивых доках - а для чего его тянут люди в проде, на реальных проектах? Часто бывает, что реальные возможности и нужны лишь на 10-20%. Даже в данном примере, ButterKnife добавлял множество фишек: биндинг слушателей событий, биндинг других xml-ресурсов. Но когда нашлось лучшее решение проблемы findViewById - про это всё безжалостно забыли!
Более глобальный пример - внедрение реактивщины в android в своё время было популярно из-за неудобной работы с потоками. Реактивные фреймворки вообще являют собой отдельную пападигму и стиль программирования, давая широкие возможности почти во всех аспектах разработки.
Но как только появились корутины - многие "выдохнули", и "глубина" технологии стала не просто минусом, а одним из стимулов отказа!
Когда у технологии есть некая "главная" решаемся проблема, из-за которой большинство начало её использовать - как правило, она устаревает именно с приходом лучшего решения этой "главной" проблемы.
3) Смотрите на ... хайп проблемы!
Бывает, что какие-то технические долги весят в продуктах годами, им придумали костыли, и... просто массово используют, не придумывая нового. Часто это какая-то отдельная область, которая особо никому не нужна. В то же время, насколько критична проблема findViewById? Есть ли статистика упавших продов, или количества ошибок при дебаге, даже в первом случае? Нет! Это просто видная, поверхностная проблема, которую благодаря простоте со временем увидели разработчики всех квалификаций - а известные в сообществе люди начали изобретать свои решения!