Неделя в @jsunderhood

Так случилось, что я попал вести коллективный разработческий аккаунт в Твитере 😃
Я вёл неделю с 26-го апреля до 2-го мая. На сайте проекта можно глянуть все твиты в хронологическом порядке, а в этом посте я вытащил самые на мой взгляд важные и полезные.

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

Понедельник: об архитектуре

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

Архитектура и фронтенд

Когда я начал постить в блоге о фронтенде конспекты книг и статьи об архитектуре, мне стали прилетать вопросы типа «А кому и нафига это вообще надо?».

Я отчасти понимаю природу этих вопросов. Кажется, что архитектура это что-то далёкое от фронтенда: мы же просто формочки шлёпаем да кнопочки двигаем. А все эти Мартины и Физерсы как-то уж очень сильно переусложняют.

Вот нафига мне выделять «слои» в приложении, если всё моё приложение — это небольшое PWA с парой кнопочек? Есть аргументы вида «будет проще переехать с React на что-то ещё» — но я не собираюсь переезжать с React, зачем мне тогда адаптеры для него?

Архитектура — это инструмент

Я предлагаю начать с того, что архитектура — это прежде всего инструмент. У любого инструмента есть область применения и ограничения.

Я, пожалуй, не стану покупать шуруповёрт, чтобы вкрутить один саморез. Но если саморезов 1000, то я уже подумаю: потратить 5 тысяч на шуруповёрт или лечить в будущем артрит кисти за бóльшие деньги 😃

Архитектура, как и шуруповёрт, стоит ресурсов. Поддержка сложного проекта с лапше-кодом, как и артрит, — тоже стоит ресурсов, и тоже, как правило, больше

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

Код без архитектуры

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

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

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

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

Издержки должны быть меньше выгоды

Теперь контр-пример: прототип приложения на React. Надо быстро, поддерживать будет, скорее всего, не нужно. А если и нужно — то всё равно переписывать, потому что дизайн будет другой, UX поменяется.

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

И вот мой первый тогдашний вывод:

Издержки должны быть меньше выгоды

После того проекта я решил порефлексировать на него. Что бы произошло, если бы всё-таки прототип пришлось переписать.

  • Сколько кода я бы мог переиспользовать?
  • Какой код надо было бы переиспользовать?

Домен

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

Стоит начать с домена

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

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

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

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

Другие слои

С доменом разобрались, но зачем остальные слои, они вообще нужны? Короткий ответ: не всегда.

Когда я думал, что «используя слой адаптеров, проще съехать с React», я отвечал себе, что я и не собирался съезжать с React. И это правда, перебраться с него на какой-то другой шаблонизатор сложно. Но что, если я заменю “React” на “Redux”?

Заменить стейт-менеджер обычно — затратное мероприятие. Он затрагивает много кода: хранилище, события, привязка к UI. Вместе со всем этим кодом надо и тесты переписывать — а это ещё раза в два больше работы.

С адаптером для стейт-менеджера переезд попроще 🙂
Слой адаптеров — это барьер, который говорит, где заканчивается сторонний код и начинается наш.

Адаптеры и порты

Адаптеры и порты делят внешний мир от нашего приложения, как мембрана клетки отделяет её от окружающей среды.

Они как бы ограничивают распространение изменений. Мы пишем такие «переходники», которые делают внешний мир более удобным для нашего приложения. Из-за этого и API приложения меняется редко. Адаптеры же можно написать для любой сущности, с которой приложение хочет взаимодействовать.

Это ещё и ограничивает распространение ошибок. Об этом писал Ганмер в «Паттернах отказоустойчивых приложений». Кроме ошибок, это помогает рефакторить код. Разделение по слоям — идеальный «шов», как называет его Физерс в «Эффективной работе с легаси».

Проектирование

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

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

  • Какие задачи перед нами стоят.
  • Какое решение считается удовлетворительным.
  • Какие есть ограничения.

Чего мы хотим добиться:

  • Доменный слой должен быть самостоятельным и независимым.
  • Адаптеры должны подстраивать внешние сервисы под нужды нашего приложения, а не наоборот.
  • Кода должно быть минимальное необходимое количество.
  • Модель системы должна быть максимально простой.
  • Направление зависимостей должно быть к домену.

При дизайне систем надо подубавить динамик перфекционизма, потому что перфекционизм мешает. Невозможно проработать систему на 100%. Всегда останется что-нибудь, что можно улучшить. Это стоит иметь в виду, когда мы взвешиваем выгоды и издержки.

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

Когда примерно картинка складывается, я раскидываю модули на слои и проверяю:

  • Всё ли нормально с направлением зависимостей;
  • Следую ли я принципам SOLID;
  • Что будет, если захочу заменить реализацию какого-то модуля на другую;
  • Сколько понадобится ресурсов, чтобы поменять платформу, инфраструктуру, whatever.

Диаграмма помогает не писать сразу код, а сперва приметиться на бумаге — «а оно вообще поедет?». Иногда диаграммы помогают обнаружить ошибки проектирования:

  • Функциональность засунули не туда;
  • Модули слишком сильно зацеплены;
  • Нужна шина событий, чтобы связать какие-то сущности;
  • Нарушаем SRP, DIP, ISP и прочее.

Если паровоз на бумаге поедет, то можно думать в сторону кода.

Взаимодействие модулей

Спроектируем взаимодействие на примере онлайн-магазина печенек 🍪

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

В доменный слой я бы вынес следующее:

  • печеньки и пользователя;
  • функцию для подсчёта скидки на печеньку по промокодам;
  • функции для определения, подходит ли печенька по вкусовым предпочтениям.

Дальше, слой адаптеров и портов. Тут:

  • UI, рендер страниц и компонентов, обработка событий;
  • общение с сервером и API;
  • сохранение в localStorage и прочее.

Задача адаптера — сделать интерфейс стороннего сервиса совместимым с тем, который хочет наше приложение. Это значит, что если API отдаёт данные в виде snake_case, а мы хотим camelCase, заниматься этим будут адаптеры.

Порт — спецификация, как сторонний сервис может общаться с нашим приложением, или как наше приложение хочет, чтобы с ним общались сторонние сервисы.

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

  • работу со списком товаров и корзиной,
  • сценарии покупки.

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

Вторник: ООП на фронтенде

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

Результаты опроса: 27% считает, что ООП и фронт скорее хорошие друзья
Результаты опроса: 27% считает, что ООП и фронт скорее хорошие друзья

Почему ООП хейтят

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

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

Но беспорядочные мутации — это не проблема ООП, это проблема плохо написанного кода. Запутанный код можно написать и не мутируя структуры, если data-flow непонятный.

Для управления мутациями давно уже придумали много способов работы, тот же CQS, command-query separation. Он подразумевает разделение кода на «запросы» и «команды». Запросы возвращают данные, а команды меняют состояние.

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

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

TypeScript

Мне кажется, весь хейт в том, что ООП на фронте с JS просто не удобен. Использовать его по-настоящему не получается из-за JS. В нём многого не хватает, банально — нет интерфейсов.

Но есть TypeScript. Он всё ещё страдает от JS-райнтайма, но у него уже достаточное API, чтобы писать нормальный™ код. Мне вот сильно не хватало нормального DI, пока я не наткнулся на: @wessberg/DI.

Парадигма — это тоже инструмент

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

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

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

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

Справляться со сложностью

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

Со сложностью надо как-то справляться. У нас есть фреймворки и библиотеки, но они решают только часть проблем. Проектированием системы всё равно надо заниматься нам самим.

Из всего, что я пробовал у ООП наиболее богатый инструментарий и словарь для проектирования систем:

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

Кстати, ООП не запрещает использовать преимущества ФП! Мы можем продолжать использовать чистые функции, иммутабельные структуры и вот это всё, даже пиша в ООП-стиле.

Для меня срачи на тему парадигмы выглядят как этот мем
Для меня срачи на тему парадигмы выглядят как этот мем

Как применять ООП

Я недавно написал пост о том, как совместить принципы чистой архитектуры, ООП, DDD и всё такое прочее. Для React вон люди тоже придумали стартовые шаблоны.

Чем ООП полезен

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

Проблема «насмотренности»

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

А проблема с насмотренностью в том, что не весь код, который мы видим каждый день — понятный. Мы начинаем думать, что это норма™, когда это на самом деле не так. Оттуда часто появляется ощущение, что «ООП 💩», «фронтенд 💩», «JS 💩».

Как ООП помогает в проектировании

ООП в своих принципах подразумевает деление сложного на части.

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

В принципах SOLID также заложено разделение сложного на части, а ещё — барьеры на распространение изменений.

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

Наследование как концепт должно умереть

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

Нам стоит, наоборот, собирать сложное из простого — использовать композицию

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

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

Животные → Млекопитающие → Приматы → Человек.

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

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

Ну окей, пока выглядит одинаково. …До тех пор пока не приходит задача научить человека летать.

Пусть в нашем приложении появляется Супермен. Он умеет стрелять лазерами из глаз и летать. Как это впихнуть в иерархию сущностей?

Животные → (Летающие животные? Летающие приматы? Суперчеловек?) → Человек.

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

Если же стараться не нарушить LSP и OCP, то нам в какой-то момент придётся создать такой объект, который умеет всё, знает всё, делает слишком много. В композиции же мы добавим дополнительные интерфейсы:

Супергерой = <...Интерфейсы человека> + LaserShooter + Flyable.

Как защититься от наследования:

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

Среда: как сделать код читаемым и тестируемым

В среду я рассказал о приёмах и эвристиках, которые использую, чтобы делать код чище и удобнее для тестирования.

Как сделать код читаемым

Самое простое и одновременно сложное — это нейминг. Хорошие и внятные имена для переменных и функций — это очень мощный инструмент. Первая книга на тему, которую я прочёл — это «Читаемый код» Фаучера.

Я люблю проверять имена всех экспортируемых сущностей на понятность со стороны пользователя. «Если я буду импортировать эту функцию из модуля, я пойму, что она делает? какова область её ответственности? как её использовать?»

Хорошее имя для сущности: короткое, но полное и описательное. На Гитхабе есть классный чеклист по неймингу сущностей. В этом чеклисте есть шаблон A/HC/LC:

prefix? + action (A) + high context (HC) + low context? (LC)

В идеале по названию переменной должно быть понятно, функция это, булево значение или что-то ещё.

  • Для булевых значений можно использовать префиксы: should, is, has, will, did.
  • Для функций — первым словом лучше поставить глагол действия: get, set, update, remove, delete, push.

Иногда (редко) от шаблона можно отойти, если двусмысленность получается исключить: let mounted = false — тут сложно подумать, что mounted это что-то кроме boolean.

Не используйте аббревиатуры, пожалуйста 😃
А если используете, обязательно документируйте.

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

Паттерны проектирования

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

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

Паттерны удобно использовать в связке с принципами SOLID. Некоторые из принципов прямо подразумевают какой-то из паттернов. Мы с Артёмом Самофаловым в нашей книжке о принципах SOLID добавляли разделы с паттернами под каждый принцип.

Рефактироить без фанатизма

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

А ещё (самое сложное для меня) надо купировать перфекционизм

Отрефакторить до идеала сложно, а чаще всего не нужно. Пользуемся правилом 20/80 — 20% усилий должны приносить 80% результата.

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

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

В книжке много техник, как работать с кодом, когда вы уже определились со швом. Типа, как заменить зависимость:

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

Другие приёмы улучшения читаемости

Ещё один приём, который улучшает читаемость — CQS, command-query separation. Запрос — функция, которая возвращает результат и не имеет сайд-эффектов. Команда — функция, которая меняет состояние системы и ничего не возвращает.

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

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

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

Полезные книги

Раз уж мы о книгах заговорили, то ещё «Чистый код» Мартина могу посоветовать. Хотя и считаю, что «Эффективная работа с легаси» шире, глубже и практичнее ¯\_(ツ)_/¯

Ещё офигенная и практичная книга — “Debug it!” («Отдебажь это!», простите за кустарный перевод). Она вся состоит из рецептов, как работать с багами из-за непонятного кода.

Там даже содержание — это уже рецепт! 😃

  • Глава 1 — Исследовать обстановку
  • Глава 2 — Воспроизвести проблему
  • 3 — Определить причину
  • 4 — Исправить
  • 5 — Как не допустить такой ошибки в будущем

Внутри каждой главы есть списки действий под ситуацию. Короче, рекомендую.

Как сделать код тестируемым

Используйте TDD

TDD экономит мне кучу времени. К нему надо привыкнуть, потому что сперва приходится «вывернуть мозги», но он быстро окупается. С ним:

  • Исчезает проблема «дополнительной работы»
  • Писать тесты и рефакторить входит в привычку
  • Рефакторить безопаснее
  • Видно сущности, делающие слишком много
  • API проектируется до реализации, и-за чего становится удобнее

О TDD

Я недавно делал доклад о TDD на Frontend-crew:

Там рассказываю об этом подробнее:

  • как внедрить на проекте;
  • как использовать;
  • как сделать тесты проще.

Как упростить тесты при работе по TDD:

  • Чаще использовать чистые функции
  • Обращать внимание на зацепление кода
  • Тестировать только свой код
  • Использовать удобные инструменты
  • Потратить время на удобную инфраструктуру

TDD можно использовать и при работе с React тоже. Недавно я проводил воркшоп об использовании TDD при разработке React-приложений. Он длинный, около 5 часов, но там я прохожусь по всем основным концепциям, а именно, как тестировать:

  • Функций бизнес-логики.
  • Функции, возвращающие случайные значения.
  • Простые компоненты.
  • Кастомные хуки, их связь с компонентами.
  • Работу со стором.
  • Асинхронные функции и вызовы API.
  • Пользовательские действия: клик, клавиатура.

Чем хорош TDD

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

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

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

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

А ещё TDD — это единственный способ безопасно (или даже вообще хоть как-то) отрефакторить легаси 😃

Другие способы улучшить тестируемость

Кроме TDD тестируемость улучшит Dependency Injection. Вместо того, чтобы мокать всё подряд, можно использовать DI, и подмешивать во время тестов нужные зависимости.

DI — это не обязательно контейнеры и всё такое страшное, можно использовать кустарный DI через объект с зависимостями в конце. Об этом я тоже недавно писал пост.

Копипаста — не всегда однозначное зло

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

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

А вот старый и закомментированный код — действительно зло. Он отвлекает, а иногда и вводит в заблуждение.

Четверг: как эффективнее расти в разработке

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

Боевая разработка набрала 66% голосов и безоговорочно победила в первом туре
Боевая разработка набрала 66% голосов и безоговорочно победила в первом туре

Плюсы и минусы университета

Из плюсов:

  • Нетворкинг (именно то, чем я не занимался в университете 😅)
  • Могут научить действительно полезным вещам, если попасть в правильный ВУЗ и на правильную специальность. Под правильными я тут понимаю такие, которые совпадают с вашими интересами.

Из минусов:

  • Образовательные программы по большей части не успевают за рынком
  • Офлайн-лекции
  • Одна единственная специальность.

Плюсы и минусы боевой разработки

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

Другой плюс боевой разработки: нет романтического флёра IT, который иногда продают курсы. Изнутри можно посмотреть на «настоящий код™», на компромиссы, как вообще бизнес и разработка связаны, откуда требования берутся и вот это всё. Минус — это может отпугнуть, если попасть не туда.

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

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

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

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

О менторстве

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

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

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

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

Особенно полезен ментор, когда мы подходим к более философским задачам:

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

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

Пятница: зачем и как вести блог

Под конец недели я решил обсудить ведение технического и не только блога.

Зачем вести блог

1. Блог помогает бороться с внутренним самозванцем.
Чтобы проверить гипотезу, что я «ничего не знаю и не умею», надо попробовать её опровергнуть. Статья, твит, пост — всё это собирает фидбек, из которого можно составить чуть более объективную картину мира.

Бывает, правда, кажется, будто писать не о чем. «Все обо всём уже давно написали, и смысла повторяться нет.» Пишите всё равно!

  • Не все читали то, что читали вы, кому-то это будет полезно.
  • У вас могут быть детали, которых не было в других постах.
  • Ссылайтесь на других, чтобы читатели поняли тему ещё лучше.

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

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

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

4. Это работает почти как Гитхаб!
Блог часто приносит мне хорошие офферы и освобождает меня от собесов ¯\_(ツ)_/¯

5. Это хороший тренажёр для формулировки мыслей.
Я заметил, что после какого-то времени ведения блога, у меня получается на письме хорошо формулировать мысли и задачи. С задачами вообще огонь, потому что хорошая формулировка проблемы — половина решения.

Как вытащить из себя статью

Есть три принципа, которыми я пользуюсь, чтобы избегать ступора, прокрастинации и разочарования:

  • «Есть слона по частям»
  • «Ну блин короче…»
  • «Редактировать — отдельно»

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

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

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

Причём:

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

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

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

При редактировании я обычно сперва проверяю логику повествования:

  • логично ли одно предложение перетекает в другое?
  • движется мысль или стоит на месте?
  • как связан один абзац со вторым?

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

Когда с логикой всё хорошо, можно углубляться в сам текст:

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

В самом конце можно приступать к пунктуации, орфографии и грамматике.

Как вставлять примеры кода

Здесь нам одновременно надо решить почти противоречащие задачи:

  • Кода должно быть не слишком много, чтобы было проще читать.
  • Но кода должно быть достаточно много, чтобы передать контекст проблемы.

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

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

Впечатления

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

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

В общем, если вам есть о чём рассказать и есть возможность попробовать провести неделю — советую.

Ссылки

В этот раз все-все ссылки очень удобно собрались на сайте проекта. Их там 2–3 экрана, так что я очень рад, что мне не пришлось их дублировать тут 😅