Агент пишет на любом популярном языке одинаково бегло. Но ошибается он на них по-разному, точнее, по-разному ошибка всплывает. Выдуманный метод репозитория на Java не проходит компиляцию, и вы видите его через секунду. Тот же выдуманный метод на Python проходит тесты успешного пути и падает у пользователя через неделю. Агент один и тот же, задача та же, разница только в том, сколько выдумок доживёт до продукта и как быстро вы их поймаете.
Когда код пишет агент, а вы его принимаете, язык перестаёт быть вопросом вкуса: он первая линия обороны от галлюцинаций. После статьи вы будете знать, какие пять свойств языка агент усиливает, что делать, если язык уже выбран и это Python, когда спор о стеке не стоит времени вовсе и как выбирать между Java и Go.
Где всплывает ошибка агента: компилятор или прод
Возьмём самую частую ошибку агента: он уверенно вызвал метод, которого нет. repository.findByEmailAndActive(email), user.subscription.renew(), client.retry_with_backoff(). Имена правдоподобные, в похожих проектах такие есть, и модель дописала их по памяти.
Один и тот же агент, одна и та же ошибка. На статическом языке её останавливает первый красный сигнал ещё до просмотра кода; на динамическом она проходит тесты успешного пути и всплывает у пользователя.
На языке со строгими типами компилятор не находит метод и останавливает сборку. Ошибка стоит минуту: агент видит сообщение компилятора и сам её чинит, вы об этом чаще всего даже не узнаете. На языке с динамическими типами вызов несуществующего метода — обычный код, пока до него не дошло выполнение. Тесты проверяют успешный путь, где этого вызова нет; в проде через неделю ветка выполняется, и пользователь видит AttributeError: 'NoneType' object has no attribute 'renew'. Ошибка стоит инцидента.
Для приёмки это значит следующее. Первое, что делают с кодом от агента, ещё до чтения, — прогоняют всё, что даёт однозначный красный сигнал: сборку, проверку типов, линтер, тесты. На строгом языке этот набор ловит большую часть выдумок сам. На динамическом языке набор надо собирать руками, и без него приёмка начинается с чтения кода глазами, а глаза выдуманный метод не видят: он выглядит как настоящий.
Для одного продукт-инженера это важнее, чем для команды: у команды есть ревьюеры, у вас только компилятор, инструменты и дисциплина приёмки.
Пять свойств языка, которые агент усиливает
Для одноразового скрипта разница между языками сглаживается: агент знает синтаксис любого популярного языка достаточно, чтобы написать рабочий код. Для продукта, который живёт годами, разница огромная, потому что агент не просто пишет на языке, а усиливает его свойства: строгий язык делает его ровнее, свободный — разнообразнее, и через полгода продукт из десяти сессий агента состоит из десяти стилей.
Объём в обучающей выборке
Крупные языки (Python, JavaScript, Java, TypeScript, Go, C#) представлены в обучающих данных на порядки больше, чем редкие (Haskell, Erlang, Ada). Чем больше данных, тем точнее модель воспроизводит идиомы и библиотеки, тем меньше выдумок. Это самый очевидный критерий, но один он ничего не решает: иначе Python был бы безусловным лидером, а на практике он проигрывает Java именно на следующем свойстве.
Строгость типов
Языки со статическими типами (Java, Kotlin, TypeScript, Rust, C#, Go) ловят выдумки на этапе сборки, и это разобрано выше. Языки с динамическими (Python, JavaScript, Ruby) обнаруживают их во время работы. Отсюда парадокс: Python, один из самых представленных языков в обучающих данных, для продукта хуже Java, потому что агент пишет на нём не меньше выдумок, а живут они дольше.
Сила соглашений
Чем меньше способов «как правильно», тем меньше расхождений между сессиями агента. У Go один способ форматировать код, один способ разрешать зависимости, один способ обрабатывать ошибки. У Scala или Ruby каждый второй проект изобретает свои встроенные мини-языки, и агент в каждой сессии выбирает свой.
Java здесь посередине: Spring Boot задаёт сильные соглашения, но без него и без явного свода правил получаются разнокалиберные сервисы. Это лечится не языком, а исполняемым стандартом: правилами, которые агент применяет на каждом изменении. TypeScript с фреймворком (Angular, NestJS) ведёт себя как Java со Spring; чистый Node.js без фреймворка даёт больше шума.
Явность важнее краткости
Часто звучит: «для агента главное поменьше кода, токенов меньше, контекст легче». Это верно наполовину, и граница проходит не там, где кажется. Подробно об этом следующий раздел.
Инструментарий
Линтеры, форматтеры, статический анализ, проверка типов, тесты архитектуры. Чем строже инструменты, тем быстрее и точнее красный сигнал при приёмке. Java тут вне конкуренции: Checkstyle, SpotBugs, ArchUnit, анализ уровня IntelliJ. TypeScript близко: ESLint с правилами по типам, Prettier, tsc. Python догоняет (mypy, ruff, Pydantic), но экосистема разрозненна: каждая команда выбирает свой набор, и агент не знает заранее, какой.
Явный код, а не короткий
Что в тезисе «меньше кода» правда. Шаблонный код, написанный руками, — реальная нагрузка: агент пишет тысячный геттер, тратит токены, иногда промахивается; кодогенерация (Lombok в Java, data class в Kotlin, dataclass в Python, встраивание структур в Go) снимает это начисто. Длинный файл с противоречиями — тоже нагрузка: чем больше и шумнее контекст, тем хуже модель находит в нём нужное. Дело не в «усталости», которой у модели нет, а в разбавлении важного.
Что в этом тезисе миф. Точность агента зависит от явности, а не от краткости. Явные аннотации и типы (@Transactional, @PreAuthorize, декораторы в NestJS, Protocol в Python) делают код длиннее, но каждое решение видно локально: агент прочитал двадцать строк обработчика и понял всё. Пять строк на Haskell выглядят элегантно, но половина поведения выводится из типов в других файлах, и агент, следуя по цепочке, путается чаще.
Хуже всего краткость через метапрограммирование. Вызов User.find_by_email_and_active(email, true) в Rails короче явного запроса, но такого метода в проекте никто не писал: фреймворк достраивает его на лету из имени через механизм method_missing, который перехватывает обращение к несуществующему методу. Агент генерирует похожие вызовы, и одни работают, другие нет, а узнать, какие, можно только выполнением. На Java тот же вызов был бы repository.findByEmailAndActive(...): длиннее, но агент видит, объявлен ли метод в интерфейсе, а компилятор проверяет.
Отсюда формула: агент любит явный код и минимум шаблонного повторения. Кодогенерация на этапе компиляции, когда исходник короткий, а семантика явная, — хорошо. Динамическая магия во время работы, когда исходник короткий, а поведение скрыто, — плохо. Стеки, которые попадают в это «сладкое место»:
- Java с Lombok, records и сгенерированным jOOQ — исходника в разы меньше, чем у «голой» Java, а типы и аннотации на виду;
- Kotlin со Spring —
data classи nullable-типы делают то же нативно, чуть короче и чуть менее прозрачно; - Go — стандартная библиотека и соглашения намеренно скучные, вариантов мало, агент предсказуем;
- TypeScript с NestJS — декораторы делают то же, что аннотации Spring;
- Python с Pydantic и mypy —
BaseModelи строгие подсказки типов приближают поведение к статическим языкам.
Что не работает: встроенные мини-языки и метапрограммирование (неявные преобразования в Scala, method_missing в Ruby, метаклассы в Python) и конфигурация, размазанная между кодом и YAML: источник правды становится не найти ни человеку, ни агенту.
Если язык уже выбран
Самый частый случай: продукт на Python или JavaScript уже есть, и переписывать его никто не будет. Хорошая новость в том, что все пять свойств выше — про то, как быстро всплывает ошибка, а это докручивается инструментами без смены языка. Плохая новость в том, что докручивать придётся руками, и порядок имеет значение: первые шаги дают больше всего.
Python. Первым делом проверка типов как блокирующая ступень: подсказки типов на границах модулей и mypy в CI, который не пропускает изменение с ошибкой. Это то, что превращает выдуманный метод из инцидента в красную строку до просмотра кода. Вторым — Pydantic для всех входных и выходных моделей: агент получает явную схему вместо словарей, и половина «а что тут лежит» исчезает. Третьим — ruff с настройками строже стандартных, в pre-commit и в CI. Тесты на бизнес-логику — не «покрытие 80%», а каждая бизнес-функция с ожидаемым значением из требования. Полное покрытие типами старого кода не нужно: включайте проверку для новых файлов и для тех, которые агент трогает.
JavaScript. Первый шаг — TypeScript, и это стоит дешевле, чем кажется: файлы переименовывают постепенно, а tsc в режиме проверки без сборки работает и на смешанном проекте. Если TypeScript невозможен, типы в JSDoc плюс тот же tsc в режиме проверки. Второй шаг — ESLint с набором strict-type-checked из typescript-eslint: он проверяет правила с учётом типов, а не только по тексту. Третий — pre-commit через Husky и lint-staged.
C++. Современный диалект (C++17 и новее) и запрет на сырые new/delete; clang-tidy в строгом режиме; санитайзеры адресов и потоков на всех прогонах тестов; заголовки подключать явно, а не рассчитывать, что символ приедет транзитивно через чужой include, иначе ни человек, ни агент не поймут, откуда он.
Общая идея: сильный язык даёт среду для агента из коробки, слабый требует достройки, и за достройку платят инженерным временем. Но это конечная и предсказуемая работа, а не «всё переписать».
Когда язык не главное
Все пять свойств стоят спора только если продукт будет жить и меняться. Есть случаи, когда спор о стеке не стоит и часа:
- срок неделя и продукт умрёт через квартал. Прототип для проверки гипотезы пишут на том, что быстрее у вас лично; выдумки агента здесь дешевле, чем день на настройку инструментов;
- домен простой. Сервис, который принимает форму и кладёт в базу, одинаков на любом языке; строгие типы не спасают там, где спасать нечего;
- интеграция с готовой экосистемой. Обучение моделей — Python, потому что там библиотеки; мобильный клиент — Swift или Kotlin, потому что так устроена платформа. Язык здесь выбран за вас, и правильный ответ — достройка инструментов, а не спор;
- команда знает один язык. Смена языка ради агента стоит дороже, чем настройка проверок для языка, который команда знает. Выбор языка «под агента» — для нового продукта, а не для живого.
Java или Go
Раньше выбор между ними во многом диктовала скорость набора: Python быстрее всех, Spring Boot догонял за счёт кодогенерации, Go тормозил на многословной обработке ошибок, «голая» Java без фреймворков была реально медленной, отсюда и стереотип. Когда пишет агент, разница в скорости набора стёрта: все три пишутся примерно за одинаковое время. Остаётся то, что от скорости набора не зависит.
| Что сравниваем | Java (Spring, Lombok) | Go |
|---|---|---|
| Сложность доменной модели | records, sealed types, дженерики, инструменты для DDD | простая система типов, дженерики недавно, агрегаты получаются костыльными |
| Соглашения | Spring Boot доминирует, но без свода правил разнобой | один способ всего: gofmt, обработка ошибок, modules |
| Глубина анализа кода | IntelliJ, ArchUnit, SpotBugs | vet, golangci-lint — хорошо, но менее глубоко |
| Старт и память | JVM прогревается секунды | бинарь стартует мгновенно |
| Регулируемые отрасли | стандарт в банках и госкомпаниях | местами требует обоснования |
Берите Java для сложной доменной модели с инвариантами и событиями, долгоживущего продукта с большой командой, регулируемой отрасли и глубокой интеграции с корпоративным стеком. Берите Go для сетевых сервисов, шлюзов и инфраструктурных утилит, высокой пропускной способности с простой логикой, быстрого холодного старта и маленькой команды, которой важна низкая когнитивная нагрузка.
Самый частый вариант на практике гибридный: Java или Kotlin для доменных сервисов (заказы, каталог, платежи), Go для периферии (шлюз, проксирование, лёгкие точки входа). Вопрос «Java или Go» — не «или», а «где что».
Несколько языков в одном продукте
Полный стек на одном языке (TypeScript на клиенте и сервере) проще держать в голове и в правилах агента, чем зоопарк, и это честный аргумент. Но у него есть цена, о которой забывают: серверный TypeScript без фреймворка даёт больше шума, чем Java со Spring, и «один язык» на практике означает «Node.js в бэкенде», а не «Java во фронтенде». Для продукта с толстым доменом это плохая сделка: доменная логика получает более слабый стек ради того, чтобы фронтенд и бэкенд совпали по синтаксису.
Обычно продукт всё равно двуязычный: бэкенд плюс TypeScript во фронтенде, и это нормально. Дорого становится, когда языков три и больше и каждый претендует на бизнес-логику. Тогда:
- один основной язык для бизнес-логики, остальные — для специализированных задач с чёткой границей (Python для машинного обучения, Go для сетевого ввода-вывода);
- один набор архитектурных принципов поверх всех языков: код разный, устройство одно;
- правила для агента — под каждый язык отдельно. Это инвестиция, но на большой команде она окупается.
Что не влияет
- Возраст языка. Java старше Rust на двадцать лет и дружелюбнее к агенту: больше данных, сильнее соглашения, мощнее инструменты.
- Производительность. Агент пишет одинаково корректный код на «быстром» Rust и на «медленном» Python; скорость работы программы — отдельный вопрос.
- «Современность». Scala и Haskell современнее Java по системе типов, но агент на них слабее: данных меньше, соглашения разрознены.
- Личные предпочтения. Важны для найма и удержания, к дружелюбию к агенту отношения не имеют.
Что меняется в правилах агента при смене языка
Методология не привязана к языку: устройство сервиса (операция, обработчик, репозиторий), спецификация как код, исполняемые правила ревью применимы на Java, Kotlin, Go, TypeScript и Python одинаково. Меняется слой под ней, и полезно знать, какой именно.
Остаётся тем же: правило зависимостей между слоями, форма контракта операции, список крайних случаев, которые проверяют, порядок приёмки. Меняется: чем ловится нарушение (ArchUnit на Java, dependency-cruiser в TypeScript, go vet и линтер на Go, import-linter в Python), какие типы объявляют границу (records и sealed интерфейсы, data class, структуры и интерфейсы, Pydantic-модели), как оформлена ошибка (исключение, Result, error в возвращаемом значении). Правила для агента пишут ровно на этот слой: «инвариант живёт в агрегате» одно для всех, «агрегат это record с приватным конструктором» — только для Java.
Для Java это самый зрелый трек: полный набор инструментов даёт агенту стабильные шаблоны, а требования аудита в регулируемых отраслях делают её стандартом для таких команд. Для остальных языков принципы те же, инструменты свои.
Коротко
- Агент ошибается на всех языках одинаково, а всплывает ошибка по-разному: на статическом языке — в компиляторе за минуту, на динамическом — в проде через неделю.
- Приёмка кода от агента начинается с красного сигнала, а не с чтения: сборка, проверка типов, линтер, тесты. Строгий язык даёт этот набор из коробки, динамический требует собрать его руками.
- Пять свойств, которые агент усиливает: объём в обучающих данных, строгость типов, сила соглашений, явность против краткости, инструментарий.
- Явный код, а не короткий: кодогенерация на этапе компиляции — хорошо, динамическая магия во время работы (
method_missing, неявные преобразования, метаклассы) — плохо. - Язык уже Python — не переписывать: mypy как блокирующая ступень, Pydantic на все модели, ruff, тесты с ожидаемым значением из требования. JavaScript — TypeScript постепенно, ESLint с проверкой по типам.
- Язык не главное, когда продукт короткоживущий, домен простой, экосистема диктует язык или команда знает один.
- Java — для сложного домена, большой команды и регулируемых отраслей; Go — для сети, шлюзов и быстрого старта; на практике чаще гибрид.
- Один язык на клиент и сервер упрощает правила агента, но платит за это слабым серверным стеком; правила пишут под каждый язык отдельно, принципы одни.
Что почитать дальше
- Приёмка результата AI — что проверять после красного сигнала: контракт, границы, крайние случаи.
- Как ревьюить код, который написал агент — что отдать машине, а что смотреть глазами, на любом языке.
- Настройка агентов — правила проекта, где живёт языковой слой.
- Исполняемый стандарт — как правила проекта делают агента ровным на языке со слабыми соглашениями.