Недавно ко мне прилетел вопрос, касательно того как правильно делать логирование. Нужно ли делать логер статичным или же инжектить его везде через DI?
Вообще этот вопрос часто может возникнуть у начинающих разрабов, которые только изучили концепцию DI. Я по себе помню, что как только знакомишься с этим паттерном, в твоих руках он превращается в молоток, а ты везде начинаешь видеть гвозди. DI далеко не панацея и есть много мест в коде, где этот паттерн не приносит пользы, а наоборот делает код сложнее в поддержке.
И вот топ 3 вещей которые обязаны делаться через статику:
📜Логирование/Аналитика. Мне досталось одна либа, в которой логирование было сделано именно через DI. Каждый раз когда мне нужно было писать лог, приходись прикидывать логер через конструктор, это максимальное неудобное убожество.
Суть логера в том, что он должен быть доступен в любой части программы, независимо от слоя UI/Presenter/Domain. Может возникнуть вопрос, а как тогда быть, если логер нужно подменить, например заменить в релизной сборке. Идем и смотрим как это сделано в Timber. Либа предоставляет возможность подменить реализацию во время инициализации приложения через статические методы. Устанавливаете нужный логер вначале и потом не парите голову.
С аналитикой ровно тоже самое. Аналитика также должна быть доступна из любой части программы без возни с DI. Как быть если у нас несколько систем или нужно будет заменять их в будущем? Во-первых, мы фигово предсказываем будущее, а во-вторых делайте также как Timber.
🖼 Загрузка картинок. Вот это вообще такая вещь на грани, вот почему. Очевидно что смешивать View и DI затея не самая удачная. Потому как у вас View становятся зависимы от вашего DI, помимо этого важно с самим DI не закосячить в плане скоупов и ЖЦ View, так еще это и максимально неудобно в коде.
При этом с другой стороны тут у нас network слой, что означает что его по-хорошему контролировать. Подсовывать нужные там proxy, или отслеживать куда трафик идет, возможно как-то хитро подменять url или добавлять заголовки к запросам на картинки. Должна быть возможность это все конфигурировать и подменять это все для разных фичей.
Тут на самом деле, даже и думать не придется, потому как все либы для работы с картинками работают по одному и тому же принципу. Для примера можно взять coil. По умолчанию используется один и тот же экземпляр загрузчика, который можно сконфигурировать при старте приложения. Если же нужен другой загрузчик, можно прям на месте загрузки сконфигурировать свой и подсунуть в туже же самую функцию.
⛓Dispatchers/Schedulers. Раньше почти на каждом проекте был следующий подход. Мы не используем библиотечные шедулеры (вроде
Schedulers.io(), Schedulers.computation()) а сделаем интерфейс:interface Schedulers{
fun io():Schedulers
fun computation():Schedulers
}Затем как вы уже поняли инжектим этот интерфейс везде где у нас есть асинхронные вызовы. Нужно это для тестирования, ведь теперь максимально удобно взять и подменить шедулеры на тестовые.
Идея дурацкая по двум причинам: первое как я уже писал в посте про тестирование, лучше ничего не подменять и тестировать в условиях реальной работы. Иначе в тестах на одном потоке все прекрасно, а в проде гонка и плавающие баги, второе это просто бесячее дублирование в каждом классе с асинхронщиной, что по сути везде кроме UI.
Ну а что делать если подменять все таки нужно? Если RX, то ничего делать не нужно, он из коробки позволяет подменять стандартные шедулеры. Корутины к сожалению такого не умеют, однако это можно сделать через статику. Делаем класс, который по умолчанию возвращает библиотечные реализации диспетчеров, а в тестах эту статику меняем. Единственный минус, что важно теперь следить чтобы все использовали этот самый класс для получения диспетчеров, который мы договорились использовать.