Pull to refresh
5
0
Send message
«возможно, мы нарушили YAGNI»

Как отличить соображающего разработчика от не очень соображающего? Второй в любом разговоре оперирует аббревиатурами и терминами. Даже когда они значат ничего.

Автору, буде хайптрейн оседлать пытается, рекомендую сделать статью относительно термина «НБИ» (не будь идиотом) и «ППК» (пиши правильный код). Главное писать емоционально, а противопоставлять что-нибудь осмысленное и с ненулевым порогом вхождения.
Я рад что вас заинтересовала данная тема:)

Использование композиции в целом приветствуется и является жизненно важным. Композиции данных — через Dependency Injection во вьюмодели (есть десятки лет опыта и наработок «как» это делать наиболее безболезненно). С композицией компонентов думаю понятно, они априори друг в друге живут.

По затратам на перевод… Понимаете, что-бы получить обьективную метрику нужно взять два продакшн проекта выполненных по одной бизнес спецификации но написанные по разному технически и их переписать. При этом нужно как то обеспечить что-бы люди работающие в этих командах были +- одной квалификации. А проекты должны быть именно что большие. Иначе говоря — это невозможно. И этой диллеме в контексте оценки мейнтейнабилити уже сто лет. Если бы подобные «обьективные проверки» были реализуемы — у нас бы был один «самый лучший» фреймворк а все остальные бы канули в лету ввиду худших «обьективных» метрик.

Потому так взлетают хайповые темы — главное их распиарить и проинвестировать. А реальные убытки от недооценки минусов проявятся у отдельных команд через 1-3 года. Ещё 1-3 года те будут думать что это не проблема фреймворка а проблема в головах команды. За это время можно и фреймворк переписать по принципу «миша всё ***ня давай сначала», распиарить, проинвестировать и пошла вторая итерация.
Самые известные приверы таких итераций: ангуларжс->ангулар, реакт->реакт+флакс(дефакто редакс)->реакт+хуки.

Потому важно понимать «а для чего вообще я беру фреймворк, чего я от него хочу и какую проблему решаю». Потому написана данная статья — что-бы показать как фреймворки «используются» при написании приложения, а не «диктуют условия написания приложения».

По бекбону:
Предложенная структура подразумевает что 3party либам мы отдаем такие ответственности как «рендер строго типизированных вьюх» и как «реактивность». И то и то бекбон делать не умеет (реактивность — умеет но криво и с оговорками).
Т.е. использовать бекбон для рендиринга вместо реакта — невозможно. Для реактивности вместо мобикс — возможно но нужно будет написать ооооочень много своего кода.

Если вас интересует практический кейс переезда на другой фреймворк — то я перевёл рендеринг на svelte с подачи одного из комментаторов выше в треде. Вот коммент с описанием результатов и ссылкой на бранч с кодом.

p.s.
Более конкретно: миграцию провести тем легче, чем меньше инфраструктурный код просачивается в «бизнес-значимый» код приложения. В идеале — миграция проводится тупо заменой реализаций инфраструктурных методов. В случае данной статьи — заменой реализации методов withVM и createConnect().

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

Если да, то есть нюанс:
  • открытость/закрытость — это детали реализации контрола «дропдаун мультиселект», и жить они должны на вьюмодели этого контрола
  • список выбранных елементов — это данные, о которых должна знать «конкретная форма» для того что бы их отправить, провалидировать и тэ де. В исключительных случаях она может эту информацию игнорить (как например вы можете поместить на форму текстбокс и не читать его значение) но это редкость. Т.е. список выбранных елементов должен жить на вьюмодели формы НО! наш переиспользуемый компонент должен его дублировать на случай если компонент формы его не предоставляет (оставляет компонент дропдауна в uncontrolled виде)


Вот тут набросал как будут выглядеть елементы зашаренного дропдаун контрола и пример его использования контролируемый и неконтролируемый. Всё в одном файле, комментами разделено. Будут вопросы — велкам, надеюсь код прояснит написанное.
ViewModel не отвечает за загрузку данных, а делегирует эту ответственность сервису (DAO). Например как во вьюмодели из статьи на строке 18.

offtop: В приведённом примере дао синхронно исключительно потому что так сделано в шаблонном todoMVC которое я воспроизводил 1 в 1. По хорошему там должен возвращаться промис а не значение, и код бы выглядел как:
this.todoList = await this.todoDao.getList();


При «уходе» со страницы соответствующая вьюмодель разрушается. (Кстати не только по этой причине, например дерево компонентов может быть разрушено и заменено и без перехода на другую страницу, в случае каких то динамических форм билдеров например). В WPF это отрабатывается емнип через метод Dispose, в коде из статьи — через метод cleanup.

что сделать для реализации вашего кейса:

  1. Возвращать из DAO не обычный промис, а что-то похожее промис с дополнительным методом «cancell()».
  2. В методе загрузки тудушек заменить
    this.todoList = await this.todoDao.getList();

    на
    this.todoListRequest = this.todoDao.getList();
    this.todoListRequest.then(list => this.todoList = list);

  3. Добавить во вьюмодель метод
    cleanup() {
        this.todoListRequest && this.todoListRequest.cancell();
    }

По поводу аналогии с разработкой игр я с вами согласен на все сто и рад что вы это заметили. Было бы максимально нелепо тянуть стейтлес подходы в стейтфул мир.

Минутка истории:
MVC была придумана в доисторические времена именно для statefull приложений. На моём веку использовался в таких динозаврах как java, прости господи, swing. Но «говорят» что бывало и до того. В 2000-2010 с подачи RubyOnRails, MVC феерично перекочевал в клиент-серверный (стейтлес) сегмент синхронно с девиацией слоя модели из богатой (rich, еффективно для стейтфул) в бедную (anemic, еффективно для стейтлесс). Вы, вероятно, путаете подход и его реализацию в RoR (и последовавших за ним клонах на других языках — Asp.Net MVC, Spring MVC и прочая).

C выходом технологии WPF, авторам даже пришлось придумывать новый термин MVVM что-бы их структуру кода не путали с MVC, которое де-факто стало строго ассоциироваться с клиент-серверным взаимодействием (хотя по факту девиация МVVM от именно «классического» МVС незначительная).

Подход к структурированию кода описанный в этой статье как раз основан на MVVM.
Он не имеет ничего общего с клиент-серверной разработкой. Слой представления состоит из связки View и ViewModel, где ViewModel как раз отвечает за состояние и является стейтфул. А статья — отвечает за то «как» и «где» обьявлять вьюшки и вьюмодели. Обратите внимание, я избегаю разделения одного и другого в структуре файлов и явно называю это одним слоем — иначе мы получим бессмысленное и трудное для восприятия расслоение, свойственное ортодоксальным n-layered системам.

Статья ограничена разбором исключительно слоя представления поскольку для 90% приложений выделенная доменная модель тупо не нужна. Но если доменная модель таки нужна и она не имеет привязки к конкретным представлениям — выделенный слой домена является лучшим «местом» для её определения. Повторюсь — это не «обязаловка» и нужен этот слой только лишь если ваш заказчик начинает говорить терминами и категориями, никак не кореллирующими с формочками и табличками, видимыми в приложении. Соответственно мы не можем «разместить» эти категории внутри «основы» нашего УИ приложения — дерева компонентов.

Для большинства же приложений более чем достаточно грамотно организованного дерева компонентов и вынесения интеграций со сторонними сервисами в отдельные, инфраструктурные, файлы и классы (папка boundaries в проекте-примере). При этом интерфейсы всё так же являются частью дерева компонентов и определяются внутри него, но реализации этих интерфейсов сгруппированы по принадлежности к конкретному внешнему сервису, с которым мы интегрируемся (это может быть local storage, API, another API powered by third-party provider e.t.c).
На досуге переехал на свелте в этом бранче

По итогу:
— good: вьюмодель не затронута
— good2: вью изменились исключительно с точки зрения движка-для-отрисовки, т.е. ситнтаксис слегка другой но конструкции идентичны.
— bad: функции среза перекочевали внутрь тега script конкретного компонента. С одной стороны заняло ноль усилий их перенести. С другой — в svelte варианте они не изолированы от шаблона. Для меня это неприемлимое нарушение принципа единственной ответственности. Но из за того что свелте не даёт определить в одном файле более одного компонента — нет и возможность «определить компонент и в том же файле завернуть его в HOC не смешивая со скриптом компонента». Решить можно или так как сейчас или создавая на каждую обёртку по файлу, что есть ещё большая дичъ т.к. связанный между собой код разбрасывается по разным файлам.
— bad2: тайпскрипт со свелте и связанные плагины для сборки это то ещё уныние. На сам перенос потратил часа полтора, а сборку поднимал два вечера.
Перечисленные вами факторы и обеспечиваются соблюдением принципов солид (на более менее крупных кодовых базах). DI это одна из техник, активно использующихся для разделения ответственностей, следующей из него сегрегации интерфейсов, а также инверсии зависимостей, необходимой во многих случаях для удовлетворения принципа Open/Closed.

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

Современное ФЕ комьюнити вырабатывает свои бест-практицес, к сожалению, не используя 20+ летние наработки из других технологий связанных с персистент клиентом, а наоборот «от решения задач интерактивности в контексте классических (ССР) веб приложений».

Отсюда большое количество метаний в разные стороны (флакс, редакс, хуки, css-in-js, js-in-css, yet-another-templating-engine). Особой пикантности этим толодвежиниям добавляет факт что часто трендсеттерами являются не опытные программисты, а эффективные ораторы. Из за чего експерементальный подход написанный талантливым практикантом становится мейнстримом на короткое время и подыхает в конвульсиях как только появляются реальные большие приложения, основанные на подобном подходе. Или не подыхает, а долго и грустно трепыхается на инвестициях от крупных технологических гигантов.

Каждый из подобных талантливых ораторов обязательно вещает про УБОПЛ(«удобно», «быстро», «очевидно», «просто» и «легко»). И это логично и правильно. Вот только УБОПЛ не берётся из вакуума и на больших кодовых базах не возможен без соблюдения определённой проектной дисциплины.
Возможно вам будет интересна моя статья и связанный репозиторий. Это конечно не заменит полноценного опыта разработки в команде, но на уровне шаблонного тудумвс примера мввм подход проработан.

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

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

Когда (если) появится проблема перемешивания «общей» логики и «вью-специфик» логики, либо локальные обсерваблы сделают вью не-читаемо-громоздкими — тут вам поможет данная статья.
А не возникнет проблемы — gl&hf, happy coding!
В треде явно завёлся минусаст, теперь по вашу душу:D

По сабжу: лично для меня такое соглашение (а скорее его важность) являлась бы шоустоппером для использования подобной структуры кода.

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

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

Если же выкинуть из головы редакс в принципе и обсуждать предложенный подход к структурированию кода как автономный — разделение на контроллер и стор (и соответственно наличие двух точек взаимодействия для вью) мне кажется ненужным усложнением.
Получается что вью дёргают методы контроллеров, «рассчитывая» что изменяться данные в «сторе», т.е. присутствует неявная зависимость.
Мне кажется более естественным способом привязки вью к данным наличие классической вьюмодели. У неё вью дергает методы и читает из неё данные. При этом вьюмодели инкапсулируют данные как душе угодно и могут отдавать только ридонли слепки по необходимости.

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

Лично мой подход — внедрение подобных сервисов во вьюмодели следуя Dependency Injection, но это уже вкусовщина. Тутъ пример в котором используется ДАО, инкапсулирующее работу с источником данных.
По диаграмам этот подход обеспечивает однонаправленный поток данных. Фактически по коду для обеспечения однонаправленного потока данных нужно будет постоянное и жесточайшее код ревью:

Компоненты вольны использовать сторы (как тутъ), сторы наделены геттерами и сеттерами. Если передо мною обьект и у него публичная операция на модификацию — ничего не сдерживает меня от использования этой операции.
Насколько я понимаю в вашем случае — «ничего кроме строгого запрета так не делать».
По моему опыту — такие «вербальные» запреты не работают.

Т.е. технически я не вижу причины отсутствия стрелочки «pass» направленной от вью к стору.
Поправьте меня если что-то пропустил.
В статье подчеркивается что библиотеки подбираются под ответственности и заменяемы в пределах ответственностей.

Т.е. Straightforward будет смена TSX рендерера либо либы отвечающей за реактивность. Э замена на свелте не будет аккуратной, т.к. свелте нельзя назвать библиотекой для рендера JSX, соответсвенно изменится логическая нагрузка на один из елементов структуры кода.

Но кейс интересный, на вскидку думаю что вьюхи мигрируют в свелте компоненты, а результат выполнения функций среза будет присваиватся свелтовским експортам. Интересная задачка, если вам интересно я попробую переписать приложенный TodoMVC и оценим «что и в каких обьемах» изменилось
Кстати, как бы это странно не звучало но именно вам имеет смысл вчитаться в статью переписывая в «реакт+мобкс». Потому что эта связка не определяет «где должны определяться сторы». Т.е. где физически должны лежать файлики и как сторы «деляться» между компонентами. Описанный в статье подход как раз доопределяет это. Но хозяин, конечно, барин.

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

1 — очень плотная связность между существующими обьектами: (марионеттовские view, которые логически — контроллеры, и модули). Были уроборосы наподобие такого: view1:riseEvent[E1] -> view2:proxyEvent[E1] -> view3:handleEvent[E1]:riseRequest[R1] -> module1:handleRequest[R1]:fireGlobalEvent[GE1] -> view1:handleEvent[GE1] and do some work. Это очень упрощенная цепочка, в реальности цепочки исчислялись десятками вызовов между разными вью/модулями/бекбоновскими моделями с ветвлениями. В ходе выполнения этой цепочки, различные вью случайно брали данные из подчинённой себе разметки И/ИЛИ модели.

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

В описанном в статье подходе во первых нет разделения на модели и вьюхи(marionette.views которые, напомню, на самом деле контроллеры) с недоопределёнными ответственностями для обоих. Есть только вьюмодели, данные из которых реактивно потребляются представлениями. Каждое потребление вьюмодели отслеживается через применение соответствующей функции среза, соответственно отследить все использования вьюмодели легко по использованиям функций среза и тайпскриптовским ссылкам. Нет ситуации когда непонятно где, для чего и как может использоваться конкретная функция, определённая на вью или на модели (скажем через глобальное событие трижджы спроксированное и приправленное reqres обработчиком). Вьюмодели инкапсулируют состояние, в отличии от бекбоновских моделей в которые можно установить любое свойство и на изменение которых можно подписаться из любого места приложения.

2 — непосредственно механики интеграции марионетта и реакта. У нас были адаптеры — марионет вьюшки, которые хостили реакт компоненты. И обратные адаптеры — реакт компоненты, которые могли хостить марионет вьюшки.
Процесс миграции был определён так:
— выбирается вью либо страница.
— все данные, содержащиеся на вью (контроллере) и на модели (если такая наличиствует, зачастую на иерерхии моделей) перетекают в мобкс стор (иерархию сторов). Все подписки вью и модели добиваются мобикс реакциями. Вью перерендеривается при именении данных на сторе. Регрессионное тестирование.
— перевод разметки из марионет шаблона в реакт компонент, реагирующий на уже подготовленный мобкс стор. Монтирование этого компонента в марионет приложение с помощью соответствующего адаптера. Если вью использует внутри себя какие то общие вью — монтирование их в реакт компонент с помощью обратного адаптера.

По пункту 2 нет «специфических проблем» решаемых предложенным в статье подходом, это просто требовало громадного headroom от разработчика. Условный джун или условный уставший сеньор просто не в состоянии были взяться за миграцию сколько нибудь большого фрагмента.

Предложенное решение говорит что смена ТСХ рендерера или смена движка реактивности — это изменение инфраструктурного кода, которое не затрагивает код вьюмоделей или вьюшек, т.е. конкретных фич.
Одно с другим не связано. Я имел в виду что ваш перенос приложения целиком на другой фреймворк (пусть и за год) можно назвать успехом.
Инкрементальный перенос который я упомянул (франкенштейн) можно назвать компромисом который позволил приложению «жить», но что это была за жизнь!

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

Вы не единственный в комментах кто воспринимает предложенный подход как «свой фреймворк» но я бы его так не определял.

Скорее это подход к структурированию кода. Для упрощения восприятия можно обзывать подходом к структурированию кода поверх реакт-мобкс связки.

Отвечая на вопрос про использование веб-компонентов и локалстореджа — в пункте 6 статьи определены цели, которых мы хотим достигнуть, в частности:
— возможнсть использования строго типизированных представлений (для чего мы используем TSX).
— возможность ограничить представления разметкой, т.е. сделать максимально тупым.

На мой взгляд функциональные TSX компоненты решают эти задачи лучше всего. По крайней мере я не знаю как определять строго типизированные веб-компоненты, равно как не знаю как определять веб компоненты без дополнительного жс кода (пусть и примитивного).

Что касается хранения данных. Моей целью было:
— определить обьекты, отвечающие не только за хранение данных, но и за инкапсуляцию действий по модификации этих данных
— обеспечить реактивность (перерисовку представлений при перерисовке данных)
— обеспечить возможность привязки конкретных представлений к «глобальным (вьюмодель на много компонентов)» либо к «локальным»(вьюмодель на екземпляр компонента) хранилищам данных.
— обеспечить возможность внедрения сервисов с логикой бизнеса в такие обьекты через DI.

Локалсторедж позволяет хранить данные, но все остальные требования никак не удовлетворить локалстореджем, т.е. нужно что-то ещё.
Описанный подход предлогает использовать ванильные TS классы (из примера), а уже методы withVM и connect скрывают детали реализации реактивности, привязки к представлениям, материализации.

Обратите внимание на папку с приложением — тут у нас реально живет приложение.
Тогда как реализация вышеупомянутых функций withVM и connect лежит тут. И именно тут лежит код, отвечающий за реализацию реактивность через мобикс или за реализацию внедрения вьюмоделей в дерево компонентов с помощью контекста реакт.

Называть это фреймворком… перебор, же. Возможно количество текста в статье подспудно наделяет структуру кода дополнительной сложностью:)
А может дело в моей личной привычке и восприятии, dunno.

Так а все таки как на хабре принято в случае выкладывания здесь перевода своей же статьи? Маркировать её как перевод или не стоит?
Собственно в комменте выше я описал именно это, давайте в тот тред:)
Поверю конечно. Например подход из этой статьи слабо поможет переписать приложение на вьюжс. И в случае возникновения такой задачи… ваш кейс.

Однако для удовлетворения ответственностей определённых в статье (рендеринг JSX, обеспечение реактивности) — разработчик волен выбирать лучший инструмент из представленных на рынке.

Ну и вы же таки смогли переписать приложение. Это уже победа! Я встрявал в более глубокие жопы. Из недавнего — приложение на бекбон/марионет, где никто из команды не знает почему оно именно такое как есть и что должно делаться. И невозможностью нанять новых разработчиков — люди реально увольнялись когда видели стек. Рекрутёры врали про мифический ангулар в миграция в который продлилась два месяца но не ушла дальше первой странички. (спойлер, лично я фанат бекбон/марионет, на мой взгляд эта связка, вместе с нокаутом, была для ФЕ комьюнити толчком к рич приложениям).

Решением была инкрементальная миграция, когда конкретные регионы приложения избирательно переписывались со всей болью связанной с паралельным наличием билда в двух фреймворках и передачей контроля marionette -> react -> marionette -> react.
Больно, долго, не факт что когда нибудь будет завершено, но по крайней мере новые фичи пиляться и люди нанимаються. Но опять же, я не верю что миграция «когда нибудь» будет завершена. А что если реакт и мобкс выйдут из тренда? :)

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

Не питаю иллюзий о том что смена фреймворка будет ванклик активностью. Но по крайней мере она будет реализуема в измеримое время (недели, месяцы). А не «нам нужно два года, полная спецификация продукта и отдельная команда разработки», что делает саму идею рерайта irrelevant. Ведь активный транк приложения за эти годы уйдёт далеко вперёд.
1

Information

Rating
Does not participate
Registered
Activity