5

БИНГО ошибок при создании BLoC'а

 1 year ago
source link: https://gist.github.com/PlugFox/7ee89778d0145f3bba704dbc4e4002da
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
БИНГО ошибок при создании BLoC'а

БИНГО ошибок при создании BLoC'а

ОШИБКИ:

  1. Начать писать логику непосредственно в mapEventToState,
    он у вас быстренько превратится в нечитаемую портянку и придете жаловаться на бойлерплейт.
    Если правильно готовить блок, то бойлерплейтом там и не пахнет,
    эвенты + стейты + блок умещаются все вместе на 1-2 экранах.
    Все запредельно воздушно, даже не надо создавать отдельные файлы под эвенты и стейты.
    Все ультра емко получается.

  2. Мутабельные стейты - нет и еще раз нет, все они должны быть помечены @immutable.
    Пэйлоада/нагрузки/данных стейтов это также касается.
    Желательно не проморгать и списки также завернуть в UnmodifiableListView.

  3. Создавать репозиторий прям сразу в блоке,
    а еще хуже доставлять его внутрь через гет_ит или синглтон,
    репозиторий может оказаться в блоке только через конструктор, все.

  4. Попробывать создать "свой блок", ведь "блок, это паттерн, а не пакет".
    Конечно, если вы не опытнейший архитектор с кучей ресурсов, временем на тесты/документацию/поддержку.
    А также у вас огромное комьюнити контрибьютеров готовое помогать вам в этой задаче.
    Эммм... Ну тогда что вы тут делаете?

  5. У БЛоК'а не должно быть дополнительных публичных методов, геттеров, сеттеров, переменных.
    Если у вас не выходит сделать что-то через pub/sub (add/listen), значит вы однозначно делаете это не правильно.

  6. Не соблюдаете уникальность стейтов:
    a) Вместо создания нового объекта стейта вы прокидываете существующий инстанс по ссылке
    b) Переносите объект из предидущего стейта в новый по ссылке
    c) Забываете про переопределенное равенство у стейта и его пейлоада.
    Во всех этих случаях вы рискуете хлопая ресницами удивляться, что заэмиченные стейты не доходят до UI.

  7. Cubit. Это не стейт менеджер и не архитектура.
    На проекте "это" применять нельзя.
    Исключение #1 - вы недавно начали знакомиться с реактивщиной и БЛоК'ом,
    в таком случае вы можете попробывать начать с него на небольшом демо проекте.
    Исключение #2 - у вашего Cubit'а не будет собственных публичных методов, только стандартный listen. Это может быть полезно для ловли сайд эффектов не зависящих напрямую от действий пользователя с интерфейсов. (watch к СУРБД, отслеживание геопозиции, отслеживание состояния интернета). То есть он выступает "прокладкой" между потоком репозитория и интерфейсом.

  8. Вы забываете про очередность эвентов и asyncExpand в transformEvents.
    Вы должны знать, что по умолчанию все эвенты обрабатываются строго поочередно.

  9. Вы не используете Bloc.observer для перехвата ошибок и логирования.
    Или делаете try { ... } on dynamic catch (e) { ... } без rethrow в своих блоках.

  10. Начиная с блока (включая) не должно быть импортов флатера.
    Исключение, пожалуй, несколько сущностей из flutter/foundation, которые могут быть заменены сторонними универсальными пакетами, не зависящими от flutter SDK.
    Не должно быть виджетов, не должно быть контекста.

ЗАБЛУЖДЕНИЯ:

  1. Если я создам БЛоК в initState/didChangeDependencies, добавлю в него эвенты, то они могут быстро обработаться и результирующие стейты не попадут в первый build StreamBuilder/BlocBuilder. Нет. До первого build'а эвенты даже не начнут обрабатываться, не то что эмит стейтов на их основании. А в случае StreamBuilder - первым снэпшотом ВСЕГДА будет то, что вы установите ему в initialData.

СОВЕТЫ:

  1. По возможности используйте freezed пакет под эвенты и стейты,
    а особенно его фичу с Union и методами when, maybeWhen.
    Сниппет для Android Studion / IDEA можете посмотреть здесь: https://github.com/dart-side/live-templates/blob/main/bloc.md

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

Author

PlugFox commented on Dec 2, 2020

так получается подписывать блок на обновление стейтов другого блока это хреново?

Почему хреново?
Я такого не говорил.

Начиная с блока (включая) не должно быть импортов флатера.
Исключение, пожалуй, несколько сущностей из flutter/foundation, которые могут быть заменены сторонними универсальными пакетами, не зависящими от flutter SDK.
Не должно быть виджетов, не должно быть контекста.

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

Author

PlugFox commented on Dec 2, 2020

edited
Начиная с блока (включая) не должно быть импортов флатера.
Исключение, пожалуй, несколько сущностей из flutter/foundation, которые могут быть заменены сторонними универсальными пакетами, не зависящими от flutter SDK.
Не должно быть виджетов, не должно быть контекста.

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

Давай представим две ситуации, когда тебе говорят:

  1. Слушай, а давай теперь сделаем все тоже самое, но на ангуляр дарте.
    При хорошей архитектуре логика портируется практически 1 к 1 без изменений. Портировать свое поделие сможешь?
  2. Слушай, что то у нас много ошибок, давай юнит тесты писать! Ты сможешь покрыть юнит тестом такой блок или придется рожать контекст откудато?

Идея БЛоК'а и в целом архитектуры - снизить зацепление (coupling), отделив слой интерфейса от слоя бизнес логики.
А ты, получается, все равно упорно перемешиваешь их, таща интерфейс в логику.

68747470733a2f2f626c6f636c6962726172792e6465762f6173736574732f626c6f635f6172636869746563747572655f66756c6c2e706e67

Что нужно сделать?
Достать БЛоК еще в интерфейсе и передать в конструктор другого блока.

да, я это и имел в виду
просто пока у меня часто меняются объекты которые я слушаю, мне проще пробросить один контекст чем бегать по файлам и менять конструктор

Author

PlugFox commented on Dec 2, 2020

да, я это и имел в виду
просто пока у меня часто меняются объекты которые я слушаю, мне проще пробросить один контекст чем бегать по файлам и менять конструктор

Ну это ошибка и не является оправданием)
А так - смотри сам.

@PlugFox не совсем понял чем плохо доставлять репозитории в блок через get_it? в подкасте говорится что это сделает невозможном написание тестов, а чем непонятно. Разве что в хуке подготовки теста будет заполняться инстанс get_it моковыми репозиториями. Или имелось ввиду что мы не будем знать какие репозитории нам нужны по сигнатуре и придется смотреть код?

Author

PlugFox commented on Jan 20, 2021

edited

@PlugFox не совсем понял чем плохо доставлять репозитории в блок через get_it? в подкасте говорится что это сделает невозможном написание тестов, а чем непонятно. Разве что в хуке подготовки теста будет заполняться инстанс get_it моковыми репозиториями. Или имелось ввиду что мы не будем знать какие репозитории нам нужны по сигнатуре и придется смотреть код?

Вот ты будешь писать не только юнит тест под блок, а скажем виджет тесты. Как ты доставишь зависимости тогда?
И естественно тестов должно быть больше чем 1, а гет_ит это тупо синглтон и то что ты туда загонишь в одном тесте - вылезет и в другом.

Да это все разруливается +- костылями, но зачем, если сразу можно сделать хорошо?
И более того, хорошо сделать не сложно и тебе в этом никто не мешает...

@PlugFox не совсем понял чем плохо доставлять репозитории в блок через get_it? в подкасте говорится что это сделает невозможном написание тестов, а чем непонятно. Разве что в хуке подготовки теста будет заполняться инстанс get_it моковыми репозиториями. Или имелось ввиду что мы не будем знать какие репозитории нам нужны по сигнатуре и придется смотреть код?

Вот ты будешь писать не только юнит тест под блок, а скажем виджет тесты. Как ты доставишь зависимости тогда?
И естественно тестов должно быть больше чем 1, а гет_ит это тупо синглтон и то что ты туда загонишь в одном тесте - вылезет и в другом.

Да это все разруливается +- костылями, но зачем, если сразу можно сделать хорошо?
И более того, хорошо сделать не сложно и тебе в этом никто не мешает...

А в чем проблема виджет теста? Как раз с гет_ит появится возможность управлять зависимостями без доступа к конструктору ( когда мы тестируем виджет в котором уже есть провайдер).

Как оно вылезет? перед каждым тестом формируется новое "чистое" окружение get_it.

Я не отрицаю что использование в конструкторе выглядит лучше, но если блок часто используется, приходится постоянно писать громоздкий вызов конструктора, что тоже не выглядит хорошо.
Возможно хорошим компромиссом будет помещение самого блока в get_it через injectable, что бы все его зависимости в конструкторе формировались автоматически, грубо говоря как это работает в том же ангуляре, тогда все зависимости будут в конструкторе, но при этом будут подтягиваться сами. Но я такой подход еще на практике не проверял.

Author

PlugFox commented on Jan 21, 2021

@PlugFox не совсем понял чем плохо доставлять репозитории в блок через get_it? в подкасте говорится что это сделает невозможном написание тестов, а чем непонятно. Разве что в хуке подготовки теста будет заполняться инстанс get_it моковыми репозиториями. Или имелось ввиду что мы не будем знать какие репозитории нам нужны по сигнатуре и придется смотреть код?

Вот ты будешь писать не только юнит тест под блок, а скажем виджет тесты. Как ты доставишь зависимости тогда?
И естественно тестов должно быть больше чем 1, а гет_ит это тупо синглтон и то что ты туда загонишь в одном тесте - вылезет и в другом.
Да это все разруливается +- костылями, но зачем, если сразу можно сделать хорошо?
И более того, хорошо сделать не сложно и тебе в этом никто не мешает...

А в чем проблема виджет теста? Как раз с гет_ит появится возможность управлять зависимостями без доступа к конструктору ( когда мы тестируем виджет в котором уже есть провайдер).

Как оно вылезет? перед каждым тестом формируется новое "чистое" окружение get_it.

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

Так вам и не надо каждый раз писать вызов конструктора блока. Зачем вам это?
В приложении? У вас блок в контексте.
В тестах? setUp и setUpAll.

Возможно хорошим компромиссом будет помещение самого блока в get_it через injectable, что бы все его зависимости в конструкторе формировались автоматически, грубо говоря как это работает в том же ангуляре, тогда все зависимости будут в конструкторе, но при этом будут подтягиваться сами. Но я такой подход еще на практике не проверял.

В том то и дело, что нет.
Использование контекста позволяет задать жизненный цикл при котором объект создается и уничтожается.
Позволяет задать динамично меняющиеся зависимости.
Определяет область видимости (вряд ли вы scope'ами пользуетесь в get_it).
Делает так, что вы чисто технически не можете получить что то из синглтона/сервайс локатора до того как оно туда было помещено или обновлено.

Я прекрасно понимаю почему люди используют get_it: ну чо, тут при старте приложения суну, а потом где угодно высуну и думать, чего то планировать, не надо.
Но это даже звучит как [3.14]здец и лапша вместо кода.

Думаю в ближайшем из стримов как раз расскажу как раз про то как мы выстраиваем архитектуру, чтоб не было такого: "ну вот надо чото сделать, сажусь рисовать формочки, затем к формочкам прикрепляю блок и потом пишу для него репозитории".

Так вам и не надо каждый раз писать вызов конструктора блока. Зачем вам это?
В приложении? У вас блок в контексте.
В тестах? setUp и setUpAll.

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

Я прекрасно понимаю почему люди используют get_it: ну чо, тут при старте приложения суну, а потом где угодно высуну и думать, чего то планировать, не надо.
Но это даже звучит как [3.14]здец и лапша вместо кода.

Используют для отделения описания зависимостей от описания логики и описания UI. Тем самым уменьшая связанность кода. Собственно как и DI В любой другой технологии.
Не понимаю где Вы тут увидели лапшу.
Вообще у меня бекграунд из ангуляра, поэтому возможно я пытаюсь работать с зависимостями как там. Где мы в модуле описываем зависимости, а компоненты уже сами из подтягивают.

Я понимаю что Flutter путь это передавать зависимости через провайдеры, но я стараюсь все что можно вынести за пределы флаттера выносить, что бы в моих виждетах (кроме виджетов которые должны жить сами по себе, аля контролы) было минимум не UI кода, и написание огромного конструктора в провайдере блока пока вызывает у меня кровь из глаз :D.

Author

PlugFox commented on Jan 21, 2021

edited

@karabanovbs

Для начала просто нарисуйте схему вашего приложения.
MaterialApp, Navigator'ы, экраны, виджеты, кнопочки.

Затем нарисуйте отдельно сбоку кружком GetIt.

А затем нарисуйте красными линиями что от чего зависит и что откуда что берет.

Как нарисуете - вставляйте сюда картинку и посмотрим где у вас там лапша)

Просто сейчас это выглядит так, что вы просто оправдываете то, что уже написали.
Могу показать как у нас было под тысячу строк зависимостей в injector'е (это аналог GetIt от гугла) как и у вас.
И естественно я в курсе за последствия таких допущений и сделок с совестью, больше так не делаем)))

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

Могу показать как у нас было под тысячу строк зависимостей в injector'е (это аналог GetIt от гугла) как и у вас.

Если есть возможность покажите.

Для начала просто нарисуйте схему вашего приложения.

Посмотрю, как со временем будет.

Author

PlugFox commented on Jan 22, 2021

edited

Если есть возможность покажите.

Да в том то и дело, что нечего там смотреть, так делать явно не стоит и получается дичайшая лапша с самыми неожиданными ошибками.

Даже нарушение SRP и проблемы с тестированием должны вам прям кричать о том, что вы явно делаете что то не так)

Можете ознакомиться с этим, к примеру: https://habr.com/ru/post/270005/

image

@PlugFox в статье говорится о случае когда мы скрываем зависимости внутри класса, это ошибка, я согласен (уже говорил об этом ранее). Но когда эти зависимости можно передать через конструктор, и в дополнение они аннотированы для работы с DI и следовательно экземпляр можно получать из DI не переживая о зависимостях.

То что DI внимательной конфигурации это тоже спорная проблема. И то что вы написали все в одном файле это также Ваше решение.
Не уверен что это нарушает SRP, ответственность данной сущности в конфигурации зависимостей, в чем нарушение?

У меня складывается впечатление что вы вообще против любого DI, или только во flutter? В angular, .net, java вроде используется и ничего страшного.

_ у вашего Cubit'а не будет собственных публичных методов, только стандартный listen. _

почему?

Author

PlugFox commented on Oct 22, 2021

edited

@karabanovbs

используется в <язык/фреймвок>

Это супер не валидный поинт. Много где и чего используется.
Во флатере используется GetX, более того, это топ 1 пакет по популярности.
При том, что большего говна я в жизни не видел.

it-юмор-geek-5955906

против любого DI

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

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

Если вы у себя каким то чудом смогли запретить команде использовать push операции в ваш "DI", все зависимости идут по ОБЛАСТЯМ ВИДИМОСТИ, все содержимое у него ИММУТАБЕЛЬНО, а если даже изменяются - то обязательно САЙД ЭФФЕКТОМ УВЕДОМЛЯЮТ подписчиков это уже другое дело.

Если вы делаете push операции ИММУТАБЕЛЬНЫХ объектов ТОЛЬКО НА ЭТАПЕ ИНИЦИАЛИЗАЦИИ, не храните в нем текущего пользователя и все такое - это нормально, хотя по сути бессмысленно и он по большому счету ничего и не делает.

Но если для вас "DI": ну тут я в него сунул, а тут высунул, а там снова суну, а вон там подобновлю объект - это ЧРЕЗВЫЧАЙНО плохой подход, чреватый неприятными последствиями, плавающими багами и лапшой из инверсии зависимостей, от которого на перспективу надо избавляться.

Флатер предоставляет гораздо более интересное решение для управления зависимостями.

Author

PlugFox commented on Oct 22, 2021

edited

@August79

_ у вашего Cubit'а не будет собственных публичных методов, только стандартный listen. _

почему?

Потому что в противном случае это нисколько не будет отличаться от того, что в флатере называют "simple state managment" через ChangeNotifier. У вас не будет очередности событий и как следствие поимете странные race condition когда за состоянием "разлогиниваемся" может запросто идти состояние "залогинен".

Ну и также этот подход ничуть не будет отличаться от "вызывать методы репозитория сразу из слоя виджетов", чем, собственно и будет являться.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK