Как стать автором
Обновить
43
0
Роман Теличкин @Telichkin

Ленивый тимлид: https://t.me/lazy_lead

Отправить сообщение

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

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

Спасибо за рекомендацию! Уже не первый раз встречаю положительный отзыв на эту книгу. Надо бы почитать

Учусь маркетингу у лучших представителей айти!

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

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

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

ООП — это про обмен сообщениями, внутренний стейт объектов и позднее связывание. Почитайте или посмотрите про Алана Кея — человека, который придумал ООП.

Самый ООПшный язык — это вообще Erlang, но в нем нет ни классов, ни наследования, даже нет переменных в привычном понимании.

У вас есть положительный опыт использования такого обоснования?

Огонь! Ждал подобной статьи с самого первого дня, как познакомился с Эффектором.

Теперь жду чего-то подобного про FSD. Как раз недавно была статья от ВК о начале использования FSD в каком-то проекте :)

Не путаю. Если грубо, то каждый for, while и if добавляют по единице к цикломатической сложности. У вас программа может зайти в for, а может и не зайти, если не по чему итерироваться. Получается, что for — это тоже своего рода ветвление, которое добавляет дополнительный уникальный путь исполнения в программе.

Цикломатическая сложность не лучше. Вы же делаете map по children внутри flatMap'ы. В итоге получается тот же цикл внутри цикла. Потом, когда нужно пройти фильтром по всем-всем child'ам, вы точно такое же количество раз вызываете проверку на вхождение Child.id в childIds.

Магии не бывает. Как бы не было записано решение, для него в любом случае потребуется вложенный цикл и одно ветвление внутри цикла (на самом деле два ветвления, потому что для пустой группы нужно установить значение 'NoGroup')

Мужики, ну хорош! Проблема же банальная, и код должен быть таким же банальным:

const getChildrenByGroup = (childIds, parents) => {
  const result = {};
  for (const parent of parents) {
    for (const child of parent.Children) {
      if (!childIds.includes(child.Id)) continue;
      
      const groupName = parent.GroupName || 'NoGroup';
      if (!result[groupName]) result[groupName] = [];
      result[groupName].push({ ...child, ParentName: parent.Name });
    }
  }
  return result;
};

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

const getChildrenByGroup = (childIds, parents) => {
  return parents
    .flatMap(p => p.Children.map(c => ({ ...c, ParentName: p.Name, GroupName: p.GroupName || 'NoGroup'))
    .filter(c => childIds.includes(c.Id))
    .reduce((acc, { GroupName, ...child }) => {
      // Не буду упарываться в иммутабельность аккумулятора
      if (!acc[GroupName]) acc[GroupName] = [];
      acc[GroupName].push(child);
      return acc;
    }, {});
};

Если как и вы упороться в редактирование, то можно получить даже не 7 строчек на всю функцию, а всего лишь 5. И это без ненужных импортов и кастомных функций типа pipe и innerJoin:

const getChildrenByGroup = (childIds, parents) => 
  parents
    .flatMap(p => p.Children.map(c => ({ ...c, ParentName: p.Name, GroupName: p.GroupName || 'NoGroup'))
    .filter(c => childIds.includes(c.Id))
    .reduce((acc, { GroupName, ...child }) => ({ ...acc, [GroupName]: [...(acc[GroupName] || []), child] }), {});

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

window.SENTRY.captureEvent -> import('/sentry').then({captureEvent} => ...), если быть точным, я думал это и так понятно.

Тогда можно использовать абсолютно любой подход, ленивая загрузка будет из коробки :)

Вы зачем-то чистый доменный код разбавляете системной логикой (аналитикой) или другим доменом (подсказки, например)

Что такое этот ваш доменный код в разрезе фронтенд-приложений? Если что, синюю книжку Эванса читал, DDD в реальных проектах видел (не понравилось).

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

Нам же во фронте нужно сфетчить данные, трансформировать их в удобные для отображения структуры данных, сгенерить из структур html, мутировать эти структуры в зависимости от действий пользователя, трансформировать в формат, понятный бэку, и отправить по сети. Попытки с умным лицом засовывать в эту цепочку какие-то "сложные" вещи обычно оборачиваются проблемами для проекта. Лучше смириться с тем, что с точки зрения классического программирования мы -- как фронтендеры -- реализуем в основном довольно простые алгоритмы, но у нас есть свои интересные сложности в работе, просто они немного в другой плоскости: реализация удобных кастомных селектов, которые будут работать во всех браузерах; вёрстка страниц, которая на всех разрешениях выглядит хорошо; минимизация потребления ресурсов, чтобы пользователи на обычных компьютерах не страдали.

Что-то я растёкся мыслею, поэтому повторю вопрос: что такое этот ваш доменный код в разрезе фронтенд-приложений?

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

Может и нужен, но точно не в таком простом виде. В эвентах должна быть какая-то практическая изюминка. В событийной модели DOM'а есть всплытия событий, в очередях -- возможность подписываться на события по паттерну типа on('form-*-changed', doSomething). Но когда события добавляются только для создания decoupling'а, потому что это "правильно" и Боб Мартин с Мартином Фаулером про это писали, то от такого паттерна только вред

По ссылке вы частично описали тоже самое, что и в этом комменте, но я всё равно не понимаю.

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

Но у вас же загрузка тяжёлой библиотеки в бандл будет происходить не в файле ~/features/some/analytics.ts, а где-то, где вы пропихиваете SENTRY внутрь window. Получается, в ленивом импорте здесь не будет никакого смысла.

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

// ~/analytics.ts
export function sendMsg(msg: string) {
  window.SENTRY.captureEvent({ message: msg });
}


// ~/some/model-1.ts
import * as analytics from '~/analytics.ts';

export function submit() {
  // Какой-то код сабмита
  analytics.sendMsg('model-1 form submit');
}


// ~/some/model-2.ts
import * as analytics from '~/analytics.ts';

export function submit() {
  // Какой-то код сабмита
  analytics.sendMsg('model-2 form submit');
}


// ~/some/model-n.ts
import * as analytics from '~/analytics.ts';

export function submit() {
  // Какой-то код сабмита
  analytics.sendMsg('model-n form submit');
}

Зачем тут эвенты? Чем вызов функции из модуля отличается от эвента конкретно в этой ситуации? Эвент здесь просто добавит indirection на ровном месте, который при отладке, фиксе багов или добавлении фичей будет только создавать головную боль.

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

Что значит "никак с критичным процессом не должен быть связан"? Подсказки же всё равно будут связаны с процессом, просто неявно. Типа, если явной связи не видно, то связи вообще нет? Но оно так не работает :) Если под отсутствием связи вы понимаете, что код подсказок может падать, и это никак не должно влиять на процесс оформления, то для таких случаев есть try/catch или catch у промисов. Можно писать как-то так

const noop = () => null;

async function createOrder() {
  // Какой-то важный код
  await tips.showSomeTip().catch(noop);
  // Продолжение важного кода
  await tips.showOtherTip().catch(noop);
}

Такой подход простой как палка, поэтому любое изменение фичи, фикс бага, добавление функциональности делается явно. Весь код, который нужен для понимания происходящего в рантайме, можно посмотреть в IDE с помощью Go To Definition. Такой код использует возможности языка и не обязывает читателя знать какие-то специфические библиотеки.

Чем конкретно этот подход хуже эвентов?

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

export const submit = () => {
  // делаем всё необходимое
  analytics.formSubmitted()
}

Чем эта более читаемая запись принципиально хуже decoupled-решения на эвентах?

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

login(() => loginWithTwitter('123'));
login(() => loginWithLocal('bytefer', '666'));

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


А ещё мне кажется, что этот текст писал не человек, а какой-то алгоритм, как это недавно было с новостями на Hacker News

Вы "отваливаете 100500 денег" на пенсию текущих пенсионеров, а не на свою пенсию в будущем. В России солидарная пенсионная система, а не накопительная

Я не разбираюсь в законах РБ, по-этому ваше утверждение "[Во время обыска в компании] Личные вещи – это все что находится в ваших карманах или ваших руках." для меня немного контринтуитивно.


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

Все же кажется, что к ним пришли из-за этого:


Ранее [PandaDoc] начала кампанию по сбору денег для белорусских силовиков, которые решили сменить профессию

Ссылка на результаты кампании тоже есть в посте: https://www.facebook.com/photo/?fbid=10158723155694592&set=a.10151034658869592

Потому что JSON — это модно. Шутка. Может потому что я описал процесс скраббинга данных и его можно повторить. Товарищи могут в них не верить, я не настаиваю.


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

Информация

В рейтинге
6 203-й
Откуда
Санкт-Петербург, Санкт-Петербург и область, Россия
Зарегистрирован
Активность