Подсказка вместо мышления: как автогенерация кода меняет junior и middle за один год

в 4:04, , рубрики: junior, middle, автогенерация кода, архитектура, инженерная зрелость, качество кода, технический долг

Я год наблюдал, как в нашей команде junior и middle разработчики почти полностью пересели на автогенерацию кода. Сначала это выглядело как ускорение. Через несколько месяцев начали всплывать странные эффекты: деградация архитектурного мышления, рост скрытого техдолга и зависимость от подсказок. В этой статье — не морализаторство, а практические наблюдения, конкретные примеры кода и вопросы, которые мне самому пришлось себе задать.

Как всё начиналось: эйфория скорости

Год назад мы разрешили использовать AI-ассистентов без ограничений. Никаких запретов, никаких регламентов. Хочешь — генерируй контроллеры, тесты, мапперы, SQL. Хочешь — проси переписать сервис целиком.

Первые месяцы выглядели прекрасно. Junior закрывали задачи в два раза быстрее. Pull request’ы росли как на дрожжах. Middle начали экономить время на шаблонных вещах.

Но вот что меня смутило: код ревью перестало быть обсуждением решений. Оно стало проверкой синтаксиса и очевидных багов. Архитектурных дискуссий стало меньше.

Я однажды спросил у одного junior:
— Почему ты выбрал именно такую модель данных?
Он честно ответил:
— Ну… ассистент предложил.

И это был не сарказм. Это был факт.

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

Вам не кажется странным, что мы начали быстрее писать код, но медленнее понимать систему?

Архитектурная эрозия: мелкие решения, большие последствия

Через полгода начали всплывать последствия. Особенно в сервисах с высокой нагрузкой.

Вот пример. Нам нужно было реализовать кэширование в Python-сервисе. Junior сгенерировал примерно такой код:

# language: Python

from functools import lru_cache
import requests

@lru_cache(maxsize=128)
def get_user_profile(user_id: int):
    response = requests.get(f"https://api.internal/users/{user_id}")
    response.raise_for_status()
    return response.json()
Подсказка вместо мышления: как автогенерация кода меняет junior и middle за один год - 1

На первый взгляд — отлично. Коротко, понятно, работает.

Но через месяц выяснилось, что кэш живёт в каждом процессе отдельно. В Kubernetes у нас 12 реплик. В итоге кэш не снижал нагрузку почти никак. Более того, stale-данные держались неопределённо долго, потому что инвалидировать lru_cache никто не подумал.

Когда я спросил, почему выбрали такой подход, ответ был простой:
— Это был самый частый пример.

Вот тут и проявляется проблема. Автогенерация даёт типичное решение. Но инженерная зрелость — это понимание контекста. Где граница процесса? Какой SLA? Какие требования к консистентности?

В итоге мы переписали на Redis с явной политикой TTL и централизованной инвалидацией.

# language: Python

import redis
import json
import requests

redis_client = redis.Redis(host="redis", port=6379)

def get_user_profile(user_id: int):
    cache_key = f"user:{user_id}"
    cached = redis_client.get(cache_key)
    if cached:
        return json.loads(cached)

    response = requests.get(f"https://api.internal/users/{user_id}")
    response.raise_for_status()
    data = response.json()

    redis_client.setex(cache_key, 300, json.dumps(data))
    return data
Подсказка вместо мышления: как автогенерация кода меняет junior и middle за один год - 2

Код длиннее. Но теперь он учитывает реальность инфраструктуры.

AI дал быстрое решение. Инженерная зрелость требует видеть систему целиком.

Потеря навыка декомпозиции

Самое неприятное проявилось через девять месяцев. Junior перестали дробить задачи в голове.

Раньше, когда человек писал фичу, он сначала думал:
— Какие слои?
— Какие интерфейсы?
— Где границы ответственности?

Теперь чаще звучит:
— Сейчас сгенерирую сервис и посмотрю.

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

Сгенерированный код выглядел так:

// language: Java

@Service
public class OrderService {

    @Autowired
    private OrderRepository repository;

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    public void process(Order order) {
        if(order.getAmount() <= 0) {
            throw new IllegalArgumentException("Invalid amount");
        }

        repository.save(order);
        kafkaTemplate.send("orders", order.toString());
    }
}
Подсказка вместо мышления: как автогенерация кода меняет junior и middle за один год - 3

Работает? Да.
Чисто? Не совсем.

Нет транзакционной границы. Нет доменной модели. Нет явного слоя валидации. Нет обработки ошибок Kafka. Нет idempotency.

Через год такие куски начинают накапливаться. И система превращается в набор процедур.

Когда я попросил одного middle переработать сервис с учётом DDD, он признался, что давно не проектировал вручную — чаще корректировал сгенерированное.

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

Иллюзия тестового покрытия

Отдельная история — автогенерация тестов.

Казалось бы, идеально: попросил — получил десяток unit-тестов. Покрытие выросло с 45% до 78%. Все счастливы.

Но если открыть тесты, часто видишь что-то вроде:

# language: Python

import unittest
from service import calculate_discount

class TestDiscount(unittest.TestCase):

    def test_positive_amount(self):
        result = calculate_discount(100)
        self.assertEqual(result, 10)

    def test_zero_amount(self):
        result = calculate_discount(0)
        self.assertEqual(result, 0)
Подсказка вместо мышления: как автогенерация кода меняет junior и middle за один год - 4

Формально тест есть.
Но он проверяет happy path. Нет граничных условий. Нет property-based тестирования. Нет сценариев с некорректным вводом, race condition, ошибками сети.

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

Потому что человек, когда пишет тест, вынужден моделировать поведение системы в голове. Это тренирует понимание.

Автогенерация снимает когнитивную нагрузку. А вместе с ней — часть роста.

Что происходит с инженерной зрелостью

Через год я увидел интересную картину.

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

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

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

И вот вопрос вам: если завтра убрать все подсказки, вы сможете за день спроектировать сервис с нуля? Без поиска шаблонов? Без автодополнения целыми классами?

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

Инструмент должен ускорять зрелость, а не подменять её.

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

Автор: dbanet

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js