Даже в JavaScript/TypeScript мире начала распространяться благая весть, что наследование это зло, и в большинстве случаев его лучше заменить структурной композицией или открытым конструктором. Но не все помнят, что была целая группа языков программирования объектно-базированных, где классы были и можно было делать экземпляры, но нельзя было делать новых классов: ada, fortran, foxbase, clipper... Подумайте, это интересное решение, так можно делать и в js/ts
Ээх... Вы еще говнокода не видели, AI сейчас обучается на коде мясных программистов, а вот когда он десятилетиями будет пережевывать свой код, вот тогда... Хотя нет, уже некому будет понять масштабы катастрофы, когнитивные способности мясных то падают.
То, что Promis не монада, это общеизвестно, но нашлись люди, которые говорят, что Array монада, и что из Thenable нельзя сделать монаду, здесь доказательство про Thenable, о про Array даже говорить нет нужды, ну я надеюсь, что это очевидно... или нет? https://github.com/HowProgrammingWorks/Thenable/blob/master/JavaScript/b-monad.js
class Thenable {
constructor(run) {
this.run = run;
}
static of = (v) => new Thenable((res) => res(v));
then = (res, rej) => this.run(res, rej);
map = (fn) => this.chain((v) => Thenable.of(fn(v)));
chain = (fn) =>
new Thenable((res, rej) => this.run((v) => fn(v).then(res, rej), rej));
}Любой руководитель, получив высокий уровень власти, может решить, что получил полный контроль над системой. Но стоит опуститься на уровень деталей - и его сразу придавит мощностью континуума. Кстати, это и со специалистами работает, которые отлично освоили высокоуровневый язык программирования, но за каждым оператором стоит много слоев компиляции, оптимизации, виртуализации, исполнения на конкретном железе. Копни туда и открывается непрерывная сложность: даже император не способен гарантировать, что в полдень в самом дальнем селе по его приказу все поднимут правую руку. Ошибки в коде исправить можно, но нельзя гарантировать, что вы за конечное время действительно поняли, что исправлено, и приказали коду работать правильно во всех случаях, инженеру не приходит гениальная мысль по команде, дети не смеются по расписанию, почтальон может заблудиться, а генерал - охрипнуть. Абсолютная надежность, она как скорость света, недостижима даже в мелочах.
Тут мои заметки из мастер-класса по парадигмам программирования, такой конспект, со снипетами для быстрого понимания о чем речь и примерами кода одного фрагмента на 46 разных стилях, это не 46 парадигм, но мы и говорили, что нет уже таких целостных правильных парадигм, есть те или иные отдельные свойства используемые для реализации идей. В результате, конструируем из них стили программирования: https://github.com/HowProgrammingWorks/Paradigms
Lecture: https://youtu.be/m03gMxXpIao
Traditional definition
Referential Transparency is the property of a function, expression, or object that can be replaced by its resulting value without changing the program's behavior.
lternative definition
We can pass a reference to any immutable object, pure function, closure (which does not change state) to any part of the program without fear of race conditions or data corruption, even with concurrency and asynchrony.
Meaning
- Same explicit inputs gives same result
- No hidden state, no mutation of shared data (Immutable state)
- No I/O
- Pure functions
This principle simplifies code reasoning, debugging, and optimization. Can be used in Functional, Object-Oriented Programming, other paradigms.
For Object-Oriented Programming:
In OOP, referential transparency is achieved when object’s state is immutable. "State changes" are represented by returning a new object rather than mutating existing state.
- Return a new object from methods with changed state
- Return the same one for methods not changing state
Related ideas:
- Copy-on-write: A memory optimization technique where multiple references can share the same data until one of them needs to modify it, at which point a copy is created. This allows efficient sharing of immutable data structures while preserving referential transparency.
- Ownership: A memory management concept (prominent in Rust) where each value has a single owner responsible for its lifecycle. When the owner goes out of scope, the value is automatically cleaned up. This prevents data races and memory issues while maintaining referential transparency.
Lecture: https://youtu.be/m03gMxXpIao
Спасибо AI, что объяснил всем, что код ничего не стоит, авторские права не нужны, важна способность поддерживать его работоспособность и оперативно решать задачи, а горы кода не ценность сама по себе, это просто но не для всех было очевидно, люди держались за свой говнокод, шифровали, хранили на флешках в сейфе, надеюсь, это закончилось
ФП не вывели из λ-исчисления, это выяснилось задним числом
Выводы: если что-то работает, но мы не видим, что за этим стоит обширная теория, может казаться, что оно просто работает, но нет, как Маккарти понял из Черча только слово функция и еще символ лямбда понравился, примерно в таком же стиле процедурное программирование связано с машиной Тьюринга. Просто мы еще находимся на начальном этапе становления программирования, через 200-300 лет могут быть построены адекватные теории, а сейчас пока много мифов и псевдонаучного бреда, не нужно делать вид, что все все поняли и из правильной теории напрямую вывели красивую практику и построили технические решения, все складывалось кое-как, работает и работает. Затем вывели из практики инженерные обобщения как GRASP, SOLID, GOF, они ненаучны, они полностью практичны, и ограничены, но работают, более того, и для ФП работают, имея определенные ограничения, нужно больше времени, чтобы объединить это в теорию, и вот когда ее выведут, общую для всех парадигм, то людям, которые будут учиться по новым книгам, будет казаться, что это все так и было выведено сразу гладко и согласовано.
- Структура и согласованность объектов
- Не добавляйте и не удаляйте поля после создания; инициализируйте все поля в конструкторе или фабрике.
- Сохраняйте форму объекта, одинаковый порядок полей и типы в экземплярах.
- Форма объекта (spape, hidden class) — структура времени выполнения, обеспечивающая быстрый доступ к свойствам.
- Не изменяйте типы идентификаторов или полей; сохраняйте одинаковую форму на протяжении всего использования.
- Избегайте изменения формы через необязательные свойства, вместо этого используйте
nullиundefiled. - Мономорфный код — оптимизирован для формы одного объекта, чтобы оставаться в рамках прогретого бауткода.
- Встроенное кэширование — JIT-оптимизация, которая запоминает видимые формы для быстрого доступа к свойствам.
- Избегайте типов union в большинствбежатье полиморфный и мегаморфный код.
- Стабильный доступ к свойствам: предпочитайте точечную нотацию
.fieldдинамическому доступу[name].
- Представление данных
- Избегайте пустот в массивах: никаких разреженных массивов и удалений;
- Отдавайте предпочтение массивам с содержимым одного типа.
- Держите Number в пределах небольших целых чисел со знаком, чтобы избежать случайных
doubleилиNaN. - Используйте типизированные массивы для числодробилок.
- Повторно используйте предварительно выделенную память для снижения нагрузки на сборщик мусора.
- Функции и состояние
- Делайте функции, удобными для inlining: небольшие, чистые и с мономорфными параметрами.
- Избегайте полиморфных возвратов, чтобы виртуальная машина могла специализировать места вызовов.
- Отдавайте предпочтение стрелочным функциям, когда контекст объекта не нужен.
- Не изменяйте входящие параметры.
- Избегайте
bind,apply,callдля функций в производительном коде.
- Циклы и управление потоком выполнения
- Избегайте функций внутри циклов.
- Отдавайте предпочтение циклам
forдля производительности иfor..ofдля выразительности; - Избегайте
for..inи.forEach(). - Выбирайте метод итерации массива, соответствующий намерению (семантике).
- Оптимизируйте инварианты циклов
- Отдавайте гиклам, а не рекурсии.
- Эффективность выполнения
- Оптимизируйте лексическую область видимости, поддерживайте узкую видимость идентификатора.
- Отдавайте предпочтение
const, и реже используйтеlet, только когда нельзя обойтись без переназначения идентификатора. - Формирование строк: используйте шаблонные строки или массивы с
join. - Избегайте deopt: не используйте
with,eval,new Functionили переопределение прототипа. - Прогревайте предсказуемые пути перед использованием и измерением производительности.
- Разделяйте горячие и холодные пути, чтобы горячие функции оставались небольшими.
- Ad-hoc polymorphism - Functions or operators behave differently based on argument types
- Function and method overloading - Multiple functions with same name, different signatures
- Operator overloading - Custom behavior for built-in operators
- Type-class polymorphism - Ad-hoc polymorphism via type constraints (e.g. Haskell type classes, Rust traits)
- Coercion polymorphism - Implicit or explicit type conversions
- Subtype polymorphism - Objects of derived types can be used where base types are expected
- Class inheritance - IS-A relationship via base classes
- Interface / protocol polymorphism - Contracts that types must implement
- Structural (duck typing) polymorphism - Compatibility based on structure, not explicit inheritance
- Parametric polymorphism - Code written generically to work with any type (examples will be added ASAP)
- Generic functions - Functions parameterized by types
- Generic data structures - Data structures parameterized by types
- Dispatch mechanisms - How the runtime selects which method to call
- Dynamic dispatch - Method chosen at runtime based on object type
- Virtual functions and methods - Base class methods overridable by derived classes
- Multiple or multimethod dispatch - Method chosen based on multiple argument types
Examples: https://github.com/HowProgrammingWorks/Polymorphism
- Nominal Typing - совместимость и эквивалентность типов/классов/интерфейсов по имени в том числе и через наследование.
User { name: string } ≠ Department { name: string }
- Structural Typing - совместимость типов определяется структурой или формой (соответствием полей и методов как в JS/TS).
User { name: string } ≡ Department { name: string }
- Encapsulation - объединение данных и поведения в одну абстракцию (на синтаксисе классов, прототипов, замыканий).
Counter { value, inc() }
- Hiding - ограничение видимости внутреннего состояния и деталей реализации из внешнего кода.
Counter { #value, inc(), get value() }
- Structural Composition - создание сложных объектов путем создания простых в конструкторах и записи ссылкок в поля, с дальнейшим делегированием.
Car { engine: Engine, wheels: Wheels[] }
- Aggregation - слабая форма композиции, при которой содержащиеся объекты могут существовать независимо от контейнера.
AggregateError { errors: [Error, Error, Error] }
- Delegation - абстракция передает ответственность за поведение своему скомпозированному структурному компоненту.
Semaphore { #counter: Counter, get count(), enter(), leave() }
Вынужден сказать вам правду. Паттерны - это не только решения типовых проблем, но и бездонный источник идей для излишнего усложнения и запутывания кодовой базы:
- как только человек выучил паттерны, ему хочется применять их везде, и в первые месяцы после умных книжек программист творит сплошной оверинженеринг: в коде появляется в разы больше абстракций, чем нужно,
- абстракции не бесплатны: всякая гибкость, динамическая диспетчеризация, pure fabrication и мелкодисперсная декомпозиция повышают количество объектов, слоев и вызовов, просаживается производительность,
- часто код становится сложным для понимания, повышается порог входа: без глубоких знаний коллеги испытывают трудности из-за того, что вы перестали решать проблемы в лоб и применяете сложные для них концепции,
- усложняются отладка и тестирование: цепочки делегирования и фабрики маскируют реальные места ошибок, теряется стек, или в нем появляются непонятные промежуточные слои, перехватчики, а если используется рефлексия, то - динамически построенные абстракции, которых в коде никто не видит.
Что делать?
Только практика и менторин могут помочь не потерять время на оверинженеринг и эксперименты. Нужны задачи по:
- переписыванию кода с удалением паттерна
- выбору простейшего паттерна для решения проблемы
- использованию встроенных в язык возможностей, решающих ту же задачу, что и паттерн
- задачи с трейдофами, когда в условии должны быть NFR или дополнительные ограничения
- рефакторинг плохо написанных паттернов, упрощение, когнитивная оптимизация
- реальные проекты, где есть ограничения во времени и нужен компромис
Функциональное программирование, все равно имеет дело с coupling и cohesion, с дженериками и интерфейсами, с адаптерами и фасадами. Хоть паттерны и появились в среде ООП, но они полностью пронизаны идеями из ФП, а ФП должно использовать приемы распределения ответственностьи и изоляции сложности, которые развились в ООП. Синтаксическая разница между парадигмами не так существенна, как мы уже говорили, можно делать монады на классах и инкапсуляцию на замыканиях. На самом деле, между парадигмами не так много разницы. Главное различие - это иммутабельность, но не сложно представить себе такое ООП, в котором каждый матод отображает экземпляр в новый объект. Какие еще существенные отличия? Выражения в ФП против пошагового исполнения в разных императивных стилях. Да, но это не влияет на применимость GRASP и SOLID. Работа в данными важнее способа исполнения кода. Композиция и там и там предпочтительнее, а наследования нужно избегать, хоть и там и там оно реализуется. Сокрытие и делегирование везде используются. Изоляция и расширяемость везде достигаются, да, немного разными способами, но это не так существенно. Так что, большинство принципов GRASP и SOLID вполне применимы во всех парадигмах. Более того, эти принципы работают даже в самом ужасном коде, который плох с позиции любой парадигмы. Если код выполняет то, что нужно и делает это надежно, тесты проходят не от случая к случаю, а всегда, то на каких-то принципах он же основан, пусть в нем может быть путаница в понятиях или плохая декомпозиция, чрезмерная сложность или запутанность, но базовые принципы работают даже там, где нам сложно это осознать сквозь дебри кода.
Тут пример хороших и плохих реализаций на ФП и простом императивном процедурном программировании для той же задачи, которую я публиковал на этой неделе, в слишком гранулярной реализации https://github.com/HowProgrammingWorks/Abstractions/tree/master/JavaScript
Вчера перезаписал 2 лекции по метапрограммированию. Там про техники, позволяющие программно изменять поведение программ с использованием метаданных:
- Introspection - чтение структур и программных абстракций: типы, поля, методы, наследование и т.д.
- Reflection - динамическое изменение поведения и структуры программы.
- Scaffolding - автоматическая генерация кода/структур для ускорения разработки.
- Code Generation - программная генерация кода из метаданных.
- Macros - расширение синтаксиса языка через шаблоны или скрипты.
- Decorators - добавление к коду метаданных, влияющих на выполнение.
- Dynamic Invocation - вызов методов, функций, конструкторов и классов по имени во время исполнения.
- Monkey Patching - изменение кода в рантайме, добавлением и удалением свойств и методов.
- Generics - унификация и параметризация типов и алгоритмов если в коде изменяются только типы.
- DSL - доменно-специализируемые и встраиваемые языки для выразительности синтаксиса под задачу.
- Self-Modification - программы, изменяющие собственный код, байткод или бинарник.
- Polyfilling - вмешательство в поведение языка и платформы, перехват и подмена стандартного API.
- Homoiconic code - использование одинаковых структур для данных для исполняемого кода программы, как в LISP.
Гранулярность кода (на скриншоте слишком мелко-гранулярный ФП код)

Общий инженерный принцип SoC (Separation of concerns) решает большую часть проблем хрупкости и понятности. SoC нравится мне гораздо больше паттерна SOLID:SRP (Single Responsibility Principle), потому что он точнее.
SRP утверждает: один класс — одна причина для изменения. Но большинство причин можно декомпозировать. А вот SoC указывает на гранулярность — мы сами выбираем уровень гранулярности. Фактически это выливается в организацию модульности, контрактное программирование, тестирование, управление зацеплением и Protected Variations по GRASP, во многом Open/Closed из SOLID.
Тут пример того, как на курсах я описываю парадигмы, конечно с примерами, и обсуждением, какие эффекты мы можем получить, благодаря этим характеристикам и как их комбинировать.
Императивное программирование
- Характеристики: явные команды, циклы, ветвление, пошаговый контроль, изменяемые данные, экономия памяти, риск "гонок", повышение производительности, сложность поддержки и отладки.
- Влияние на архитектуру: сложность масштабирования, оптимизация отклика (latency)
- Хорошо для: системный код близкий к железу, сценарная бизнес-логика, оптимизация hot-paths.
- Плохо для: абстракций среднего уровня, вычислений, кода с сильной конкуррентностью.
- Синтез: императивная "оболочка" + ФП или ООП "ядро"
Конечные автоматы
- Характеристики: явное состояние, надежные переходы, события, детерминрованное поведение (предсказуемое), декларативность моделирования.
- Влияние на архитектуру: повышает прозрачность жизненных циклов, верификация процессов.
- Хорошо для: моделирование воркфлоу, оркестрация, парсинг, сетевые протоколы, геймдев.
- Плохо для: много состояний и переходов, сложные домены, "туманные" доменные модели без четких фаз.
- Синтез: FSM на границах + сценарии/SAGA для длительных процессов.
Прототипное программирование
- Характеристики: динамическое изменение наследования, экономия памяти, гибкость модели.
- Влияние на архитектуру: нативная поддержка для JavaScript, высокая эффективность и гибкость, меньше ритуалов для результата.
- Хорошо для: расширяемые платформы, Web-программирование, метапрограммирование.
- Плохо для: для сложных доменов риск непредсказуемых состояний.
- Синтез: прототипы для frontend и UI + строгий домен на ООП, ФП, процедурном.
По ссылке можно посмотреть примеров кода, тут 46 стилей с разными парадигмами и разными эффектами, про которые я говорил на стриме: https://github.com/HowProgrammingWorks/Paradigms
- Код может быть и быстрым и понятным одновременно, если не доводить концептуальность и оптимизацию до фанатизма.
- Более 90% оптимизации под виртуальные машины может выполняться интуитивно, если запомнить всего несколько простых правил.
- Нет "лучшей" парадигмы программирования, и лучшего стиля, можно выбирать любую машинерию, чтобы получать желаемые характеристики кода.
- Паттерны Prototype и Proxy в GoF путаются с возможностями языка JavaScript, а паттерны Iterator и Decorator встроены в язык.
- Паттерн Strategy можно реализовать через Map<string, Function> или Map<string, Constructor> и много такого...
- Паттерн Observer породил в JavaScript сразу много: EventEmitter, EventTerget, Signals, MessagePort API, BroadcastChannel и др...
- Нужно разделять синтаксис и парадигму, может быть монада в синтаксисе класса, а может быть ООП на замыканиях.
- Паттерны готовят почву для построения архитектуры, без них код рыхлый и неуправляемый, из которого хорошей архитектуры не построить.
- Паттерны - это не теория, а практика, каждый паттерн дает типовое решение для распространенных проблем, которые встречаются везде.
Медленная разработка, хрупкий код, застой в карьере, все эти вещи, о которых я писал выше, имеют одну причину — лоскутные знания и однообразный опыт, который и опытом то считать можно только для резюме, формально.
Корень проблеми — отсутствие системного подхода и воли к глубокому изучению специальности. Можно 10 лет проработать, но так и не понять, почему плохо менять тип поля в рантайме или выражение req.user.profile.contacts.email совершенно недопустимо. Можно построить десяток API на NestJS, но так и не понять, почему middleware это плохо, всю жизнь верить, что в JavaScript нельзя сделать race condition и Node.js однопоточный.
Накопленный годами техдолг, хаотичная разработка без плана, отсутствие наставников и системного подхода к решению задач дают один результат — раздолбайство, магическое мышление и вера в байки, передаваемые от тех, кто не привык подвергать критике услышенное, к тем, кто ленится проверить факт простым экспериментом на 10 минут.
Вы все время пилите окошки, апишки и модельки? Ничего по настоящему интересного и сложного? Кому-то это подходит, а вы чувствуете себя некомфортно. Что это, застой, выгорание?
Признаки кризиса:
- в почте нет предложений о работе, не зовут на собесы
- вы не едите в карьерном лифте и ЗП не растет
- страшно уволиться, остаться без работы
- нет радости от работы
На самом деле, и выгорания то не существует — это миф, такое состояние безвыходности вы сами себе обеспечили и убедили себя, что работа бессмысленна, но не в работе проблема. Если заапгрейдить себя, то вы или работу почините или уйдете на нормальную.
Когда вы чувствуете деградацию, первое, что нужно исправить, это самоуважение, а тут без наращивания хард-скиллов никак. Если вы профессионал и любите свою работу, то никакого застоя и выгорания не будет. А если нет экспертизы, тут не с выгоранием нужно бороться, а наращивать знания и опыт.
Хоть я не искал работы целенаправленно уже около 20 лет, но я понимаю, что если бы в мою почту не приходило десяток оферов уровня CTO и архитектора каждую неделю, то уверенности бы у меня поубавилось.
Хрупкий код — это когда каждое изменение ломает что-то еще, а вы сидите в дебагере, а потом откатываете до рабочего коммита, иначе проект не собирается. Дебагер в проекте — это зло, его место — исследование рантайма, можно лучше понять инструмент — да, но в разработке, дебагер — это просто слив времени в никуда.
Это обычное состояние проектов, когда код хрупкий, иначе это намного дороже для компании. Новое портит старое, вы ковыряетесь в легаси, рефакторите без цели. Такой код — это техдолг, который растет экспоненциально, как снежный ком. Ваше время уходит не на создание ценности для продукта, а на тушение пожаров. И тут вы со своим дебагером. Давайте тушить пожар бензином. Отличное решение.
Что задерживает разработку и как? Куда уходит время, которое можно было бы потратить на новые фичи?
🐢 долгое согласование — вопросы или задачи сформулированы так, что люди подсознательно их игнорируют, уходят от ответа, не отвечают на сообщения, не приходят на созвоны; процесс принятия решений затягивается.
🔗 высокое зацепление — части программы слишком тесно связаны: много обращений к чужим свойствам и методам, из-за чего изменения в одном месте ломают другое, и модули невозможно исправлять независимо.
🤯 недооценка сложности — задача кажется простой, но в процессе реализации выясняется, что есть скрытые зависимости, то же зацепление, обновление зависимостей, исключения и дополнительные условия.
🔥 тушение пожара — вместо планомерной работы, новой функциональности, приходится срочно чинить критические баги или решать кризисные ситуации, у пользователей работа встала, теряем клиентов, разрушаются данные.
🧩 нет типовых решений — похожие задачи есть всегда, но для них не всегда подготовлены типовые решения, каждый раз разработчик выделяет решение из сторого кода, нет единого переиспользуемого подхода.
📦 плохая декомпозиция — код разделен на модули не по смыслу и не по зацеплению, а по непонятному признаку, слишком крупно или нелогично; "ось изменений" проходит по нескольким классам или модулям.
🙈 непонятный код — сложно читается, не подходящая гранулярность (слишком мелкие или крупные части); именование даже не намекает на что-то известное; оверинжиниринг: использованы лишние абстракции, паттерны, слои.
⚖️ противоречивые требования — разные руководители и представители заказчика присылают разные противоречивые требования или команды хотят разное, требования конфликтуют между собой или даже противоречивы внутри.
🛑 ручные процессы — отсутствие автоматизации: нет или мало тестов, ручной деплой, воспроизведение багов делаются вручную, не соблюдается semver и не отслеживаются версии зависимостей, вызывающих проблемы.
🌀 изменение приоритетов — задачи и цели постоянно «прыгают», фокус команды рассеивается, людей дергают на созвоны, на которых политика опять меняется и ничего не доводится до конца, ответственных не найти.
🚧 технический долг — старые и успешно замаскированные проблемы никуда не исчезли, костыли и временные решения, когда-то введённые для ускорения результата, замедляют развитие новых возможностей.
🔒 блокеры от внешних команд — выполнение задачи упирается во внешнюю зависимость и все всех ждут (например, API другой команды, решение смежного отдела, партнеров, безопасности), и прогресс стоит на месте.
Код не стоит ничего! Ценно владение кодом, а владение — это не авторские права, а возможность внести в код изменения в предсказуемые сроки.
Изменять код без страха, что он рассыпется в руках, и понимание, как он работает, — не так просто. Для надежного владения нужно перекрестное владение кодом нескольких разработчиков, которые чувствуют код, помнят, где что находится, и, получая задачу на фичу, могут выдать эстимейт и придерживаться его с разумной погрешностью.
Если нет владения кодом, то нет продукта. Непредсказуемый код ничего не стоит и только тянет компанию на дно. Но проблема не только в коде — владение это процесс.
Если ревью тянется неделями, каждое изменение рождает новые баги, как снежный ком — это системная проблема: отсутствие владения кодом, а скорее всего, и инженерной культуры. Как следствие — ужасная кодовая база. Как она стала такой — вы знаете.
А что делать? Вместо переписываний нужен рефакторинг, покрытие тестами, понижение зацепления, внедрение практик перекрестного ревью, работа малыми правками, никаких незакоммиченных изменений, оставленных на завтра. Не подгонять тесты под код, а исправлять код до тех пор, пока он не пройдет тесты. Изменения должны стать предсказуемыми, малыми и атомарными.
Кто там кричит «AI заменит программистов»? Тут в github тысячи issues есть в самых популярных репозиториях. Когда вы их собираетесь закрывать с помощью AI?
Node.js 1.7k, Next.js 2.3k, TypeScript 5k, React 811, Redis 2.2k, Angular 1.2k, Go 5k, Deno 2.3k, Rust 5k, Kubernetes 1.9k
Я вот делаю 1 раза в неделю лайвкод с парным программированием на AI: Cursor, Copilot, Claude code и т.д.
Тут пример приложения с автоматической синхронизацией состояния между закладками, сервером и несколькими устройствами по принципу local-first, объяснение подхода и того, как в нем применяются Service worker, CRDT, OPFS и другие части технологии https://youtu.be/jHgprxfOgBY
Вчера проводил лайвкодинг с Cursor 1.3.9 (claude-4-sonet, claude-3.5-sonet, gpt-4.1, o3, gemini-2.5-pro) для студентов курса по паттернам, Node.js и асинхронности. Показал, как правильно формулировать задачу для ИИ — воспринимать его как исполнителя, а не как волшебную коробку, которая все делает за вас. Больше часа писал техническое задание, а затем ИИ очень быстро все реализовал — но все идеи уже были в ТЗ. Я предоставил примеры кода из своих предыдущих разработок, описание задачи заняло 71 строку: https://github.com/metarhia/metautil/blob/gsid-ai/lib/TASKS.md
После этого, с небольшими доработками в течение 10–15 минут, он сгенерировал вот эти 43 строки кода: https://github.com/metarhia/metautil/blob/gsid-ai/lib/gsid.js а также много вспомогательного кода для анализа результатов, отчета по производительности и оптимизации, который я добавил в конец файла TASKS_md.
Некоторые материалы я публикую здесь, другие будут доступны только студентам. Скоро запишу видео со сравнением — что получается, если воспринимать ИИ как ассистента, и что выходит, когда человек не понимает задачу и не умеет управлять ИИ.
🚀 Together, these technologies form the infrastructure for local-first applications:
- PWA (Progressive Web App)
Web apps with UX close to native: offline mode, installation, fast loading. Solve issues with poor connectivity and slow networks by combining the strengths of web and native applications.
- CRDT (Conflict-Free Replicated Data Types)
Data structures for automatic conflict resolution in distributed systems. Solve synchronization and concurrent editing problems, enabling offline-first applications without data loss or conflicts.
- CAS Containers (Compare-And-Swap)
Atomic concurrency mechanism holding entire database record protected by hashes or versions. Solve race conditions and concurrent modification conflicts, ensure data consistency, and enable optimistic concurrency control in distributed databases.
- IndexedDB (browser built-in database)
Client-side database API for transactional storage of structured data in browsers. Solves offline persistence, local querying, caching, and building b-tree indexes.
- OPFS (Origin Private File System)
Secure, high-performance file system accessible only by web applications within their origin. Solves large file storage issues and enables high-speed file operations on the web.
- Blockchain (without mining)
Distributed, reliable ledger for decentralized databases and immutable history. Solves data integrity, immutability, transparency, and trust issues.
- JavaScript Smart Contracts
Business logic executed in JavaScript within decentralized environments. Solves automation and trust issues related to data changes, ensures automatic enforcement of agreements, and secure code execution.
- WebSocket
Protocol for real-time, two-way data exchange over a single TCP connection. Solves latency issues and supports interactive near-real-time applications.
- WebRTC (Web Real-Time Communication)
Protocol for real-time streaming of multimedia and peer-to-peer data exchange. Solves issues of direct real-time communication, low latency, and decentralization without intermediary servers.
- Metaschema
Declarative schema language for modeling, validation, and data synchronization. Solves problems of data inconsistency, schema evolution and migration, simplifies metadata definition, and reduces complexity when working with structured data.
Features:
- Single WebSocket connection shared across tabs
- Can be installed as a native app
- Works without internet connection
- Background caching
- Automatic reconnection
- HTTPS headers and CORS support
Link: https://github.com/HowProgrammingWorks/PWA
Главная проблема AI в программировании в том, что люди хотят, чтобы AI за них писал и был им синьором, а они ему помогали как джуны. А нужно воспринимать AI как джуна, чтобы он помогал вам, как синьору.
💡 Как сделать новый популярный фреймворк:
- Взять один случайный паттерн
- Дать ему неузнаваемое название
- Сделать презентацию, пообещав всем, что это и есть решение всех проблем
Почему так получается, что программисты все время решают проблемы инструментов, нагрузок и масштабирования, фронтенда и интерфейсов, микро оптимизации, но вообще не обсуждают проектирование предметной области, выразительность модели, когнитивная нагрузка?
- Это технологический фетишизм
- Никогда не видел бизнес-логики только CRUD везде
- Это плохой контакт с бизнесом
- Предметная область настолько разная, что мы не знаем как об этом говорить
- С предметной областью нет проблем
- Отстань, мы просто хотим писать код, закрывать ишью
New course structure
- Layered (onion), DDD, Clean architecture
- App structure, Modularity, DI, unittesting
- DTOs, models, race conditions
- Hexagonal Architecture, ports and adapters architecture
- Clustering, Parallel, Distributed systems, CAP, ACID, BASE, Locking, CQRS
- Actor Model
- Databases, data modeling
- Domain Specific Languages: DSL, AST, LISP
- Command, QueryObject, CQS, CQRS, EventSourcing
- Messaging: MQ, Pub/Sub, Pull
- System integration and topology: API, bus, brocker, MQ
- Communication styles: data, call, event, log sync, p2p, blockchain
- Feature-Sliced Design
- Architecture for Web: DDD for Frontend and Backend
- Pipeline architecture
- SOA: web services, microservices, serverless
- Data warehouses and DBMS: relational, noSQL, columnar, key-value
- API Design
- Corporate integration buses (exchange with external subsystems)
- Task and resource schedulers
- Testing, quality assessments, continuous integration
- Infrastructure, deployment, update, migration, reengineering
- Balancing, replication, sharding, resharding, backups and recovery
- Security, authorization, authentication, application firewall
- Application and system logging, incident investigation
- Analysis and reengineering of business processes
👉 https://github.com/HowProgrammingWorks/Index/blob/master/Courses/Architecture-2025.md
Признак хорошо спроектированного контракта (будь то интерфейс, сигнатура, абстрактный класс, фасад, тип, API...) — это когда:
- Понятно, как использовать, не заглядывая в исходники. Достаточно имен и, в крайнем случае, тестов или примеров.
- Не требует трассировки вызовов в реализации контракта. Всё очевидно на уровне интерфейса.
- Ошибки локализуются в 1 шаг, без анализа длинных цепочек вызовов.
- Следует LoD (Law of Demeter) и принципу "Do not talk to strangers", ограничивая ненужные зависимости.
- Использует осмысленное именование, которое отражает суть и минимизирует когнитивную нагрузку.
Про взаимодействие клиента и сервера разработчики привыкли думать в терминах конкретного протокола (websocket, grpc, http api, sse), но в первую очередь нужно выбирать стиль взаимодействия, а потом уже транспортный протокол и формат передачи данных. Вот эти стили взаимодействия:
- REST/Resources – объекты взаимодействия это ресурсы с четкой идентификацией изменяемые через CRUD-операции. Плюсы: явная адресация, идемпотентность, простое масштабирование. Минусы: плохо подходит для интерактивных и реактивных сценариев, игр, чатов, соцсетей, обновления по инициативе сервера.
- RPC/Calls – императивный подход, ориентированный на выполнение удаленных процедур и контрактное взаимодействие. Минусы: чувствительность к сетевым задержкам и версионности API. Плюсы: надежность, простота и естественность для мышления программиста.
- Events/Messages – асинхронная модель, взаимодействие строится вокруг событий и уведомлений, а не запроса-ответа. Позволяет легко масштабироваться, но и легко сломать данные, требует продуманного механизма последовательности событий и согласованности данных в условиях высокой нагрузки.
- Shared Database — Все участники взаимодействия обращаются к общей базе данных. Такой стиль переносит всю логику на клиент и упрощает интеграцию, но вместе с тем создает узкое место (SPOF) и плохо масштабируется, годится для систем с низкой интенсивностью взаимодействия, корпоративных приложений.
- Simple data sync (без разрешения конфликтов) – Передачи данных без сложных механизмов консистентности, оптимистическое сохранение, кто последний тот и прав. Хорошо работает в однонаправленных потоках синхронизации, системах с нестрогими требованиями к актуальности, однопользовательских системах.
- Operation log sync (с разрешением конфликтов) – Репликация через журнал операций, позволяющая автоматически разрешать конфликты и обеспечивать согласованность в распределенных системах. Требует строгого контроля порядка выполнения команд, использует CRDT и Operational transformation.
- Peer-to-Peer — Модель без централизованного сервера, где узлы взаимодействуют напрямую, реплицируя состояние по мере необходимости. Позволяет строить отказоустойчивые сети, но требует механики согласования состояния состояния, используется в blockchain.
— Нужна ли оптимизация JavaScript обычному разработчику?
— Чем лучше будут оптимизированы библиотеки и фреймворки,
тем меньше нужно залазить в оптимизацию прикладным программистам.
Кто-то думает, что это важно и интересно, другие считают, что это все фетишизм и карго культ, но в действительности все гораздо сложнее.
- Есть продуктовые разработчики, их нельзя пускать к оптимизации, их задача эффективно решать задачи предметной области. Но у них есть соблазн и непреодолимое желание что-то поизмерять и пооптимизировать, потому, что их уже тошнит от монотонной работы... формочки, модельки и апишки. Есть ощущение, что ты мнешь дерьмо в ступе и далек от настоящего программирования. Хоть это не так, нужно вникать в предметную область, которая не так проста, вот только не хочется...
- Есть специальные люди, вот как Мурыч, которые с утра до вечера только и занимаются оптимизацией, да... как основной профессией. Это не так просто, как кажется прикладным программистам, невозможно просто повторить операцию 10 млн раз и сравнить несколько ее реализаций, тут очень просто измерить совсем не то, что вы хотели измерить, нужно иметь большой опыт именно измерений и хорошо знать устройство виртуальной машины.
- Есть системные программисты, которые пишут платформы, библиотеки и фреймворки, они бы хотели оптимизировать свой код и именно им это действительно нужно, но в большинстве случаев они тоже гонятся за популярностью своего продукта, и на декомпиляцию, дебаг и профайлинг времени у них мало.
- Так вот, профессиональные оптимизаторы вполне могут подсказать им основные типовые шаблоны эффективного кода и они даже поймут многое, потому, что часто умеют опыт си и ассемблера, ну в основном понимают, так даже от понимания низкоуровневого языка, системный код становится быстрее в разы, потому, что человек может себе в общих чертах представить оптимизации, но не про все знает.
- В прикладном коде хочется писать более понятно и надежно, что не всегда совпадает с оптимизациями. Ну часто и совпадает, но не всегда. Если их немного обучить писать оптимально под V8, добавить автоматическую проверку оптимизаций линтером, паттерны, ревью кода... то можно меньше заморачиваться оптимизацией на прикладном уровне.
- Прикладным прогпраммистам нужно больше заниматься на семантикой, надёжностью и поддерживаемостью кода, но немного простых правил оптимизации и им тоже можно внушить. Бессмысленно прикладным программистам самим писать тесты производительности, исследовать поведение виртуальной машины, дезасемблировать код, для того, чтобы вникнуть, нужно лет 5. Но вот не писать примеси, не делать optional полей и не использовать деструктуризацию массива, не использовать юнион тайпы для классов и структур - это важно и нужно.
- Нарешаются литкода для собесов, а потом, на реальном проекте переменную нормально назвать не могут,
- Очень любят книжку с кабанчиком, но деплоят по FTP,
- За TypeScript могут глаза выцарапать в интернетах, но
anyи юнионтайпы на каждом шагу, - Запишут в резюме кучу фреймворков, но а потом из npm код ставят не прочитав исходника,
- Расскажут на собесе про паттерны, а потом пишут:
if (enabled === true)... - Полгода пилили микросервисы, но про coupling и cohesion нет, не слышали,
- На ревью кода гнобили джуна за необработанную ошибку, а в проде процесс запущен через
forever...