Как стать автором
Обновить
2
0
Левицкий Даниил @levitckii-daniil

Пользователь

Отправить сообщение

Мы согласны, что throw является допустимым подходом для обработки ошибок и исключений, особенно с учетом разделения Error и Exception в Dart. Но давайте я попробую расшифровать, какие структурные проблемы решает Either в нашем примере использования.

В Дарте есть разбор ошибок по типам:

Это больше история про реализацию, а не про контракт. Например, у вас есть интерфейс:

abstract class AuthInteractor {
  Future<bool> authorize();
}

Пока вы не обратитесь к его реализации, вы не сможеет узнать, выбрасывает он UnAuthorizedError или нет (может он вообще ходит в сеть и выбрасывает Http-ошибки), а если реализаций несколько, то все еще хуже, ошибки могут быть разные, а ваш код об этом даже не узнает. В java, например, было ключевое слово 'throws', позволяющее указать на уровне интерфейса, какие ошибки выбрасывает метод, также оно заставляет вас обработать эти ошибки в месте вызова. В Dart, кажется, такой функциональности нет.

А если говорить об Either, то вы четко на уровне контракта описываете допустимые ошибки:

abstract class TimeApiService {
  Future<Either<CommonResponseError, TimeResponse>> getTime();
}

При вызове такой функции вы получите Either, а не результат TimeResponse, что гарантированно заставит вас обработать ошибку (CommonResponseError). В случае, если тип ошибки изменится, то типизация вам подскажет все места, где сломался контракт.

Типичный кейс: делаем сетевой вызов, ловим ошибку на уровне контроллера, выводим лог, зануляем какие-то переменные, отправляем в Catcher, перевыбрасываем. Снова ловим на уровне виджета, показываем снэкбар с ошибкой.

Этот случай как раз и показывает проблемы, которые могут возникать при перевыбрасывании. Например, если ваш интерактор обращается к 3 репозиториям, каждый из которых выбрасывает ошибки со своими типами, то, перевыбрасывая ошибки этих типов, его контракт раздувается и становится очень сложен для восприятия, а это очень частый кейс при работе с throw. Потом эта ошибка начинает протекать во все слои вашего приложения и, при смене контракта, вносить правки становится очень тяжело.

Хотел заметить, что ошибки, которые генерируюстя не в UI-слое, архитектурно находятся за пределами UI-слоя, поэтому проникновение обработки ошибок в UI нарушает чистоту архитектуры. Еще одна проблема - завязывание виджета на ошибки делает его плохо переиспользуемым, потому что для воспроизведения этих кейсов в других местах, придется выбрасывать такие же ошибки.

Это нормальная правильная обработка ошибок, вы к ней просто не привыкли.

После досточного длительного опыта работы в нативной разработке с Java/Kotlin, где сильно распространены throw/try/catch, довольно сложно к ней "не привыкнуть" :)

Сейчас в проекте у нас сгенерированый файл с деревом зависимостей приложения занимает 600 строк, но это не человеко-читаемый код, потому что он генерируется, если бы мы его писали руками и с использованием Provider, он скорее всего бы дошел до 1000-1500 строк. Плюс проект у нас пока достаточно молодой, если апроксимировать цифры на наш основной нативный проект, то это была бы история про 10000+ строк.

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

Однако для нашей команды намного привычнее концепция GetIt для управления зависимостями, весь инфраструктурный код генерируется, а ЖЦ каждого объекта наглядно виден в коде самого объекта. Это досточно серьезные плюсы для нашей команды, и при этом мы не видим каких-то минусов или проблем, создаевамых из-за использования GetIt+Injectable.

Благодарим за обратную связь, мы всегда рады услышать мнение других и понять движемся ли мы в правильную сторону! 

Хотим обратить внимание, что мы просто делимся опытом решения, которое выработали за длительное время и с проведением экспериментов конкретно под наши задачи. Мы не настаиваем, что оно единственно верное и все проекты должны быть такими, поэтому просим не расценивать ответы на тезисы, как вступление спор: 

1. Const - подразумевают исключительно константы. Если называть их util, подразумеваются утилитарные вещи - константы / общие функции без домена / экстеншены над dart core. У нас был такой опыт, утилиты стали доходить до нескольких тысяч строк кода, поэтому мы приняли решение сделать более жесткое сужение.

2. Про arch вы действительно правы, мы указали, что в идеальном мире они должны быть вынесены в отдельные pub-package, но на этапе, пока решение не задокументировано и покрыто тестами, его лучше не выливать в package для других внутренних/внешних потребителей. Сделать arch саб-модулем решение хорошее, мы к нему тоже пришли. Про свой опыт модуляризации, в том числе пакета arch, планируем рассказать в будущем, это достаточно неоднозначная тема.

3. mappers - вещь опциональная, даже на уровне статьи мы нигде не указываем ее, как обязательную часть нашей структуры директорий. Хотим заметить, что их область использования чуть шире, чем fromJson и toJson, потому что вы можете работать не только с REST, а также в Mapper вы можете добавить проверки соблюдения контракта с бекендом, аналитику нарушений контрактов и тд. Из-за этого логика маппинга начинает распухать и раздувать модели, что, по-нашему мнению, удобнее вынести в изолированный объект. Вполне возможно наше восприятие было искажено длительной Kotlin/Java разработкой, и поэтому мы приходим к таким выводам.

4. Вы правы, SingleResult фактически и есть StreamController, развёрнутый над Bloc, и StreamListener, развёрнутый в SrBlocBuilder, поэтому не очень понятно, в чем состоит костыльность абстракции. Ввод дополнительной абстракции, полностью совместимой с оригинальным Bloc, мы обосновываем экономией кода в каждом отдельном Bloc, но самое важное - мы можем строить инфраструктуру вокруг этой абстракции: централизованное логирование SR, аналитика, реализация TimeMachineDebuger с учетом SR. 

5. Мы пробовали строить приложение исключительно на Provider, но скорость разработки значительно уменьшалась при росте используемых объектов. Когда проект разрастается и подходит к 30, 50 тысячам строк кода, собирать объекты руками становится достаточно проблематично. А для понимания ЖЦ объекта приходится обращаться к дереву виджетов, что не всегда очевидно, плюс иногда было необходимо обращаться к дереву без контекста.  

Мы не исключаем, что мы просто не смогли его приготовить, но решение GetIt + Injectable позволило решить все наши боли без видимых потерь и повысить скорость работы. Мы не сделали абсолютно все объекты на GetIt, а использовали Provider(BlocProvider) из-за не стандартного ЖЦ объекта (factory/singleton), BLoC должен вести себя как Scoped-объект. А наши тесты использования Scope в GetIt показали, что проект начинает усложняться и пока она нужна только для StateManagment-объектов, нам удобнее использовать BlocProvider. 

6. Мы упомянули в статье, что ThemeData + ColorScheme + TextTheme не бился с дизайн-токенами, которые были сформированы в нашей компании задолго до начала разработки на Flutter и поэтому поделились свои решением, которое позволяет решить эту проблему. Нам показалось, что это достаточно полезно для команд в похожей ситуации. Также решение в виде централизованного Bloc над сменой темы позволяет включать всю инфраструктуру Bloc (логирование, аналитика и так далее) в этот процесс, а также менять тему без контекста. Хотим заметить, что мы сделали минимально необходимый mapping нашего объекта темы в стандартную тему.

По нашему опыту обилие StateManager-решений характерно и для нативной разработки. Если говорить про Flutter-решения, то их обилие косвенно связанно с разным бэкграундом разработчиков, переходящих во Flutter, также у каждого StateManager есть свои плюсы, минусы и ограничения, так что тема действительно актуальная, будем стремиться ее раскрыть. Благодарим за обратную связь!

В нашей статье мы не разрабатывали новый StateManager, а использовали один из наиболее популярных - Bloc, с небольшой доработкой SingleResult для приближения к MVI. Но ваша идея хорошая, постараемся в будущем разработать статью, сравнивающую разные StateManagment решения, возможно в окружении нашего “шаблона“.

Информация

В рейтинге
Не участвует
Откуда
Санкт-Петербург, Санкт-Петербург и область, Россия
Дата рождения
Зарегистрирован
Активность

Специализация

Mobile Application Developer
Lead
Flutter
Dart
Kotlin
Android development
Development of mobile applications
Java
Designing application architecture
SQL
JavaScript
Node.js