Pull to refresh

Comments 16

Тоже стояла либа для DI, приложение на реакте. Страницы загружались по 4 секунды, выпилили, вздохнули свободно. Эти паттерны похоже в научно исследовательских институтах придумывают

Подскажите, что это за либа?

«постмортем» в заголовке, это что? «постсмерть»? :)

Вот в этой статье хорошо рассказали о Post Mortem: https://habr.com/ru/post/332762/


После завершения (с любым итогом) проекта менеджер обязан написать т.н. «post mortem». Цель очевидна: сделать хорошее и плохое, что было в ходе проекта явным как для себя, так и для других. Если бы жанр таких post mortem стал более популярен у нас, уверен, процент успешных проектов от этого вырос бы, какими бы ни были критерии «успешности».
Спасибо, не знал. Думал, ошиблись.

Почему не применить всем понятное слово "отчёт"? Отчёты бывают и о ходе разработки, и о результатах. В отчёте описываются возникшие, решённые и нерешённые проблемы.
На одном из моих рабочих мест отчёт на 30-40 листов после каждого проекта был обязательным требованием, никаких "постмортем".

Не мог пройти мимо. Я создатель библиотеки DITranquillity, может видели статьи. И работаю на проекте где Регистраций больше 1000. И на проекте встала проблема времени запуска на 5c уже более двух лет назад. Из них 3.5 секунды был DI. В силу чего в 3 версии своей Либы я делал упор на скорость, и время снизилось до 0.02 секунд на старте для DI на томже 5c.


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


А у swinject кстати график экспонициальный… То есть чем больше регистраций, тем больше время получения объекта, и это время где-то на 400 регистраций становиться уже не совместимыми с жизнью.


Ну и ссылочка на либу. Там можно найти performance тесты и проект для сравнения скорости со swinject. https://github.com/ivlevAstef/DITranquillity

Спасибо! Про библиотеку не слышал, почитаю и попробую. Вам лайк, репозиторию звезда.


Похоже, именно вас я и пытался найти этой статьей. У меня несколько вопросов к фреймворкам, а вы разбираетесь в их работе:


  1. Что делать с отладкой и профилированием? Логи и стектрейс становятся очень длинными, прочитать и вычленить суть сложно.
  2. Какие хорошие практики по организации развесистой структуры есть? Что можно почитать по теме? Например, наша проблема: пользователь выбрал новую страну и это аффектит половину зависимостей.
  3. Какие ограничения на зависимости? В какой момент может случиться комбинаторный взрыв? Вы приводите пример на 1000 регистраций, но вы и фреймворк свой сделали, нет сомнений что у вас все вычищено до блеска. Как не тупить обычным разработчикам?
  4. Допустим, я подключил библиотеку и использую её 5 лет. А потом что-то сломалось и мне понадобилось починить/переписать. Что мне делать с такой кучей кода?
  5. Зачем вообще решать эти проблемы в рантайме? Что я приобретаю, в сравнении с ручным управлением? Что теряю?

Много вопросов, вывалил как есть.


В вашей статье упоминается, что вы перенесли часть работы с рантайма на компиляцию, это оч круто.


container.register(YourViewController.self)
  .injection(\.presenter)

Да вопросом много, и разных. Ответ будет большим, извиняюсь заранее. Для тех кому не хочется читать всё, советую прочитать пункт 5 — там поднята важная проблема описанного решения в статье.


  1. Эту проблему я решал в своей библиотеке самой первой, так как до этого имел дело с Typhoon. Яб даже сказал — это причина возникновения библиотеки :) Вначале были исключения, но потом перешел на логирование и внятные fatalError. То есть, если что-то пошло не так, то есть относительно внятное описание, и с 80% вероятностью в описании будет указано место куда смотреть. Конечно, для этого надо уметь читать мой ломанный английский :D
    По другим либам – все грустно, иначе я бы не стал писать свою. В прочем сейчас уже появились CompileTime либы: Weaver, needle. Они частично решают проблему – частично, так как графы зависимостей бывают сложными и с циклами, а это уже надо проверять отдельными функциями.


  2. Почитать… ну "Чистая Архитектура" можно. Я еще люблю "ООАД" от Гради Буч. А так, каждое решение влияющее на большую часть приложения надо принимать командно, и желательно до того как начали его делать :)
    В вашем случае не понятно, почему смена страны ломает зависимости. Возможно, у вас есть распространенная проблема, когда в зависимости случайно попадают и данные.
    Из возможных проблем вижу лишь — у разных стран доступны разные экраны, и некоторые экраны выглядят настолько по разному, что у них много реализаций. Тут или умение работать с container-ами (кто мешает создать новый? :) ), или не подмешивать техническую бизнес логику в DI, и решать это в прикладном коде используя или аля фабрики, или обычные условия в router/coordinator/navigator.
    Я бы склонял к последнему, так как, если подмешивать в DI бизнес логик, четвертая проблема, указанная вами, выльется в катастрофу.


  3. Слегка с конца — в команде 25 человек, и более того я уходил с проекта на 2 года. Так что использование DI в проекте далеко от идеала :) Например, даже когда я был на проекте, и мы переходили на третью версию библиотеки, которая начала искать проблемные циклы, то оказалось, что у нас есть циклы длиннее 15 объектов, и при создании одного простого экрана, приходится создавать более 50 зависимостей. К слову сказать два года назад Swinject просто ломался на подобных графах, и просто крашился, из-за внутренних ошибок.
    По скорости
    Там видно, сколько занимает сам процесс регистраций, и сколько занимает процесс получения 50000 раз объекта, при разном количестве регистраций. Можно увидеть, что сам процесс регистрации зависимостей не линеен, и где-то на 4000 регистраций время станет не приятным. А вот получение объектов линейно зависит от количества регистраций, и даже на 4000 тысячах ещё приемлемое. Но я не представляю себе проекты даже с 2000 регистраций на Swift в ближайшие лет 5, а там и мощности будут быстрее, и сам язык быстрее (по профайлу самое медленное место в библиотеки это ARC), и у меня еще есть идеи как оптимизировать. Ну, и на чистом 'C' всегда можно написать.
    "Как не тупить обычным разработчикам?" — Если решили чем-то пользоваться, то научитесь этим пользоваться. Вы же когда пишете на языке понимаете что пишите? Ну, я надеюсь :D Тоже самое и с библиотеками — прежде чем начать на них писать, их надо как и язык изучить, и разобраться. DI контейнер автоматически предполагает, что вы понимаете, зачем вам нужно внедрение зависимостей на проекте, а не "так сказал тим лид".
    По практике скажу — это не самая простая тема, я проводил лекции внутри компаний, и видел, как люди не понимают концепцию. И это даже не зависит от опыта.


  4. Все на ваш страх и риск. Предположим, вы писали на UIKit, а apple через 5 лет сказало — выпиливаем теперь только SwiftUI :D Вы можете уменьшить проблемы, если все будет отделено. Если у вас UIKit был только во View, а не оказывался случайно в базе данных (такое бывает), то проблема будет маленькой и решаемой.
    Тоже самое и с библиотеками DI. Я выбрал концепцию "декларативный контейнер" и не долюбливаю концепции внедрения через атрибуты, аля Dagger2. В Swinject, DITranquillity при правильном использовании, код знающий о DI либе всегда отдельный, он никак не пересекается с другим. Соответственно подмена этого кода = подмене только этого кода, без необходимости искать по всему проекту. Но это в любом случае подмена, которая потребует времени.


  5. Начнем с главного. DI либы аля моей и Swinject не представляют из себя никакой магии — это просто хорошо обернутый Dictionary. Как работает Dip не помню, я его код 2.5 года назад смотрел, и уже не помню по каким причинам решил больше не возвращаться. Соответственно если отбросить то, что библиотеки пишут люди и допускают разные ошибки, то оверхед не такой уж и большой, по сравнению с ручными вызовами.
    "Зачем вообще решать эти проблемы в рантайме?". Я задам встречный вопрос — вы перешли к варианту 450 функций resolve(). Но не описали: каким образом у вас решаются циклические зависимости? Что делать если объекты не надо пересоздавать каждый раз? А если надо создавать каждый раз, но у вас ромбовидные зависимости A->B A->C B->D C->D где объект D должен будет создаться единожды, но на каждое получение объекта A, он разный? Что делать если объект реализует с 10 протоколов, и вы хотите чтобы он отдавался по всем этим протоколам? А обратная ситуация — один протокол, но много реализаций?
    А теперь добавим модульность, и последние вопросы становятся еще интересней :)



Собственно говоря, я к чему. На проекте в 10-30к swift строчек кода, вполне можно обойтись концепцией DI без библиотек. Да где-то придется попотеть, вспомнить про Фабрику сервисов, а именно этот паттерн описан у вас, просто он использует возможности автовывода типов. Да это займет условных 1000 строчек, а с библиотекой бы заняло 100. Но это возможно, я так делал и все было хорошо. Кстати у вас точно не модульное приложение ;) Ну или вы не до конца описали GeneralAssembly — при модульности, его придется делать многоуровневым и с использованием протоколов.


Но как только размерность проекта выходит за рамки человеческого понимания — в нашем проекте уже никто не знает, как работает ядро, или как работает самый верхний роутинг, многое написано 4-5 лет назад и людьми которые уже давно уволись, но с тех пор код там не менялся. И такого кода много. C зависимостями у нас также — они очень сложные, они межмодульные. Многие зависимости отключаемые/включаемые. То есть выкинул модуль, собрал проект, и все кто зависел от этого модуля продолжат работу, просто без части функционала (Ну это в идеальном мире правда… все мы люди, и ошибки есть).
И вот тогда DI библиотеки начинают экономить уйму времени, и кода.


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

Спасибо за такой развернутый ответ, есть над чем подумать.


Пока могу ответить только за 5-й пункт как реализовали корнер кейсы сейчас. Но, все решения пока принимались в режиме «надо срочно ускорить старт, сложные задачи решим попозже»:


  1. Циклические зависимости. Я их вычищал при переписывании, поддерживать в любом случае неудобно. Парочка осталась на самых базовых классах, эти объекты поднимаются на старте и после создания ручками проставляются связи через проперти. Рядом лежит todo на доделывание.
  2. Как не пересоздавать. Сделал lazy var проперти для таких объектов, и уже к их возвращаю через resolve(). Про ромбовидные зависимости либо я не понял, либо вот так и решились: объект A извлекается как обычный resolve(), а D это lazy var, который тоже возвращается из resolve().
  3. У объекта много протоколов. Не проверял, но, resolve() -> A & B & C должен работать.
  4. Один протокол, но много реализаций. Для этого у меня решения сходу нет.
  5. Модульность. Модульность бывает разной, применительно к DI пока задачу не понял. Мы делим приложение двумя образами.
    • Слоями: сеть, хранение данных, доменная логика, аналитика.
    • Экранами: меню, корзина, история заказов. Деление по экранам позволяет собрать мини приложения на замоканых данных для быстрой работы со всеми видами тестов.
  1. Простой пример цикла в популярных паттернах наших :) Presenter знает о View, но View должна как миниум держать presenter. В некоторых случая должна иметь delegate. Конечно подобный цикл можно сделать и ручками, без DI.


  2. Предположим есть экран — это объект A. Каждый раз когда заходишь на экран, он должен создаваться, и все его зависимости (B,C,D) тоже должны создаваться каждый раз. Но на объект D ссылаются B и С. Если сделать его lazy var, то D не пересоздастся при создании нового A. Если его сделать всегда создаваемым, то у B и С будут разные экземпляры D.


  3. Я про случай когда вот такие функции должны возвращать один и тот-же объект: resolve() -> A, resolve() -> B, resolve() -> C.


  4. Я про вот эту модульность: "слабо связанные снаружи". Все остальное это "мы просто сделали куча фреймворков, и назвали модулями". Смысл модулей, в том чтобы максимально все скрыть, оставим минимум для взаимодействия (ну если это не куча вспомогательных функций), и не забывать при этом что в отличии от классов между ними связь в обе стороны не возможна на прямую.
    Ну а теперь более конкретно, на примере экранных модулей. Есть модуль A и модуль B. С экрана из модуля A можно перейти на экран из модуля B. С экрана из модуля B можно перейти на экран из модуля A. Как делать будем? И как усложнение — реализаций каждого экрана может быть несколько, и только сам модуль знает какую реализацию когда использовать.



P.S. На все эти кейсы решение есть, но назвать эти решения удобными и понятными не всегда можно. Как говорится "плавали знаем" :)

  1. Presenter знает о View, но View должна как миниум держать presenter. Я начал с того, что регистрировать каждый класс не нужно. У экрана все должно сводиться лишь к паре инфраструктурных зависимостей. Пример:

extension ApiDeadViewController: StoryboardInstantiatable {
    func didInstantiateFromStoryboard(_ assembly: GeneralAssembly) {
        self.presenter = ApiDeadModule.ApiDeadPresenter(
           ui: self,
           interactor: ApiDeadModule.ApiDeadInteractor(localeService: assembly.resolve()))
    }
}

4 — Даже в постановке задачи это звучит как штука, над которой не очень много контроля и с ней не супер просто/приятно работать. Я бы выносил связь модулей выше и писал сценарий явно. Но я понимаю желание свести это на уровень DI.


Чуть вернусь к изначальному моему утверждению «не используйте рантайм библиотеки». Я рад, что узнал про вашу разработку и я понял, чего мне не хватало раньше: графика производительности в ридми каждого фреймворка. Для меня это было самым важным вопросом, но я не мог найти на него ответ за приемлемый срок. Теперь он у меня есть, спасибо вам :-)

Нет, приложение пока не работает с пиццериями в США

Я имел ввиду прогу для российского рынка в американском App Store, как делают многие здесь для клиентов с американскими экаунтами

Понимаю, но пока так не открываем. Запросов не было (или оч мало), а в странах, где есть Додо, но нет приложения это будет сильно смущать. Такой негатив видно в России, когда приложение минусуют за то, что в каком-то городе нет пиццерии.

Sign up to leave a comment.