Sabotaging projects by overthinking, scope creep, and structural diffing

25.04.2026 · 5 мин

У меня есть знакомый — назовём его Саша — который уже третий год «разрабатывает свой язык программирования». Я спрашиваю его в январе: «Ну как, запустил?». Он отвечает: «Ищу идеальную систему типов». В марте: «Перечитал всё про Rust и Clojure, пытаюсь понять, какой подход лучше». В июле: «Нашёл крутую статью 1979 года про constraint-based языки…»

Знаете, что он сделал за три года? Ничего рабочего. Зато знает всё о теории компиляторов.

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

Две дороги: сделать или закопаться

Кевин заметил интересную закономерность: когда у него появляется идея для проекта, она всегда развивается по одному из двух сценариев.

Путь первый — простой. Идея → делаем → получаем результат → радуемся. Например, решил он как-то выходные с другом сделать полку для кухни и напечатать на 3D-принтере держатели для контейнеров IKEA. Собрались за кофе, обсудили дизайн, покрутили детали в CAD-программе OnShape, распечатали пару итераций, доработали наждачкой — готово. Цель была ясной: просто сделать полку с другом и получить удовольствие.

Путь второй — кроличья нора. Идея → «а давай посмотрим, что уже есть» → находим тонну всего → начинаем думать «а может взять готовое решение?» →, а может написать своё? →, а вдруг оно хуже популярного? →, а вдруг популярное не подходит? → …→ прошло 4 часа…→ ничего не сделано.

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

Критерий успеха — это главное

Почему одни проекты летят вперёд, а другие тонут в бесконечном ресерче?

Кевин считает ключевым фактором то, насколько чётко ты понимаешь собственный критерий успеха.

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

А вот с другим проектом — улучшением инструмента для сравнения кода в Emacs — всё пошло по второму пути. Началось с простой задачи: хотелось более удобного способа сравнивать код в редакторе. Но потом он решил «посмотреть существующие инструменты». И понеслось…

Четыре часа исследования привели его к выводу о существовании «семантического диффинга на уровне PhD» и к фрустрации от того, что все эти инструменты требуют MCP-серверы (Model Context Protocol — протокол для подключения AI к внешним системам), а ему это не нужно.

В какой-то момент он вспомнил исходную цель и сказал себе: «Подожди! Мне просто нужен удобный диф в Emacs — я сам это сделаю за четыре часа».

Но есть проекты-долгожители Кевина — интерфейс для прототипирования железа, язык программирования на стыке Clojure и Rust, язык для CAD (программа для 3D-моделирования, как продвинутый «Фотошоп» для черчения) — которые буквально застряли в этом бесконечном исследовании:

ДВА ТИПА ПРОЕКТОВ
═══════════════════════════════════════

Путь А                    Путь Б
─────────────────         ──────────────────────────────
Идея                      Идея
  │                         │
  ▼                         ▼
Чёткая цель               Размытая цель
  │                         │
  ▼                         ▼
Действие                  "Поискать,
  │                       что есть?"
  ▼                         │
Результат                  ▼              ▲──┐
(готов за выходные)       Бесконечный     │   │
                          ресерч          └───┘

За 3 года:
✓ Полка сделана        ✗ Сотни часов 研究,
✓ Пользуюсь            ✗ Сотни прототипов,
✓ Радость             ✗ Ноль результата
Сравнение путей развития проектов

По его словам, критерии успеха там размыты до предела: то ли он хочет заменить собой Rust/Clojure полностью? то ли только для части задач? то ли просто хочет поиграться с дизайном языков?

Он честно признаётся: исследование ему нравится (он «любит учиться через чтение»), но есть ощущение неудовлетворённости — внутренний критик (страх неудачи?) подавляет желание создавать.

Закон сохранения разрастания функций

Но вот парадокс: даже когда мы начинаем делать дело быстрее (спасибо AI-помощникам), это не обязательно приводит к результату.

Кевин описывает случай с Finda — своим инструментом для поиска файлов по всей системе через fuzzy-сопоставление путей (приблизительный поиск, который находит похожие совпадения даже с опечатками). Он решил переписать его с помощью LLM (Large Language Model — большая языковая модель, типа ChatGPT) и нашёл крутую библиотеку Nucleo.

Всё шло хорошо… пока он не заметил поддержку анкоров (специальные команды типа ^foo для поиска только в начале строки или $bar для конца). И тут началось:

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

Вывод Кевина звучит почти как физический закон:

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

Это напоминает известный принцип YAGNI («You Aren’t Gonna Need It» — «тебе это не понадобится»: не добавляй функции заранее, пока они реально не нужны), который разработчики переизучают всю карьеру снова и снова.

Структурный диффинг — отдельная вселенная

Вторая часть статьи посвящена технической теме структурного сравнения кода (structural diffing — структурное сравнение кода, когда инструмент понимает логику программы, а не просто построчно сравнивает текст).

Обычный diff (классический построчный инструмент сравнения файлов) работает построчно:


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

Есть классный инструмент difftastic (инструмент для семантического сравнения кода на основе treesitter — библиотеки для парсинга синтаксических деревьев, которая превращает код в структуру, которую можно «читать» программе), который использует treesitter, чтобы сравнивать код семантически (семантическое сравнение кода — понимание смысла изменений, а не только букв). Это большой шаг вперёд…, но иногда он ошибается:

Кевин показывает пример, где структура PendingClickRequest была переименована в PendingClickPendingClickRequest — difftastic видит это как удаление одного типа и добавление другого вместо того, чтобы сопоставить их правильно.

Более зрелые решения предлагает semanticdiff.com. Ребята даже написали статью, почему они отказались от treesitter в пользу собственного подхода:

Контекстно-зависимые ключевые слова были постоянным источником раздражения… Грамматика выглядит корректной, но парсинг (разбор текста на составные части по правилам грамматики языка программирования) фейлится из-за того, как работает лексер… Не хочется, чтобы твой инструмент падал просто потому, что кто-то назвал параметр async.

Что с этим делать?

Кевин резюмирует свою мысль так:

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

Это стоит запомнить каждому, кто хочет создавать, а не только изучать.

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

Иногда лучше сделать «плохо», чем сделать «идеально никогда».

Ссылки

Дмитрий Полухин — продуктовый дизайнер. Пишу про разработку, AI и дизайн интерфейсов. Обо мне, контакты и профили.