Как стать автором
Обновить
277.52
H3LLO.CLOUD
Облачный провайдер, которого вам захочется обнять

Вайб-кодинг: практика, о которой почему-то не говорят

Время на прочтение10 мин
Количество просмотров43K
В феврале мир разработки перевернулся с выходом Sonnet 3.7. Потому что вдруг внезапно оказалось, что джуны уже не очень-то и нужны. И нейросетка нормально заменяет мидлов тоже.

Я откидываюсь в кресле, беру наушники и смотрю, как работает LLM. Можно сразу несколько, работающих над разными частями проекта:

image

Пример проекта с прикручиванием аналитики к инфраструктуре:

  • Сначала в GPT 4.5 провёл продуктовые исследования и сформулировал требования.
  • Попросил превратить это в архитектурный план.
  • Отревьюил, поправил тупые ошибки.
  • Затем этот план (как метапромпт) скормил Sonnet в VS Code через плагин Cline. Попросил сначала создать общую структуру, шаблонные имплементации, документацию, спецификации API (protobuf для gRPC, REST API).
  • Архитектурно сразу заложил микросервисы. Sonnet для каждого сервиса подобрал и обосновал оптимальную базу данных (где-то Postgres, где-то ClickHouse и т.д.).
  • Сгенерировал SDK для взаимодействия, примеры использования. Сразу заложил observability: централизованные логи, метрики Prometheus, трейсинг Jaeger/Tempo, дашборды для Grafana.
  • Потом итерационно генерировал код: сначала тесты (End-to-end, BDD), потом имплементацию под эти тесты.
  • Написал манифесты для Kubernetes и Docker Compose для локального запуска.
  • Сгенерировал даже скрипты для тестов REST API через curl и gRPC через gRPCurl.

И всё.

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

Качество LLM-кодинга сильно зависит от языка программирования


Главная проблема — объём блока (точнее, зависимостей) и нормальная обучающая выборка.

Scala


Попытки написать на Scala работающий микросервис по OpenAPI-спецификации провалились.

Модель генерирует код, он не компилируется. Скармливаешь ошибки компиляции — следующая итерация кода содержит ещё больше ошибок.

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

Попытки транслировать код с другого языка (например, с Go) в Scala тоже могут быть проблематичны, особенно если требуется сохранить функциональный стиль Scala — на понятиях PHP, например, такое просто не опишешь.

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

Вот спойлер со сравнением способа мыслить на Scala и Go

Пример кода на Go и Scala



Go


Код на Go форматируется с помощью стандартной утилиты (gofmt), и это — единственный способ выразить задуманную функциональность.

package main

import "fmt"

func main() {
    fmt.Println("Hello, Go!")
}

Этот стиль буквально зашит в инструменты языка.

Scala


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

1. Минималистичное приложение, использующее App Trait (Concise Style)

Этот стиль использует встроенный в Scala трейт App, сокращающий бойлерплейт.

object HelloWorld extends App {
  println("H3LLO, Cloud!")
}

2. Явный метод main (Java-like style)

Тут в коде явно определён метод main. Этот вариант более многословный и явный.

object HelloWorld {
  def main(args: Array[String]): Unit = {
    println("H3LLO, Cloud!")
  }
}

3. Функциональный стиль

В этом примере мы видим, что функции — это first-class citizens. Если скомпилировать этот код, мы по-прежнему увидим H3LLO, Cloud.

val greet: String => Unit = msg => println(s"H3LLO, $msg")

object HelloWorld extends App {
  greet("Cloud")
}

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

4. Однострочный минимализм

На Scala можно впихнуть кучу операций в одну строку, и так тоже катит:

object HelloWorld { def main(args: Array[String]) = println("H3LLO, Cloud!") }


Golang


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

  1. Go — очень квадратно-гнездовой язык. Задачу чаще всего можно решить одним, максимум двумя способами. Нет такого разброса стилей и подходов, как в Scala или даже JS. Это упрощает обучение для LLM: меньше вариативность «правильных» ответов на один и тот же запрос. Эта прямолинейность отличается от C, который хотя и прост на первый взгляд, как автомат Калашникова, но позволяет легко «провалиться» на низкий уровень, иногда незаметно. Go тоже позволяет уйти в сторону ассемблера, но при решении стандартных задач он очень явно описывает путь.
  2. Строгая стандартизация. Встроенный форматер (gofmt), линтеры (golint), общепринятые конвенции — всё это делает кодовую базу на Go очень однородной. Это лучше, чем в Python, где тоже есть линтеры, но вариативности в написании больше. В Go сама идея языка была в том, чтобы код легко читался всей командой.
  3. Качественная и большая обучающая выборка. Огромное количество успешного Open Source написано на Go (Kubernetes и вся его экосистема, Docker, Prometheus, Terraform и т.д.). Вероятно, этот код использовался для обучения моделей с высоким весом, возможно, даже с усилением (как если бы Википедию добавили в базу 10 раз).
  4. Меньше «говнокода». Возможно, Go — язык более молодой, его чаще выбирают уже более опытные разработчики для серьёзных проектов (это сейчас слабое допущение). В отличие от Python или JS, куда порог входа ниже и больше новичков, генерирующих код не самого высокого качества, который тоже попадает в обучающие выборки. Плюс сам язык Go меньше прощает ошибок и не провоцирует написание «кривого» кода так, как это делает, например, JS. Сам язык как бы «не предусматривает» говнокода в той же мере.

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

Rust


Тоже хайповый современный язык. LLM генерят код, который проходит проверку синтаксиса в IDE. Но при попытке сборки (которая в Rust сложнее, чем в Go) вылезает куча ошибок. И вот с исправлением этих ошибок сборки LLM справляются хуже, чем с написанием изначального кода или исправлением ошибок в Go. Вероятно, дело опять же в сложности языка и, возможно, пока ещё менее репрезентативной выборке кода с решёнными проблемами сборки.

Экспериментировали мы не очень много.

Java


Самая интересная ситуация, показывающая принципы LLM-кодинга.

Проблема — в структуре типичного Java-проекта: там везде глубокая вложенность пакетов, сложные цепочки наследования. Чтобы понять, что происходит в одной функции, LLM нужно проанализировать много связанного кода.

Это требует огромного окна контекста: меньше 64к токенов — почти бесполезно для серьёзного Java-кодинга. К счастью, модели с большими окнами появились.

Ещё нужны агенты для исследования кодовой базы. Инструменты вроде Cursor, Cline позволяют LLM «ходить» по проекту. Но и тут проблема: системный промпт агента (который сам по себе может быть большим, на тысячи токенов), прочитанные файлы классов и интерфейсов, дерево проекта — всё это быстро забивает даже большое окно контекста, особенно учитывая, что в Java много мелких лексем (точки, скобки), каждая из которых — токен.

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

image

Идеальное решение (которого пока нет в массовом использовании) — агенты, способные хранить состояние проекта в долговременной памяти, а не только в текущем окне контекста. Тогда LLM сможет «помнить» всю структуру и историю изменений. Сейчас же каждый новый агент начинает почти с нуля.

Сравните с Go: там часто достаточно контекста одного файла + импортов, чтобы понять, что происходит. Добавьте нашему вымышленному программисту не только отвлекающие звонки, но и болезнь Альцгеймера, чтобы он забывал задачу каждые 15 минут, — и вы получите примерное представление о LLM-кодинге на Java.

JavaScript


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

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

Регулярные выражения


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

Про Python рекомендую вот эту публикацию.

image

Что получается


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

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

Если это есть и он готов выступать тимлидом LLM-агентов, то эффективность растёт в разы.

Разработчик общается с LLM так же, как раньше с джунами — ставит задачу, проверяет, корректирует.

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

Код-ревью становится важнее: генерируемый код нужно внимательно читать.

Понимание архитектуры и умение читать чужой код становятся критически важными.

Меняется структура команд: потребность в джунах, которые в основном занимаются рутиной, снижается. Мидлы должны быстро расти до уровня Middle+, способных работать с архитектурой. Сильно растёт ценность Senior и лидов, способных грамотно ставить задачи и контролировать результат.

Результат: проект, который силами небольшой команды делался бы минимум месяц до начального рабочего состояния, у нас был собран за три дня. Ещё примерно столько же ушло на ревью (в том числе по ИБ), ручное тестирование крайних случаев и подготовку к продакшену. Затраты на токены — около 150 $.

Уверенность в коде? После моего ревью — высокая. Если сравнивать сгенерированный код до ревью с кодом джуна до ревью — уверенности в LLM-коде у меня больше. Он сразу пишет с учётом observability, тестов, лучших практик (если правильно попросить), даже с интеграцией токенов для защиты вызовов. Джуна этому ещё учить и учить.

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

Правильное техническое задание становится самой дорогой и важной частью.

Пайплайн «задача -> реализация» у нас уже радикально упростился. При обсуждении фич мы меньше зависим от опыта конкретного разработчика в конкретной технологии. Нужно интегрировать Cassandra? Не проблема, даже если у человека нет 5 лет опыта с ней. LLM выдаст «курс молодого бойца», поможет спланировать интеграцию, сгенерирует код. Разработчик контролирует процесс, вносит правки с учётом специфики нашего Go-кода. Стек стал гибче. Мы можем выбирать лучшие технологии под задачу, не ограничиваясь текущими знаниями команды. LLM помогает быстро вкатиться и интегрировать. Разработчик при этом получает реальный опыт с новыми инструментами.

Практическая скорость выросла, по нашим оценкам, примерно вдвое.

За один спринт (две недели) команда делает то, что раньше заняло бы месяц, и это при работе над новым проектом с нуля. Создать новый микросервис по образу и подобию существующих? Даёшь LLM пример, и он генерирует всю обвязку. Остаётся наполнить бизнес-логикой.

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

И давайте ещё раз: меньше совещаний. Это важно. Синки только по необходимости.

Освободившееся время уходит на ревью, продумывание сложной логики и тот самый «вайб-кодинг» — параллельную работу, где на одном экране код генерит LLM, на другом — правишь или пишешь сам, потом меняешься ролями для ревью. Работа становится более «дирижёрской». Даже DevOps-задачи частично можно переложить на LLM (генерация Dockerfile, K8s-манифестов).

Но есть и проблема контроля. Иногда тяжело ограничить место изменений. Просишь LLM поправить что-то в одном конкретном месте, пишешь «только здесь, только это», но нет гарантии, что он не затронет что-то ещё в другом файле или модуле.

Интересно, что даже размер микросервиса теперь может определяться не только бизнес-контекстом, но и размером контекста LLM — микросервисы стали де-факто стандартом, потому что с ними LLM справляются легче.

Как мы теперь нанимаем


Мы требуем уметь хорошо кодить руками на входе.

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

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

Этап джуна придётся проскакивать на тренировках, а не на реальных проектах.

Сам промпт-инжиниринг из области заученных лайфхаков превратился в умение сформулировать задачу. Модели достаточно развились, чтобы понимать свободные формулировки. Осталось только внести в них смысл — ради смысла и нужен профессионал. Нужно декомпозировать задачу, учесть все нюансы: версии библиотек и фреймворков (сказать: «Next.js», не уточнив App Router vs Pages Router — получить не то), протоколы взаимодействия (не указать gRPC — получить HTTP по умолчанию), конкретные требования к архитектуре, базам данных, безопасности.

Промптинг становится итерационным. Мы часто сначала генерируем верхнеуровневую архитектуру (например, в Markdown), правим её руками. Потом генерируем схемы взаимодействия между микросервисами (Protobuf, GraphQL, OpenAPI, SQL DDL) — это становится критически важным шагом, и только потом используем эту схему как часть промпта для генерации кода самих сервисов. Потом — документацию. Потом генерируем тесты (интеграционные тут особенно важны). Потом — структуру файлов и сигнатуры функций с комментариями. И только потом — сам код.

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

Ценность навыка написания кода снижается в пользу навыка чтения чужого кода. Однако и тут есть нюанс: после оперативного запуска нового кода ценность смещается в сторону знания кодовой базы. И именно этот фактор делает разработчиков незаменимыми (вспоминаем про bus factor, которого боятся все инвесторы).

Тим О'Райли недавно написал отличную статью о том, как меняется кодинг. Его мысль перекликается с нашими наблюдениями: LLM — это следующий уровень абстракции после фреймворков. Если раньше многие разработчики использовали фреймворк, не до конца понимая, как он работает «под капотом», то скоро появится целый класс специалистов, которые будут «просить LLM сделать», не зная деталей реализации. Программирование как навык и как бизнес ждут серьёзные трансформации в ближайшие 5–10 лет. Да, похожие вещи говорили с появлением BASIC и других высокоуровневых языков, и это нормально — каждая такая революция меняла ландшафт.

Как реагируют программисты? Похоже, сообщество разделилось на несколько групп, как отмечалось в Wired. Первая группа верит, что разработчики будут не нужны. Вторая считает LLM продвинутыми стажёрами, верит в рутину, но не верит в понимание. И реалисты — видят усилитель возможностей и личной эффективности. Мы скорее относим себя к реалистам. LLM не убьёт профессию, но сильно её изменит. Мир разработки переживает переломный момент, и важно адаптироваться. Работы не становится меньше, она становится другой.
Теги:
Хабы:
+65
Комментарии196

Публикации

Информация

Сайт
h3llo.cloud
Дата регистрации
Дата основания
Численность
11–30 человек