Разработка
8 антипаттернов в кодовой базе Android
Сделаем нашу жизнь проще.
Мы работаем с кодом каждый день. В основном с некоторым существующим кодом, созданным другими, или, как мы всегда любим называть его «унаследованным кодом» (legacy code).
На самом деле, независимо от того, старая кодовая база или новая, всегда есть какие-то антипаттерны, на которые можно пожаловаться. Идеальная кодовая база встречается довольно редко.
В этой статье я хочу перечислить то, что я больше всего ненавижу в кодовой базе Android-приложений, это предвзятое мнение обычного разработчика.
Так что смело не соглашайтесь.
№1 Без Kotlin
Если проект использует только Java и все еще находится в активной стадии разработки, я не думаю, что у него есть амбиции использовать все новые технологии.
Я не думаю, что работа над этим проектом — хороший выбор для молодых разработчиков, которые только вступают в мир Android.
Я бы посоветовал перейти на Kotlin, по крайней мере, для новых функций или нового кода, который мы затрагиваем, бит за битом.
Если вы не можете сделать даже этого, бегите от этого проекта.
№2 Без DI-фреймворка
Ну, это уже 2022 год.
Для мира Android внедрение зависимостей не является новой темой. В былые времена у нас был Dagger, а теперь Hilt решает вопросы еще проще.
Если вам не нравится следовать рекомендациям Google, вы также можете выбрать одно из многих сторонних решений, таких как Koin.
Есть много вариантов на выбор, но вам не нравится ни один из них?
Последствия могут быть такими:
- Существование множества шаблонного кода только для сборки экземпляров зависимостей.
- Ваши экземпляры синглтонов могут быть катастрофой для понимания и обслуживания.
- Если вы используете ViewModel, вы пишете множество фабрик для создания ViewModel-ей.
№3 Объект с глобальными переменными
Это может быть еще одна более широкая тема, является ли синглтон паттерном или антипаттерном.
Да, синглтон полезен.
Только тогда, когда вы ясно понимаете его границы. Когда вы уверены, что это необходимо. Не злоупотребляйте им для всего.
Еще хуже, когда в объекте есть публичные переменные. Каждый в любом месте может читать и писать их. Да, это глобальные переменные.
Это похоже на ключ к дьявольским вратам.
Вы не можете отследить все сложные способы использования, код сложно отлаживать и поддерживать.
Объекты плохи по следующим причинам:
- Сложно сделать модульное тестирование.
- Иногда вам приходится писать уродливый код, чтобы присвоить значения, а затем устанавливать их в null.
Можно использовать их, когда внутри них только какие-то утилитарные функции. Но в мире Kotlin у нас также есть другие варианты, такие как функции верхнего уровня и расширения.
№4 Более 10 переключаемых функций и A/B-тестов
Если вы менеджер по продукту, у вас могут быть веские причины для всех включаемых и выключаемых функций и A/B-тестов. И я не оспариваю их ценности.
Как упоминалось с самого начала, я всего лишь обычный разработчик, которому приходится иметь дело с этим кошмаром «if-else».
Даже если я полностью понимаю и согласен с ценностями бизнеса, это не мешает мне их ненавидеть.
Внедрение новых функций с помощью переключателя может быть тривиальной задачей, поскольку вам нужно оставить все как раньше, когда переключатель выключен.
Но если разработчики делают это в масштабе, например, копируют файлы и дают им другие имена, могут возникнуть дубли. И теперь уже другие изменения (для других функций), возможно, нужно будет применять к обеим версиям.
Если разработчики решат сделать это даже в небольшом масштабе, например, с if-else на нескольких строках, то нужно будет быть точными при каждом изменении.
В таких условиях реализовать совместимость непросто. И это порождает плохой запах кода. Если разработчик очень терпелив, он добавит модульные тесты для сценариев включенной и выключенной функции. Но он также может упустить некоторые условия, если старый код не был покрыт тестами. И часто рефакторинга не будет. Какой смысл рефакторить код, который нам скоро не понадобится?
Это также раздражает QA, поскольку каждая функция должна тестировать больше потоков, что означает не только включение и выключение функции A, но также включение и выключение функции B в функции A.
Да, и все определенно становится хуже, если наши включаемые и выключаемые функции вложенные. У них возникают сложные отношения, которые вам нужно объяснить QA и себе. Если вложено более двух переключаемых функций, я думаю, пришло время поговорить с вашим PO, чтобы удалить одну.
№5 Устаревшие зависимости
Некоторые библиотеки были очень популярны, но теперь устарели.
Я смотрю на тебя, ButterKnife.
Это известная библиотека, но я считаю, что сейчас уже довольно редко можно увидеть ее в каком-либо Android-проекте.
Но на самом деле мы можем столкнуться и с другими.
Кроме того, могут быть некоторые сторонние библиотеки, которые больше не поддерживаются.
Если у вас есть этот флаг в ваших gradle.properties:
android.enableJetifier=true
Вы можете запустить эту задачу, чтобы проверить, использует ли какая-либо из ваших зависимостей устаревшую библиотеку поддержки, а не AndroidX:
./gradlew checkJetifier
Если вы найдете какие-то библиотеки, могу поспорить, что они не обновлялись годами.
Пожалуйста, оцените, можете ли вы найти им лучшую замену.
№6 Злоупотребление наследованием
Очень больно видеть кодовую базу, в которой есть куча базовых классов для Activity, Fragments и ViewModel-ей.
Куча означает несколько или много.
Например, между Fragment фичи и Fragment AndroidX может быть 2 или 3 родительских фрагмента, которые играют свои загадочные роли.
Когда разработчик хочет добавить новый Fragment, он может запутаться, следует ли наследовать от базового фрагмента и какую «базу» использовать.
И то же самое для ViewModel. Вам действительно нужна ViewModel для хранения состояния загрузки и другая ViewModel для хранения переменной навигации? Как новой ViewMoel решить, нужно ли повторно использовать их все или делать что-то самостоятельно?
Обычно BaseFragments и BaseViewModels будут иметь некоторый базовый тип, определенный как generic. Или, что еще хуже, у вас есть BaseUseCase.
Пожалуйста, попробуйте избавиться от этой сети наследования, она усложняет навигацию в IDE как никогда.
Мы должны минимизировать сложность, не так ли?
Надеюсь, вы всегда помните о композиции, а не о наследовании.
№7 Интерфейсы для всего
Вам не нужны интерфейсы для всего.
Например, интерфейс для репозитория, интерфейс для источника данных. И они используются внутри.
Если интерфейс имеет только одну реализацию, нет смысла ее оставлять.
Возможно, какой-то разработчик разработал это с добрыми намерениями, но в итоге мы получили лишь много шаблонного кода.
Если другая реализация предназначена для теста, давайте посмотрим, является ли это реальным случаем или просто пока используется для мокирования.
№8 Стиль EventBus
EventBus — известная библиотека. Она также известна как антишаблон, потому что она настолько удобна, что вы можете опубликовать одну вещь и получить ее где угодно. Это делает вещи неорганизованными.
Чрезмерное использование EventBus в кодовой базе может привести к катастрофе. Это уменьшит намерение писать более структурированный код.
Иногда мы пишем «код EventBus», даже не используя библиотеку. Я надеюсь, что такой подход применяется только в нескольких случаях, а не во всей вашей кодовой базе.
8 антипаттернов: подведение итогов
Это все на данный момент.
Надеюсь, эта статья будет вам полезна. Вы можете обнаружить, что мы страдаем от одних и тех же проблем. Или вы узнаете, почему разработчики в вашей команде не так счастливы и каждый день заканчивают с клоками волос в своих руках.
Сделаем нашу жизнь проще.