Доброе утро! Сегодня вторник, а значит поговорим об ООП на фронте.

Пока я заливаю в себя кофе, давайте проведём опрос. Как вы думаете, ООП и фронтенд:
А пока идёт голосование, обсудим, чем ООП плохо и хорошо, а что его не любят и наоборот.

Начнём с хейта 😃
Сразу начну с того, что не каждому проекту ООП нужно.

Иногда гораздо проще написать пару функций с объектами, и никаких солидов не надо. Об этом подробнее в конце 🙂
Во-вторых, насколько я вижу, ООП прочно ассоциируется с мутацией данных, а это сейчас немодно.

Я подозреваю откуда это взялось, хотя и не уверен до конца.
Я согласен, что иммутабельные структуры данных — это круто и надёжно. Но сам лично не считаю мутации чем-то плохим, если они _контролируемы_.

Когда мутации становятся беспорядочными, всё и правда резко становится сложным и запутанным.
Но беспорядочные мутации — это не проблема ООП, это проблема плохо написанного кода 😃

Запутанный код можно написать и не мутируя структуры, если data-flow непонятный.
Для управления мутациями давно уже придумали много способов работы, тот же CQS:
- bespoyasov.ru/blog/commands-…
- en.wikipedia.org/wiki/Command–q…

Command-query separation — подразумевает разделение кода на «запросы» и «команды». Запросы возвращают данные, а команды меняют состояние.
С ним, кстати, вполне реально выстроить как 1-way, так и 2-ways data-flow, он достаточно универсален, чтобы быть удобным в обоих случаях.
Так вот, тот ООП энтерпрайз, который следовал CQS, был вполне себе понятным ¯\_(ツ)_/¯
Так что, возможно, дело не в мутациях.

Ну ок, что есть ещё? А! Куча лишних сущностей!
Обмажутся своим ООП и начинают городить фабрики фабрик провайдеров фабрик провайдеров.
Но опять, это не проблема самого ООП 😃

Да, среди принципов SOLID есть SRP, single responsibility principle. Он говорит, что надо делить обязательства между модулями.
ota-solid.now.sh/srp

Но я видел фабрики фабрик не только в ООП коде.
Может, дело просто в самой его сложности?
Ну… ООП сложный, но не сложнее настоящей™ функциональщины.

Теорию групп я так и не могу сказать, что осилил 😃
github.com/fantasyland/fa…
Хотя я даже работал на проекте, где это использовалось.

Очень оказалось полезно как раз для ООП. Проще стало понимать ковариантность и контравариантность типов.

- ru.wikipedia.org/wiki/Ковариант…
Мне кажется, весь хейт в том, что ООП на фронте с JS просто не удобен.

Использовать его по-настоящему не получается из-за JS. У него многого не хватает, банально — нет интерфейсов.
Но есть TypeScript 🙂

Он всё ещё страдает от JS-райнтайма, но у него уже достаточное API, чтобы писать нормальный™ код.

Мне вот сильно не хватало нормального DI (без декораторов!), пока я не наткнулся на:
- github.com/wessberg/di
Другое дело, что это опять-таки не всем проектам нужно 🙂
Парадигма программирования, как и архитектура, — это инструмент для укрощения сложности. И, как с архитектурой, нам стоит исследовать выгоды и издержки перед применением.
Взять те же принципы SOLID.
ota-solid.now.sh

Мы можем (хоть с оговоркой и не все) использовать их в отрыве от ООП, как инструмент проектирования.

Я так Тяжеловато переписал:
bespoyasov.ru/blog/tzlvt-upg…
В коде Тяжеловато даже классов нет 😃

Я вообще считаю, что ООП — это больше не про классы, а про отношения между сущностями.

Но классы — это всё ещё наиболее удобный инструмент для написания трушного™ ООП.
В простейших случаях можно обойтись и объектом с парой функций.

Но вот то, как эти объекты будут друг с другом взаимодействовать, в каких отношениях они будут находиться, _проще спроектировать_ в терминах ООП ¯\_(ツ)_/¯
Я причём не говорю, что интерфейсы и реализации — это строго лишь ООП, нет.

Просто как их использовать для проектирования и написания кода, описано обширнее всего в книгах, так или иначе связанных с ООП.
— Ладно, вот простой вопрос: можно ли вообще писать в ООП-стиле фронтовый код? Не бекенд, а фронт?

Да. (Но нужен TypeScript 😃)
Есть ошибочное мнение, что фронт — типа ненастоящее программирование.

Но современный фронт сложный ¯\_(ツ)_/¯
А сложное надо проектировать ¯\_(ツ)_/¯

Кому-то это может не нравиться, кто-то хочет, чтобы вся сложность снова ушла на сервера, но сейчас мы имеем что имеем.
Со сложностью надо как-то справляться.

У нас есть фреймворки и библиотеки, это хорошая помощь, но они решают только часть проблем. Проектированием системы всё равно надо заниматься нам самим.
И вот тут ООП может помочь:

- Как разделить обязанности между модулями,
- как использовать композицию,
- что должно быть полиморфным,
- где и какие ставить предусловия,
- что от чего должно зависеть.
Из всего, что я пробовал у ООП наиболее богатый инструментарий и словарь для проектирования систем ¯\_(ツ)_/¯

Круто, если есть или будет что-то ещё более удобное, но я не видел.
Кстати, ООП не запрещает использовать преимущества ФП! 🙂

Мы можем продолжать использовать чистые функции, иммутабельные структуры и вот это всё, даже пиша в ООП-стиле. (Такое слово есть, я проверил 😃)
У Марка Симанна есть отличная статья на эту тему, очень советую:
blog.ploeh.dk/2020/03/02/imp…
Мне в целом воинствующее разделение на ООП / ФП не нравится. Для меня срачи на тему парадигмы выглядят вот так 😅
Но я немного отступил от темы. Можно ли писать нормальный ООП-код на фронте?

Я недавно написал пост о том, как совместить принципы чистой архитектуры, ООП, DDD и всё такое прочее:
bespoyasov.ru/blog/generatin…
Внутри ссылаюсь на офигенную статью @hgraca
herbertograca.com/2017/11/16/exp…
там настолько круто всё разжёвано!

Я перечитываю её разок в месяц-два, чтобы рефрешнуть в памяти, как нормально проектировать.
Для Реакта вон люди тоже придумали стартовые шаблоны:
- github.com/eduardomoroni/…
- github.com/bailabs/react_…
Но опять же, тащить огромную инфраструктуру в небольшой проект я не стану — научен горьким опытом 🙂

Сейчас я стараюсь приносить в проект инструменты по мере их необходимости.
Обычно всё начинается с домена, внутри которого лежит пара функций да типы. Применяю S, O, I, а L и D на полшишечки. Чем сложнее становится управлять, тем больше инструментов буду использовать.
Именно поэтому мы вчера столько времени уделяли проектированию:


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

fuckgrechka.ru/tzlvt/)
Отследить, в какой момент пора наращивать инструментарий мне помогает ощущение «Чё-та сложна».

Иду по индукции с малого, если становится сложно (или там много писать, или повторяться приходится), добавляю инструменты.
Мне ещё иногда кажется, проблема и преимущество JS в том, что «нам не объяснили, как на нём писать правильно».

Поэтому, думаю, и выбор парадигмы — больше вопрос удобства, привычки и вкуса.
Так-с, ухожу работать!
Потом продолжим 🙂
Продолжим 🙂
Чем ООП полезен?

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

Под тестируемостью — возможность удобно подменить зависимость при тесте на стаб или мок.
Пример.

Я как-то писал пост о DI:
bespoyasov.ru/blog/di-ts-in-…

В нём был логгер. Давайте рассмотрим, как он устроен.
В типах я описываю публичный интерфейс:
github.com/bespoyasov/di-…

Интерфейс — это контракт на поведение, он говорит, как с этим модулем можно общаться, что этот модуль _гарантирует_ предоставить как API:
Реализация интерфейса описана классом:
github.com/bespoyasov/di-…

Реализация _инкапсулирует_ в себе детали, которые внешнему миру не важны.

Всем потребителям публичного API по барабану, куда уходит сообщение. Им лишь важно, что они могут дёрнуть метод `log`.
Обратим внимание, что интерфейс называется более абстрактно, чем реализация.

Нам важно сохранять инкапсуляцию и в названии сущности, потому что это снижает зацепление.
ru.wikipedia.org/wiki/Зацеплени…

Чем меньше разные модули знают об устройстве друг друга — тем лучше.
Затем я в DI-контейнере указываю, какой именно класс реализует интерфейс `Logger`:
github.com/bespoyasov/di-…

Таким образом я сбрасываю с себя ответственность за выбор нужной сущности на контейнер — соблюдаю принцип инверсии зависимостей:
ota-solid.vercel.app/dip
В сущности, которой требуется логер, я указываю _интерфейс_ как зависимость:
github.com/bespoyasov/di-…

То есть мне здесь уже не важно, _что_ реализует `Logger`.

Я просто знаю, что есть некая сущность, которая гарантирует метод `log`, который я могу тут использовать.
Это значит, что если я решу заменить консольный логер на какой-то другой, то единственное, что надо будет заменить: реализацию и композицию.
А чтобы протестировать модуль, который использует этот модуль как зависимость, мне надо замокать интерфейс Logger:
Этот мок я «подсуну» в регистрацию при тестировании:
И ничего другого не поменялось!
Если мы ещё не будем забывать о LSP и OCP, то масштабировать будет ещё легче.

- ota-solid.vercel.app/ocp
- ota-solid.vercel.app/lsp
Теперь поговорим о том, чем ООП неудобен 🙂

Расскажите о своём опыте тоже? пробовали ли? что не понравилось?
DI, за который я сейчас топлю, когда-то был для меня непреодолимым барьером 😃

Я помню, пришёл в проект на первом Ангуляре, а там DI.

Вот смотрю в код: вижу, используются какие-то сервисы. А ОТКУДА?! При вызове же ничего такого нет, что за магия?
Поэтому для меня основное неудобство — это в первую очередь порог входа.

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


Я видел проект, где чёрт ногу сломит 😃

Писать в ООП-стиле так, чтобы было _читаемо_ — сложно. (Хотя, наверное, в принципе писать так, чтобы было читаемо — сложно.)
Нужно постоянно лавировать между «кучей сущностей» и «дырявыми абстракциями» :–/

У меня есть подозрение, что навык писать понятно приходит только с опытом и насмотренностью.
А проблема с насмотренностью в том, что не весь код, который мы видим каждый день — понятный.

Мы начинаем думать, что это норма™, когда это на самом деле не так.

Оттуда часто появляется ощущение, что «ООП 💩», «фронтенд 💩», «JS 💩».
Я пока лишь могу порекомендовать читать книжки:
bespoyasov.ru/tag/books/

...и пробовать руками.

Ещё хочется порекомендовать читать исходники, но как-то не могу вспомнить, какие бы проекты произвели хорошее впечатление 🤔

Ну разве что Sentry ничего:
github.com/getsentry/sent…
Дальше, не очень понятно, как это применять к нынешнему фронтенду — React, Vue, вот это всё.

Есть шаблоны:
- github.com/eduardomoroni/…

Там фреймворки на своём почётном месте во внешнем слое.
Это и правда требует больше времени и ресурсов.

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

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

medium.com/devschacht/pol…

Композиция (нет, не наследование!) учит выделять функциональность так, чтобы потом её было удобнее сочетать.

en.wikipedia.org/wiki/Compositi…
В принципах SOLID также заложено разделение сложного на части, а ещё — барьеры на распространение изменений:
ota-solid.vercel.app
Например, SRP и ISP требуют, чтобы модуль занимался только одной задачей.

OCP и LSP ограничивает изменения «коробочкой» модуля, а ещё заранее заставляют думать о том, как код будет меняться.
LSP и DIP обращают внимание на зависимости модулей и их направление.

Всё это — какие-то части проектирования.

У ООП и проектирования похожи терминология и инструментарий, поэтому, мне кажется, принципы проще применять во время рисования квадратиков на бумажке.
А теперь — о том, почему наследование как концепт должно умереть 😃
Наследование классов — это прямой путь к антипаттерну God-Object.

Проблема наследования в том, что будущее нельзя предсказать, и мы не можем заранее спроектировать такую _иерархию_, которая бы отвечала всем требования.
Нам стоит, наоборот, собирать сложное из простого — использовать композицию.

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

Что нам нужно, чтобы описать человека, используя композицию? Надо выстроить иерархию сущностей со своими свойствами:

Животные →
Млекопитающие →
Приматы →
Человек.
Что нам нужно, чтобы описать человека, используя композицию интерфейсов?

Собрать те свойства и методы, которые нам потребуются:

Человек =
Скелет +
Нервная система +
Имунная система +
Сердечно-сосудистая система + ...
Ну окей, пока выглядит одинаково.

...До тех пор пока не приходит задача научить человека летать.

Пусть в нашем приложении появляется Супермен. Он умеет стрелять лазерами из глаз и летать.

Как это впихнуть в иерархию сущностей? 😃
Животные →
Летающие животные?
Летающие приматы?
Суперчеловек?
Человек.

Нипанятна. Нам в какой-то момент придётся создать такой объект, который умеет всё, знает всё, делает слишком много.

en.wikipedia.org/wiki/God_object
В композиции мы добавим дополнительные интерфейсы:

Супергерой = <...Интерфейсы человека> + LaserShooter + Flyable.
Я, кстати, сейчас не говорю об абстрактных классах. Там несколько другое поведение, и используются они иначе. Сейчас речь именно о построении иерархии типов сущностей.
Как защититься от наследования? 😅

- Забыть о слове `extends` (или что там в вашем языке используется 🙂)
- Если есть возможность, использовать `sealed`-классы.

docs.microsoft.com/en-us/dotnet/c…
Кстати, а накидайте, пожалуйста, случаев, когда без наследования никак не обойтись?

Я что-то пытался сейчас вспомнить, не могу найти подобных. (Абстрактные классы и трейты не считаются! 😃)

• • •

Missing some Tweet in this thread? You can try to force a refresh
 

Keep Current with jsunderhood

jsunderhood Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

PDF

Twitter may remove this content at anytime! Save it as PDF for later use!

Try unrolling a thread yourself!

how to unroll video
  1. Follow @ThreadReaderApp to mention us!

  2. From a Twitter thread mention us with a keyword "unroll"
@threadreaderapp unroll

Practice here first or read more on our help page!

More from @jsunderhood

28 Apr
Да ^_^

Если приходится писать тесты для махрового легаси, которое писали до вас, то советую посмотреть на книжку Физерса «Эффективная работа с легаси»:
bespoyasov.ru/blog/working-e…

Чуть подробнее сегодня писал вот тут:
Он предлагает искать швы — места, в которых можно относительно безопасно «распилить» комбайн на части.

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

Представьте, где именно вы бы разделили систему на несколько частей (по смыслу, по поведению, по зависимостям) — там и будет шов.
Read 4 tweets
28 Apr
Копипаста, кстати, не всегда однозначное зло:
bespoyasov.ru/blog/copy-past…
Дублирование кода _на ранних этапах_ может показать, как всё на самом деле работает, и какие паттерны мы ещё не увидели.

Удаление дублирования — это прогнозирование будущего. Чем больше исходных данных удастся собрать, тем точнее будет прогноз.
Чтобы не потерять места с копипастой, помечаю их коммент-флагом `DUPLICATE`.

После самого флага пишу, какую функциональность он дублирует. Это даёт флагу осмысленное и уникальное имя, по которому потом проще искать места для рефакторинга.
Read 7 tweets
28 Apr
А сегодня поговорим о том, как сделать код читаемым и тестируемым ^_^

Расскажите о своих приёмах, как вы улучшаете кодовую базу на проектах? Какие применяете методы, принципы, эвристики?

Я пока начну 🧶
Самое простое (и одновременно сложное 😃) — это нейминг.

Хорошие и внятные имена для переменных и функций — это очень мощный инструмент.

(Первая книга на тему, которую я прочёл — это «Читаемый код» Фаучера:
bespoyasov.ru/blog/the-art-o… )
Хорошее имя для сущности: короткое, но полное и описательное.

На Гитхабе есть классный чеклист по неймингу сущностей:
github.com/kettanaito/nam…
Read 70 tweets
26 Apr
Теперь немного ссылок на Гитхабы!

Есть несколько шаблончиков для Реакта:
- github.com/eduardomoroni/…
- github.com/bailabs/react_…
Есть и без Реакта!
Вот я писал недавно пост об архитектуре, есть репозиторий для с исходниками:
- github.com/bespoyasov/tre…

А есть и с Реактом, и с Next!
Вот сайт недавно переписывал:
- github.com/bespoyasov/www
Структура файлов в двух последних репозиториях не отражает слои, но из поведение — вполне.

Кстати, репозиторий с сайтом ещё и хорошо показывает, как и когда можно остановить глубину проработки.
Read 5 tweets
26 Apr
У меня есть смутное подозрение, что это именно то, что в статье “How I put it all together” называют компонентом.

(Такой кусок гексагонального пирога.)

herbertograca.com/2017/11/16/exp…
То есть там конечно есть особенности, и оно не «точь в точь такое же», но кажется, будто бы идея где-то очень близко.
Кстати!

Структура папок никак не влияет на архитектуру и не отражает её 😃

То есть мы можем поделить приложение на слайсы/компоненты, которые будут содержать в себе код фичи под каждый случай.

Но при этом деплоить, например, по слоям. Вообще без проблем.
Read 4 tweets
26 Apr
Теперь немного о собственно проектировании.

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

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

Чем больше исходных данных мы насобираем, тем больше вероятность, что мы правильно составим «карту территории».
(О соотношении карты и территории: ru.wikipedia.org/wiki/Соотношен…)
Read 22 tweets

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3/month or $30/year) and get exclusive features!

Become Premium

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!

Follow Us on Twitter!