Обновить
4

Пользователь

6
Подписчики
Отправить сообщение

Нет, вам по сути сказали, что есть иллюзия понимания, а есть натурально понимание. Если закралась ошибка - это не означает, что мы имеем дело с иллюзией понимания. Это разные процессы.

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

По идее, это один из кейсов, который должен иметь свой тест. Поэтому в коде это комментировать не нужно. Будет соответствующий тест с человекопонятным описанием.

Причем, даже если все это прописать для нейронки, в тестах все равно все это нужно повоторить

TDD через плагин Superpowers. Есть плагин Superpowers, который заставляет Claude работать по TDD-циклу: сначала пишет один тест, запускает - убеждается что красный, потом пишет минимальную реализацию, снова запускает - зелёный. Следующий тест. По одному. Если Claude пытается написать код до теста - плагин буквально удаляет его и заставляет начать с теста. Жёстко, но работает. Тест-тавтология исчезает, потому что тест пишется до реализации и проверяет намерение, а не обратную совместимость AI-кода с самим собой. И вот что я не ожидал: наблюдая red→green в терминале, я получаю уверенность без чтения каждой строки. Тест был красным. Стал зелёным. Claude его не менял. Значит, реализация соответствует спеке.

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

Из текста следует, что заменили шило на мыло.

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

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

Как был какой-то беззубый фетишизм в анализе готового кода так он никуда и не делся.

И тут щёлкнуло: comprehension debt существовал задолго до AI. Просто раньше мы писали мало и медленно, и не замечали. AI увеличил объём - и долг стал виден. Половина моего легаси - тоже код без автора. Автор был. Он забыл.

Всегда замечали. Эта проблема еще в середине 70 встала, когда начали писать большие программные системы. И на протяжении всего времени эту проблему всегда держали в голове. Наличие такой проблемы в разработке - это один из классических признаков производства говнокода.

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

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

Зависимости могут быть разнообразными, но сути это не меняет. В адресной строке всегда должен быть url, т.е. указатель на ресурс. Пользовательские и служебные данные этого положения не меняют.

Попробую дать свою обратную связь.

Сегодня этот слой работы — написание кода — схлопывается до 5-10%.

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

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

Вот эти вот:

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

Занимают львиную долю только на определенном уровне развития. Пока ты не достаточно хорошо знаешь синтаксись, недостаточно хорошо понимаешь паттерны. А бойлерплейт просто по определению вещь из разряда копировать-вставить.

Вы говорите вот такую штуку:

Нейросети отлично справляются с реализацией, освобождая нас для высокоуровневого «Как?»: проектирования архитектуры и выстраивания связей.

И далее сразу же идет:

Теперь наша работа — это не укладка кирпичей, а создание чертежей.

А разве "проектирования архитектуры и выстраивания связей." - это не создание чертежей?

А вот это прям даже как то поразило:

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

Дело в том, что еще с середины 70х писали хорошие книги ровно вот про это. В конце 90х при формализации юниверсального языка моделирования все это было наглядно структурировано в выразительную систему. И вот в конце 2025 года мы читаем: "Мы увидели, как неверный подход к постановке задачи и отсутствие..."

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

ИИ здесь не помощник, потому что если бы мы спросили его "как сделать админку", он бы просто сделал админку, не задавая вопросов "а зачем?".

На самом деле, ИИ везде может быть помощником. Просто нужно понимать о какой работе идет речь. Если разделить разработку ПО на анализ-проектирование-кодирование-тестирование-деплой то на каждом этапе будут свои типы задач. Например, нет никакого смысла спрашивать "как сделать админку?", если до этого не задать вопрос: "что я должен сделать?". Этот вопрос вы так же можете задавать ИИ. Собственно выше вы к этому и пришли. Правда я не понял, использовали вы ИИ или нет. Видимо нет, раз пишете, что ИИ не помощник.

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

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

И только теперь, когда у нас есть выверенное ТЗ...

Либо у вас с глоссарием проблемы, либо со смыслом. ТЗ - это первичный документ проекта. А на данном этапе у вас уже есть спецификация, т.е. вторичный документ, который описывает конкретные характеристики приложения.

В таком режиме ИИ выступает как идеальный Junior+/Middle, который пишет код строго по гайдлайнам, не отвлекаясь на самодеятельность.

Гайдлайны - это наборы ориентиров для получения единого стиля, политики. Это не ТЗ или спецификация, поэтому никакие строгие гайдлайны не исключают самодеятельность. Они только влияют на ее форму. Скорее всего опять вопрос к выбранному вами глоссарию.

 Это позволяет держать контроль над кодовой базой и избегать "спагетти-кода", который так любят генерировать нейросети, если их не ограничивать.

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

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

Тут вы поднимаете другой вопрос. А можно ли продать неработающего монстра, но дешевого? И как мы можем видеть на примере огромного количества некачественного ПО на рынке ответ: "Да можем". Вы, судя по всему, в этом не заинтересованы. Но с другой стороны, а не изменится ли это в будущем, когда конкуренты начнут использовав ИИ делать все быстрее, дешевле и возможности маневрировать будет еще меньше чем сейчас? То, что там 30-40% будет не работать? Ну так и что. Вон, была такая игра как Киберпанк за много много миллионов зеленых рублей. Так там на выходе не работало до 30% всех навыков прокачки. И это далеко не единичный случай супердорогово ПО очень и очень низкого качества.

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

Тут сразу возникает вопрос, а выделенного аналитика у вас нет? Или это дорого?

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

Поэтому первым делом и нужно поработать с ИИ, что бы выяснить, что нужно делать. А уже потом просить это реализовать. Паттерны, микросервисы и идемпотентность ИИ сможет расжевать так же как и все остальное. Тут вопрос в другом, позволит ли бизнес разработчику тратить время на вот эту самую "насмотренность". Потому именно в процессе работы разработчик ее и получает. Но вот если бизнес ставит в рамки, когда на это нет времени, то и насмотренности взяться будет не откуда. Зато появятся условия для появления той самой магии.

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

Во первых, так было всегда. Уже давным давно при ревью никто не ищет запятые. Во вторых, вот это вот "становится критическим" по сути сводит на нет плюсы от использования ИИ. Потому что, если человек должен через себя пропускать все детали и брать за них ответственность, то это ровно то чем занимался разработчик и без всяукого ИИ. Откуда тогда возьмется 3-10 краное ускорение то? Если разработчик все так же отвечает за каждую строчку кода?

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

Тут хочется спросить, а вы точно в курсе, что данное положение на рынке было последние 40 лет? Когда требовали "быстро писать код" как раз и понимали "быстро написать код, который нужно написать"

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

Это просто белый пушной зверёк какой-то. Честно, слов нет.

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

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

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

Мне почему-то сразу представилась ситуация, когда небольшой инвестор хочет за копейки грамотных исполнителей, и грамотные исполнители, которые за копейки работать не хотят. Вот и получается, что "интересы" в принципе не пересекаются XD

А что за мысль в шпаргалке, где в интерфейсе Person поле name является readonly?

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

Но, по моему, зря начинать ее с таких пассажей и прививать нездоровое отношение к any:

Тип any в TypeScript по своей сути является злом и небезопасной особенностью встроенной по умолчанию в систему типов языка.

Да не является any никаким злом по сути. Именно по сути, это полезный и необходимый элемент языка. Лучше бы в ведение добавить цитату из документации:

Don’t use any as a type unless you are in the process of migrating a JavaScript project to TypeScript. The compiler effectively treats any as “please turn off type checking for this thing”. It is similar to putting an @ts-ignore comment around every usage of the variable.

Иными словами, any это никакой не тип как все остальные, а специальный тип, потому что это элемент языка, который эффективно рассматривается компилятором как отключение типизации на месте использования.

По моему, если с этого начинать, то далее по тексту все будет восприниматься куда более ясно.

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

Попробую дать некоторую обратную связь.

Хорошо: Использование enum.

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

Рассмотрим пример:

Скрытый текст
enum Genre {
  Romantic = 'romantic',
  Drama = 'drama',
  Comedy = 'comedy',
  Documentary = 'documentary',
}

class Projector {
  configureFilm(genre: Genre) {
    switch (genre) {
      case Genre.Romantic:
        // Логика для романтического фильма
        break;
    }
  }
}

const projector = new Projector

projector.configureFilm(Genre.Comedy);

А теперь навалим жира, что бы Genre так же использовалась в свойстве объекта

type UsefullData = {
  value: Genre
}

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

const getRomanticDataMock = () => ({
  value: 'romantic'
}) satisfies UsefullData

Тут будет ошибка:

Type '"romantic"' is not assignable to type 'Genre'.(2322)

И если таких свойств много придется тащить зависимости от Genre

const getRomanticDataMock = () => ({
  value: Genre.Romantic
}) satisfies UsefullData

projector.configureFilm(getRomanticDataMock().value)

А вот если отказаться от enum такой ситуации легко избежать.

Скрытый текст
const Genre = {
  Romantic: 'romantic',
  Drama: 'drama',
  Comedy: 'comedy',
  Documentary: 'documentary',
} as const

type Genres = (typeof Genre)[keyof typeof Genre]

class Projector {
  configureFilm(genre: Genres) {
    switch (genre) {
      case Genre.Romantic:
        // Логика для романтического фильма
        break;
    }
  }
}

const projector = new Projector

projector.configureFilm(Genre.Comedy);

type UsefullData = {
  value: Genres
}

const getRomanticDataMock = () => ({
  value: 'romantic'
}) satisfies UsefullData

projector.configureFilm(getRomanticDataMock().value)

Что изменилось: Мы избавились от изменяемого состояния (let totalOutput). Код стал короче, выразительнее и сфокусирован на что мы хотим сделать (посчитать сумму), а не на как это сделать (инициализировать счетчик, перебрать индексы, прибавить значение).

По моему, тут вы перемудрили.

Вам же ничто не мешает сделать так:

const contributions = [{ linesOfCode: 100 }];
let totalOutput = 0;

for (const contribution of contributions) {
  totalOutput += contribution.linesOfCode;
}

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

Хорошо: Использование readonly для защиты от изменений.

Это конечно хорошо, но делать сразу все readonly не всегда удобно. Например, вам ничто не мешает сделать так:

interface Config {
  host: string;
  port: string;
  db: string;
}

const config = {
  host: 'myhost',
  port: '100',
  db: 'mydb'
} as const satisfies Config

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

Используйте interface, если вы хотите использовать классическое ООП с extends или implements. Интерфейсы лучше работают с ошибками в IDE.

Вот тут не очень понял мысль. Вам ничто не мешает с extends или implements использовать объектный type. Если ваши данные - это всегда какой-то объект, то совершенно спокойно можно его описывать и через type, и через interface. Все различия начнут появляться только в специфических случаях, т.е. когда мы хотим расширить объектный тип определенный в другом месте. Вот тогда придется использовать interface, просто потому что type на это не способен.

Очень большое количество воды в статье. Не меньше 80% можно просто выкинуть и вообще ничего не поменяется.

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

Допустим, что все это правда, хотя дискуссионных моментов, очень много, и остается вопрос, а бизнес к этому готов?

Ну, вот например:

Исторически развитие шло от низкоуровневого ассемблера к C и C++, затем к Java, потом к Python и JavaScript — всё выше и выше по уровню. Сегодня уже не важно, знаешь ты Python или Go: достаточно задать задачу нейронке, и она напишет код на нужном языке. Это меняет правила.

Да, это действительно меняет правила, если серьезно так не вычитывать все что нейронка накодила. Иными словами, если действительно "пользоваться" тем, что Вы не знаете тот язык на котором создается ПО.

И вот вопрос, бизнес готов переложить ответственность с человека на ИИ? Это о каком бизнесе тогда идет речь?

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

Сегодня знание конкретного фреймворка не ценность, а опыт. Причём опыт не только технический, но и продуктовый. Умение видеть архитектуру, понимать бизнес-контекст, представлять, как фича встроится в продукт — вот что имеет значение.

А раньше как было? Уже лет 15, т.е. еще до ИИ огромная часть работы, бОльшая часть работы была про умение выстраивать архитектуру, понимать бизнес-контекст, представлять что за фича сейчас в работе и т.д. и т.п.

Мидлом разработчик становился не когда:

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

А когда он и Реакт и его экосистему мог использовать для эффективного решения поставленной задачи и, скорее всего, в команде. И брал на себя ответственность за это решение.

На первый план выходит база: На первый план выходит база: computer science, системное мышление, продуктовое видение, бизнес-понимание. Всё остальное — языки, библиотеки — может генерироваться автоматически. Но если ты не понимаешь, что именно строишь, ни одна нейросеть не спасёт., системное мышление, продуктовое видение, бизнес-понимание. Всё остальное — языки, библиотеки — может генерироваться автоматически. Но если ты не понимаешь, что именно строишь, ни одна нейросеть не спасёт.

А разве "computer science" просто по определению не включает в себя вот это: "языки, библиотеки" - что бы это ни значило?

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

Вот такие "посылы" меня, прям, озадачивают. Фактически, что бы решить задачу разработки ПО нужно быть разработчиком ПО.

И вот дальше, интересные моменты идут.

Так называемые «фреймворк-программисты» вместо того, чтобы быть инженерами, называют себя «React-разработчик» или «Vue-разработчик».

А тут точно не поменялись причина и следствие? Ну, потому что сколько я не открываю всякие хэдхантеры там в первую очередь бизнес определеяет того, кто им нужен в данный момент. Нужем им реакт разработчик с 5-ю годами опыта? Они будут именно такого и искать.

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

Для меня это кринж! React или любой другой фреймворк — это просто инструмент. Большинство рынка устроено именно так: «я выучил React — я специалист», но это ложный путь. Если человек не меняет подход, он теряет конкурентоспособность. Вы — фронтенд-разработчик, software engineer, который решает задачу формирования UI. И вот здесь мы возвращаемся к тому, что действительно важно.

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

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

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

Поэтому снова вопрос, а рынок готов подстраиваться под новые тендениции или для рынка ИИ - это очередная вундерфафля, или инструмент на который они смотрят как на тот же Реакт с экосистемой?

А вот новые продукты — стартапы, корпоративные сайты, сервисы — скорее всего будут изначально AI-based. Для небольших команд это очевидная экономия: вместо пяти фронтендеров можно будет иметь одного сеньора, который управляет процессом и выстраивает систему, а сам код генерирует ИИ.

Вот эту вот мантру слышу на протяжении последних 20 лет. Это вот о чем? 5 сеньеров заменят одним сеньором? 5 мидлов заменят одним сеньором? О какой конфигурации идет речь?

Почему и вот эта и большая часть материала какие-то соевые и воспринимаются скорее как пиар ИИ? Почему нет отсылок к каким-нибудь AI-based проектам, которые уже доказали высокое качество при меньших затратах? Или просто высокое качество?

Почему за каким-нибудь ИИ-based сервисом стоит сильная команда, которая и без ИИ выдавала отличный результат?

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

И снова вопрос, а бизнес позволит встречать все это с любопытством и позитивом?

Выгляидит подобное как какая-то соевая мура.

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

И поэтому возникает вопрос, а почему именно сейчас возникает подобное: "Я вижу, что часть аудитории реагирует на это жёстким хейтом: «технологии всё испортили, карьера рушится»."?

А не является ли причиной, что бизнес так слепенько уверовал в пиар типа:

"Сегодня уже не важно, знаешь ты Python или Go: достаточно задать задачу нейронке, и она напишет код на нужном языке."

"Я недавно записал курс по вайб-кодингу. Суть в том, что нейронка может написать код «по вайбу», то есть по вашему описанию. И да, она это делает без проблем."

Или начинаются какие-то унизительные пассажи типа:

быть инженером или уйти в историю как «человек, который красил кнопки»

И при этом забывает, что на самом деле правила не поменялись и до сих пор на первом месте:

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

На первый план выходит база: computer science, системное мышление, продуктовое видение, бизнес-понимание.

Как и 15 лет до этого!

Может кто-то просто неправильно пиарит ИИ? И не нужно кричать на каждом углу, что ИИ всех заменит, потому что правила вовсе не поменялись. Появился инструмент, который совсем не панацея и для того, что бы его эффективно использовать нужно все так же быть профессиональным разработчиком. Или, На Самом Деле, уже все таки не нужно?

Почему крайними ВДРУГ стали действительно любознательные разработчики разного уровня? А то создается именно такое впечатление, что это ИИ поставщики молодцы, и только какие-то тупые разработчики не хотят с радостью встречать прогресс. Вон же как здорово, один может 5х заменить и ничего знать при этом не надо про "язык, библиотеки".

По Dependency Inversion верхнеуровневый модуль здесь это react-router

По идее, верхнеуровневый модуль - это тот который использует react-router и находясь на самом верху имеет связи с нижестоящими модулями. Поэтому внутри router наоборот самый высокоуровневый код. В данном конкретном примере из статьи.

Честно говоря, яснее не стало.

В статье речь про принципы SOLID и Clean Architecture, а они как раз нарушаются.

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

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

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

Это не посредник, а сервис-локатор. Кроме того, это нарушает OCP.

Что такое "сервис-локатор"? Какой у него шаблон? Почему нарушается OCP? Иными словами, вот у вас есть задача создать не переиспользуемый модуль. Шаблона у вас нет. Почему тогда нарушается OCP? Дальше вы так же будете создавать не переиспользуемые модули.

Эти объекты слабо связаны друг с другом, и очень жестко с роутером.

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

Это не так. Чтобы протестировать этот компонент с react-router, нужно смоделировать весь стек роутинга: RouterProvider, createBrowserRouter и т.д. Если ваш компонент использует useMatch, например, то без провайдера он просто упадет с ошибкой.

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

В некотором смысле да, но любой способ требует обернуть приложение в RouterProvider.

И что из этого следует?

Так получилось, что в реакте у нас все компонент, но если абстрагироваться и вспомнить что это в первую очередь функция, то все встает на свои места.

Подождите. Вы рассматриваете приложение как систему из конфигурации компонентов. У компонентов есть назначение. Это назначение определяет место компонента в архитектуре приложения. Поэтому причем тут функция? Компонент нужно рассматривать по назначению в архитектуре приложения.

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

Поэтому я и отослался на то, что контроллеры обычно не такие.

Конечно, это же просто пример. Хардкод пути в match, ничем не хуже хардкода пути в <Route path="..." />, и ничто не мешает вынести его в константу.

Так в этом и есть суть вопроса. Что в начальном, что в вашем случае у вас есть место, где все зависит от конкретных реализаций. Литерал это или экспортируемая константа суть не меняет. Но в первом случае нечто UserSettings не знает о ней, а в вашем случае знает.

В некотором смысле да, и поэтому ниже предлагается решение. Но даже без этого решения есть разница. Здесь App импортирует только модули первого уровня (Home, Users), но не UsersSettings, UserProfile.

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

Page интерфейс. App рендерит верхний модуль Users, но у Users есть вложенные страницы, которые он принимает пропсами. Можно передать их из App, но тогда App начинает зависеть от внутренней реализации Users. В этом примере мы из Users возвращаем другую реализацию Users в которою передаем вложенные страницы.

То, что вы написали означает, что App транзитивно связан со вложенными страницами Users. Иными словами, это ровно тоже самое что было в начале, если бы вы просто вынесли какие-то маршруты в отдельный файл. Вы обсолютно так же в этом отдельном файле без избыточного интерфейса Page можете условно выбирать любые вложенные страницы.

Вы нигде не разрушили связь. У вас всегда все связано на уровне модулей.

@RequestMapping("instances")

public class InstanceController {

    @GetMapping("{id}")

    public InstanceDto getById(@PathVariable UUID id) {

        return service.get(id);
    }
}

Вы имете в виду, что тут маршрут /instances? Это из какого-то фреймворка?

То есть, теперь мы зависим от интерфейса ESM модуля, доступного по алиасу pages. Это можно читать как:

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

Какой композиции? Вы реэкспорт из index файла называете композицией?

render(
  <App 
    router={router} 

    // тут будет ошибка если в экспорт попало не то что нужно
    pages={pages} 
/>)

Где тут композиция? По факту, вы создаете элемент App и передаете ему на вход pages. Само по себе, это не композиция. Просто у вас один модуль жестко связан с другими модулями.

Вот если бы вы в App импортировали компоненты Pages и тут же декларативно указывали как должно и где создаваться элементы - это была бы композиция.

Изменилось. Нам больше не нужно редактировать файл App.tsx при добавлении новой страницы.

Так и раньше можно было легко взять и раскидать все по разным файлам. Никто этому не мешал.

В этом и смысл. Это «децентрализованный» роутер, у него нет файла конфигурации с описанием роутов, а пример демонстрирует, что даже в запутанном графе компонентов все роуты корректно определяются и регистрируются, в том числе относительные, с учетом скоупа.

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

Попробую дать небольшую обратную связь.

Сразу видны нарушения принципов, которым мы пытаемся следовать при проектировании. Например:

Здесь модуль Router зависит от низкоуровневых модулей (конкретных компонентов)

Само по себе это врядли нарушение. Все зависит от принятых на проекте паттернов.  Например, находясь в слайсе app в FSD он и должен зависеть от низкоуровневых модулей. И нарушение принципа тогда не будет.

Чтобы добавить новый роут, например /users/:id/comments, нужно изменить код роутера.

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

/router.tsx отвечает и за создание роутера, и за рендеринг.

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

Иными словами данный модуль является местом инкапсулирующим способ взаимодействия множества объектов. Именно тут слабо связные объекты настраивают друг для друга.

Ещё один существенный недостаток такой реализации — проблема тестируемости. Мы не можем просто протестировать UserSettings

Почему? Вы даже сами создали отдельный элемент <UserSettings />

Так же можете его создать в любом другом месте и тестировать совершенно независимо от всего остального.

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

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

Нет жестких зависимостей

function UserSettings() {
  if (!router.match('/users/:id/settings')) return;
  
  return <div>Settings</div>;
}

Получается следующее, у вас некоторая сущность UserSettings во первых, занимается не только собственной поставленной задачей вывода <div>Settings</div>, но еще и обслуживает задачи маршрутизации. Что очень похоже на наружение принципа единственной ответственности. Вот для тестирования такого UserSettings действительно нужно поднимать обертку обеспечивающую маршрутизацию. Что вы в тестах и делаете.

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

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

// index.ts
import { router } from './Router'
import { HomePage } from '@/pages/home'; 
import { UsersPage } from '@/pages/users';
import { App } from './App'

const pages = [HomePage, UsersPage];

render(<App router={router} pages={pages} />)

Данный index.ts как раз и является аналогом изначального файла роутера. Он так же зависим от всех "ниже лежащих" модулей и конфигурирует их зависимости.

И это тоже любопытно:

Скрытый текст
// Users.tsx
import { UsersList } from './UsersList';
import { UserProfile } from './UserProfile';
import { UserSettings } from './UserSettings';

const nestedPages = [UsersList, UserProfile, UserSettings]

const RealUsersPage: Page = function({ router, pages }) {
  return (
    <Layout>
      {pages.map(Page => <Page router={router} />)}
    </Layout>
  )
}

export const Users: Page = function({ router }) {
  if (!router.match('/users')) return null;
  return <RealUsersPage pages={pages} router={router}  />
}

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

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

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

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

Классический бэкенд:

HTTP Request -> Controller -> Service -> HTTP Response

Во фреймворках обычно контроллеры не имеют связи с маршрутом. Например, если вы будете использовать React Router как фрэймворк. Маршрут у вас будет отдельно от контроллера:

route("/users", "./users.tsx")

Проблема прямых импортов

И избавляемся от прямых импортов:

import { router } from './Router'
import { App } from './App'

import * as Pages from '@/pages';

const pages = Object.values(Pages); // 

render(<App router={router} pages={pages} />)

Вот вы пишете:

По хорошему, наши App или Users должны зависеть от интерфейса, а сейчас это не так, у нас прямые импорты страниц.

Не понял. Что не так? Вам не понравилось что index делает импорт страниц и вы сделали их реимпорт из другого места? Но прямо имопорт страниц из модуря pages никуда не делся. У вас index.ts как зависел от этих модулей так и зависит, только теперь добавилась транзитивная зависимость.

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

Новые страницы автоматически подхватываются при их добавлении в директорию, при условии, что они экспортируются из index.ts. Достаточно создать страницу и экспортировать её — и она "попадает в систему".

То, что вы написали - это называется вручную, а не автоматически. То, что вы вручную добавляете в один файл, а не в другой дело не меняет. Вот если бы у вас работала утилита, которая бы автоматически генерировала index.ts, тогда это было бы автоматически.

Типобезопасность на уровне композиции

Какой композиции? Вы реэкспорт из index файла называете композицией?

Упрощение рефакторинга

Если автор Users решит изменить название компонента на MyCoolUsersPage, это никого вокруг не затронет.

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

Частично соблюдаем Open/Closed Principle

Вот в этом плане вообще ничего не изменилось, по моему.

-----

Я посмотрел финальный результат.

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

Например:

Скрытый текст
const BarNestedStaticRouteStaticRoute = observer(
  function barNestedStaticRouteStaticRoute() {
    const result = router.match('./nested', barNestedStaticRouteStaticRoute);
    if (!result) return null;
    return (
      <div className="route">
        <div>
          It's a match for <code>./nested/</code> relative to{' '}
          <code>./static/*</code> relative to <code>/bar/*</code>
        </div>
      </div>
    );
  }
);

const BarNestedStaticRoute = observer(function barNestedStaticRoute() {
  if (!router.match('./static/*', barNestedStaticRoute)) return null;
  return (
    <div className="route">
      <div>
        It's a match for <code>./static/*</code> relative to <code>/bar/*</code>
      </div>
      <BarNestedStaticRouteStaticRoute />
    </div>
  );
});

У меня даже слов нету...

Паттерн - это описание задачи и описание способа ее решения с указнием плюсов и минусов.

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

И вот вы показываете как пользоваться выбранным вами решением, для решения Разных задач. Это не четыре варианта типизации одной сущности. Это типизация разных сущностей, потому что вы уже в изначальном вашем решении точно указали связь между SegmentType и разными форматами coords. Вы это уже сделали в выражении SegmentCoords.

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

Какую возню? Причет тут фрустрация? В каком месте использования any? Почему? О чем тут вообще идет речь?


Вариант 2: Record Utility Type

type PathSegmentRecord = Record<SegmentType, {  // Ключи — 'line' | 'quadratic'
  type: SegmentType;  // Union для type, без строгого сужения внутри Record
  coords: SegmentCoords<SegmentType>;  // Union coords, менее строгий
}>;

type PathSegment2 = PathSegmentRecord[keyof PathSegmentRecord]; 

Тут вопрос сразу напрашивается, а это зачем? Как этим пользоваться? Если это у вас общий тип, то зачем тут Record? Какая задача решается, с помощью данного типа?

Если вам для вашей задачи нужен только PathSegment, то промежуточный тип Record вам не нужен и нужно сразу писать:

type PathSegment2 = {
    type: SegmentType
    coords: SegmentCoords<SegmentType>
}

А если вам все таки нужен такой Record, тогда:

type PathSegmentRecord = Record<SegmentType, PathSegment2>;


Вариант 3: Generic Type with Default

Типобезопасность высока благодаря generics, обеспечивающим сужение типа, но требует явного указания T.

Что это значит?

Вы только что писали:

Типобезопасность ниже, чем в mapped types, из-за union в coords внутри Record, что снижает строгость сужения типа без дополнительного кода.

Имеем:

type PathSermentFrom5 = PathSegment5

что эквивалентно

type PathSermentFrom5 =  {  
  type: SegmentType
  coords: SegmentCoords<SegmentType>

}

Т.е. это ровно тоже самое, что было только что. О чем вообще весь этот абзац?

Вот если вы уберете дефолтное значение, тогда каждый раз вам придется задавать объединение, тогда может быть, может быть (два раза может быть) получится выстроить ясную систему PathSegment

Вариант 4: Interface Inheritance

Тут все тоже самое. Какую задачу вы решаете? Если вы хотите настрогать ясных сегментов, то почему у вас BaseSegment не имеет coords свойства? Далее, если у вас PathSegment4 является главным поставщиком информации о форме, то зачем вам вообще дублирование в SegmentType?

interface BaseSegment {
  type: string;  // Union discriminant
  coords: number[]
}
// Line: extends с override type и specific coords
interface LineSegment extends BaseSegment {
  type: 'line';  // Литерал для сужения типа
  coords: [x: number, y: number];  // Две координаты
}
// Quadratic: аналогично
interface QuadraticSegment extends BaseSegment {
  type: 'quadratic';  // Литерал
  coords: [controlX: number, controlY: number, x: number, y: number];  // Четыре
}
type PathSegment4 = LineSegment | QuadraticSegment;  // Явный union

type SegmentType = PathSegment4['type']

Для менеджмента это минимизирует техдолг, так как джуны комфортно работают с интерфейсами из-за того, что это еще более базовая база, чем generics, но при росте команды добавление новых интерфейсов может привести к дублированию, конфликтам при слиянии.

А-а-а-а, я ничего не понял. Базовая база, какое-то дублирование, какие-то конфликты...

По моему, у вас все очень сильно перемешалось и поэтому образовалось огромное количество непонятно чего. Наверное, стоило определить задачу, а потому ее 4 раза решить разными способами. А у вас получилось странное использование одного решения, для решения Разных задач.

Это не одно и тоже. Если дебоунс используется, что бы не спамить бэк, то useDefferedValue в базовом исполнении не поможет. Т.е. интерфейс будет вести себя как вы хотите, но на фоне будут проходить все запросы к серверу. А обычный дебоунс пропускает запросы только после delay

А вот это все проходило хоть какую-то проверку временем? Подвергалось пристальному рассмотрению перед публикацией?

Я это и про примеры и про решение.

Если идти сверху вниз.

Зачем вам в useDebounce нечто, что вы называете dependencies?

Ну, потому что useTimeount от этого не зависит, а зависит только от callback. А значит если, callback меняется, то все хуки срабатывают. И что это тогда за dependencies, если он не зависим от callback?

А самое забавное, что потом идет пример, который наглядно демонстрирует всю эту ситуацию.

Вы в свой хук отправляете литерал стрелочной функции. И поэтому вообще не имеет значения, что у вас там за массив [count] такой, потому, что литерал в зависимостях будут заставлять выполянться все хуки, куда он прокинут.

И второе, вы проверяли это работает? Потому что на вид, вы неверно сэмулировали useEffect. При выполнении эффекта сначала должна выполняться функции отмены предыдущего эффета, а потом уже функция нового эффекта. А у вас наоборот. При этом clear у вас общая для любого reset. А это значит, что вы устанавливаете новый ресет, и тут же clear его отменяет. Или я где-то упустил мысль?

Думаю все обсудили. Удачи)

Скрытый текст

Допустим хочет посмотреть как работать с какойто сущностью а в папке entities ее нет. А оказывается он маленький и разработчик засунул в pagesТак же далее при масштабировании она может разрасититься и эта сущность останется раздутой в рамках pages слоя

По идее, все что угодно можно раздуть XD

Не совсем понял что вы имеете ввиду. Да и как понять репозиторий имеет доступ к источникам данных?

Я имел в виду архитектуру гугла:

Уровень пользовательского интерфейса (ui elements -> state holders) -> Уровень данных (repositiries -> data source)

Скрытый текст

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

Что значит размазываем логику по слоям? Выделяя логику в отдельные слои мы делаем эту логику доступной для Всего вышестоящего слоя. Поэтому мы ничего не размазываем, а создаем независимые строительные элементы. Если у нас строительный элемент переиспользуется мы уже не можем говорить, что что-то размазываем.

Скрытый текст

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

Так вот и смотрите, у вас app->feature->core, что находится внутри каждого слоя?

Повторю архитектуру гугла:

Уровень пользовательского интерфейса (ui elements -> state holders) -> Уровень данных (repositiries -> data source)

По сути, четыре уровня. При этом четко обозначено, что находится на каждом уровне и направление зависимостей. Если проводить аналогии с гуглом, то у вас data source из разных фичей не могут быть целью для обращений репозиториев, которые не являются частью фичи, а у гугла такого ограничения нет. Для них как вы говорите на Уровне данных вся функциональность размазана по всему уровню и к ней имеет доступ любой state holder из Уровня пользовательского интерфейса.

Скрытый текст

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

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

Скрытый текст

execute просто общепринятое название для выполнения действияу каждой команды он может быть свой

Честно говоря, впервые об этом слышу. Но я знаю шаблон Команда. Там действительно есть метод execute. По идее, как мне это представляется. Вот если взять архитектуру гугла. Единственным источником данных является репозиторий. В вашем примере - этот репозиторий отвечает за хранение информации формы регистарции. Он такой один, поэтому вы его задаете в зависимости для Di, и тогда передавать в execute LoginBody вам не нужно. И соответственно, если если любую команду конфигурировать только через Di у вас элемента любого класса команды будет иметь один интерфейс execute. Если у вас шаблона нет, то execute лучше называть по семантике выполняемой операции.

тут больше момент уже договоренности внутри команды

Если шаблон есть, то это уже не зависит от разработчика. Нужно следовать интерфейсу шаблона.

Скрытый текст

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

Да этот пример со vue можно не учитывать, потому что он крайне специфичен.

Суть фреймворков как раз в том, что они меняются крайне редко.

Тут же не согласен, так как view model создается один раз и живет на протяжении всего жизненного цикла компонента

Модель создается один раз. А вот весь компонент выполняется постоянно, когда идет обновление. Вы использовали литерал и поместили его в зависимости хука useEffect, поэтому данный useEffect будет выполняться каждый раз когда идет обновление компонента.

 useFlow(viewModel.uiEvent, (event) => { // <--- эта функция при каждом рендере новая
      ...
    }
  })

Di будет выглядеть ровно так же как и у вас) fsd вообще до лампочки, какие вы там используете шаблоны в своих слоях.

1
23 ...

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность

Специализация

Фронтенд разработчик, Фулстек разработчик
Старший
ООП
TypeScript
JavaScript
React
Vue.js