AI пишет код. Зачем тогда методология?
Самое частое возражение к Use Case Pattern: «зачем учить методологию, если Claude и так пишет код». Полный ответ с экспериментом, пятью сценариями где AI без методологии разваливается, разбором что такое «общий контекст» технически, и историческими параллелями.
Это самое частое возражение к Use Case Pattern, его задают почти все, кому я первый раз показываю методологию. В разных формулировках:
— Зачем мне твой паттерн, мне Claude и так напишет код. — Сейчас можно просто описать что нужно — и сгенерируется. — Методологии — это для эпохи, когда AI не умел. — Что вы все цепляетесь за слойную архитектуру, дайте AI задачу — он справится.
Я с этим согласен. Частично. И именно потому, что согласен частично, я строю Use Case Pattern.
Эта статья — длинный ответ на возражение. Цель: убедить вас не в том, что AI плох (он отличен), а в том, что методология в эпоху AI становится не ненужной, а более ценной. Чем умнее становится AI, тем больше зависит от того, какие правила вы ему дадите.
Где AI без методологии действительно достаточно
Начну с признания. Для целого класса задач AI без всякой методологии справляется на ура:
- Прототип, MVP, проверка гипотезы. Один разработчик, один сервис, цель — за две недели проверить, нужно ли это рынку. Здесь методология тормозит. Бери Claude, описывай задачу, получай код. Через три месяца либо переделаешь с нуля, либо выбросишь. Любой методологический оверхед — деньги в мусорку.
- Скрипты, утилиты, ad hoc автоматизация. Один файл на Python, который раз в неделю собирает отчёт. Никто его не будет поддерживать командой. Никто не будет ревьюить. Если работает — работает.
- Рефакторинг с понятной целью. «Вынеси этот метод в отдельный класс», «перепиши switch на pattern matching» — Claude отлично делает это в любом контексте. Нужна не методология, а тесты и здравый смысл.
- Изоляция учебных задач. «Покажи как работает Saga на простом примере». Здесь методология даже мешает — упрощённый пример должен быть упрощённым.
- Личный пет-проект. Один человек, один кодовый стиль (даже если он меняется каждый месяц), нет команды, нет долгосрочной поддержки. Делайте как удобно.
Если ваш контекст — один из этих, закройте эту статью. UCP вам не нужен. Весь дальнейший разговор — про другой контекст.
Где AI без методологии разваливается
Контекст, в котором методология нужна — это команда, продукт, время. Любые два из трёх — уже причина задуматься.
В backend-кластере, которым я руковожу, команда — 4 фича-команды плюс платформенная, 20+ инженеров. Продукт — государственная IT-система с 5+ годами планируемой жизни. Время — на годы вперёд, не на спринт. И в этом контексте я наблюдал пять сценариев, в которых AI без методологии раз за разом разваливается.
1. Несогласованность между сессиями
Поставьте Claude трижды одну и ту же задачу: «напиши Spring Boot сервис обработки заказов с REST API на создание, оплату, отмену». В трёх отдельных чатах, без всякого общего контекста.
Получите три разных решения. Все три — рабочие. Все три — типизированные, с тестами, с обработкой ошибок. Но:
- В первом — три отдельных контроллера:
OrderController,PaymentController,CancelController. - Во втором — один
OrderControllerс тремя методами:POST /orders,POST /orders/{id}/payment,POST /orders/{id}/cancel. - В третьем —
OrderControllerс методами иOrderCommandHandlerдля команд.
Каждое решение по отдельности нормально. Вместе — три разных стиля проектирования API в одном кодбазе. Через месяц у вас 30 сервисов, каждый чуть-чуть по-другому. Через год новый разработчик в команде проводит первую неделю на «понять, как у нас принято».
Решение Claude зависит от того, что чаще встречалось в его training data на момент конкретного запроса. Без явных правил извне он не выберет ваш стиль — он выберет средний из всех Java-разработчиков мира.
2. Несогласованность между разработчиками
Та же задача, но решает её разработчик A, B, C — каждый в своей сессии Claude.
A — работал в Spring до этого, попросит Claude использовать Spring Data JPA. B — пришёл из Kotlin/Ktor, попросит Claude сделать на чистых JDBC + DTO. C — недавно прочитал Vaughn Vernon, попросит Claude сделать «по DDD».
Все трое получат от Claude рабочий код. Все трое будут считать его «правильным» в своём контексте. И после слияния PR-ов в main у вас три сервиса в одном репозитории, написанные в трёх несовместимых стилях, и никто из троих не сделал ничего «неправильно».
Это не баг Claude. Это последствие того, что у Claude нет источника правды о вашем стиле, кроме промпта конкретного разработчика.
3. Несогласованность во времени
Вы делаете сервис в январе. Claude генерирует красивый код. Через год — добавляете новую фичу. Снова просите Claude.
За год Claude обновился до новой версии. Изменилось training data. По-другому понимает «лучшие практики». Сгенерирует код, который не похож на ваш январский. На ревью замечают: «у нас обычно не так». Но обычно как? Промпт для Claude в январе никто не сохранял. Договорённости — в головах разработчиков, многие из которых уже ушли.
Вы получаете дрейф — каждый новый кусок кода чуть-чуть в другом стиле, чем то, что было до. Через 3-5 лет код выглядит как археологический раскоп: «вот это писали в 2025, видно по неймингу; а это в 2027, тут уже другая школа».
4. Сложный домен и архитектурные trade-off
Claude не делает архитектурных решений. Он применяет шаблоны.
Спросите его: «у меня заказ, который должен быть оплачен через внешний шлюз, потом отгружен через курьерку, и если отгрузка провалилась — деньги вернуть. Как это сделать?»
Claude напишет один из вариантов:
- Прямые вызовы (oh well)
- Synchronous orchestration через transaction
- Saga с компенсациями
- Event-driven через Outbox
- 2PC через Atomikos (если ему так захочется)
Все варианты он напишет грамотно. Но выбор между ними — это архитектурное решение, которое зависит от десятка факторов: SLA, сколько денег готовы потерять при отказе, какие соседние команды есть, можно ли просить заказчика подождать 5 секунд, насколько критичен idempotency, и т.д.
Без вашей методологии Claude выберет «самое популярное в training data» решение для подобной задачи. Это будет грамотно, но необязательно правильно для вашего контекста.
С методологией — у Claude есть инструкция: «у нас в проекте уровень зрелости 3 (DDD), для распределённых процессов используем Saga + Outbox, идемпотентность ключевая, синхронных распределённых транзакций избегаем». Тогда Claude выбирает не среднее, а ваше.
5. Регрессия качества по умолчанию
Claude по умолчанию делает то, что выглядит правильным, но не всегда то, что правильно.
Несколько типичных дефолтов «голого» Claude:
- При ошибке возвращает
nullилиOptional.empty()без логирования - В catch-блоке пишет
// log error and continueбез явной обработки - Идемпотентность не реализует, если её явно не попросить
- Метрики не добавляет (никто же не упомянул метрики)
- Тайм-ауты на внешние вызовы — дефолтные (то есть бесконечные)
- DTO возвращает наружу как есть, даже если внутри domain-объект
Каждый из этих дефолтов технически работает. Но в проде через полгода вы ловите: тихие ошибки которые никто не видел, висящие соединения с упавшим внешним сервисом, дублирующиеся заказы потому что нажали «купить» дважды.
С правилами методологии Claude поведение меняется: «не возвращай null, возвращай типизированную ошибку», «обработка ошибок обязательна, swallow запрещён», «все внешние вызовы с тайм-аутом из конфига», «DTO != domain». Эти правила должны быть проверяемыми — иначе их никто не соблюдает.
Демонстрация: один и тот же бриф, два прохода
Чтобы это было не слова, разберём конкретный пример.
Бриф (одинаковый для обоих проходов): «Сделай Spring Boot сервис, который принимает команду оплатить заказ. Должен вызвать платёжный шлюз, получить ответ, сохранить статус. Если шлюз не отвечает — повторить. Если отказ — оставить заказ в pending.»
Проход 1: Claude без методологии
Дайте этот промпт Claude в чистой сессии. Получите примерно такой код:
@RestController
@RequestMapping("/api/payments")
public class PaymentController {
@Autowired
private PaymentService paymentService;
@PostMapping("/{orderId}/pay")
public ResponseEntity<String> pay(@PathVariable String orderId) {
try {
paymentService.processPayment(orderId);
return ResponseEntity.ok("Payment processed");
} catch (Exception e) {
return ResponseEntity.status(500).body("Payment failed: " + e.getMessage());
}
}
}
@Service
public class PaymentService {
@Autowired
private PaymentGatewayClient gateway;
@Autowired
private OrderRepository orderRepository;
public void processPayment(String orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new RuntimeException("Order not found"));
for (int i = 0; i < 3; i++) {
try {
PaymentResult result = gateway.charge(order.getAmount());
if (result.isSuccess()) {
order.setStatus("PAID");
} else {
order.setStatus("PENDING");
}
orderRepository.save(order);
return;
} catch (Exception e) {
if (i == 2) throw new RuntimeException("Gateway unavailable", e);
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
}
}
}
}
Это рабочий код. Тесты к нему напишутся, в проде он будет крутиться. Но у него куча проблем для production-системы:
String orderIdвместо типизированногоOrderIdRuntimeExceptionбез иерархии — все ошибки одинаковыеString status = "PAID"— magic string, не enum- Нет идемпотентности: повторный POST приведёт к повторному списанию
- Бизнес-логика в Service, не в домене
- Retry-логика руками вместо Resilience4j
- Платёжный шлюз без circuit breaker и тайм-аута
- DTO для ответа — голая String
Каждое из замечаний — пункт code-review. На команде из 20 человек по таким PR-ам уходит больше времени, чем на сам код.
Проход 2: Claude с методологией
Теперь то же самое, но Claude знает: «у нас Use Case Pattern уровень 3, REST API style guide, наш Java code style, все внешние вызовы через resilience4j, идемпотентность по Idempotency-Key обязательна, доменная логика в агрегате».
Это знание загружается через Claude Code skills (ucp-pattern-design, ucp-api-design, ucp-java-style-review). Не нужно повторять в каждом промпте — скиллы применяются автоматически.
Тот же бриф даст примерно:
// adapter/in/rest/OrderController.java
@RestController
@RequestMapping("/v1/orders")
@RequiredArgsConstructor
@InboundAdapter("REST controller for order commands")
class OrderController {
private final UseCaseDispatcher dispatcher;
@PostMapping("/{orderId}/pay")
PayOrderResponse pay(@PathVariable OrderId orderId,
@RequestHeader("Idempotency-Key") IdempotencyKey idempotencyKey,
@AuthenticationPrincipal Buyer buyer) {
return dispatcher.dispatch(new PayOrderCommand(orderId, buyer.id(), idempotencyKey));
}
}
// app/usecase/PayOrderCommand.java
record PayOrderCommand(OrderId orderId, BuyerId buyerId, IdempotencyKey idempotencyKey)
implements UseCaseCommand<PayOrderResponse> {}
record PayOrderResponse(OrderId orderId, OrderStatus status, PaymentReference reference) {}
// app/usecase/PayOrderHandler.java
@InboundPort
@RequiredArgsConstructor
class PayOrderHandler implements UseCaseHandler<PayOrderCommand, PayOrderResponse> {
private final OrderRepository orders;
private final PaymentGateway gateway;
private final IdempotencyStore idempotency;
private final DomainEventPublisher events;
@Override
@Transactional
public PayOrderResponse handle(PayOrderCommand cmd) {
return idempotency.executeOnce(cmd.idempotencyKey(), () -> {
Order order = orders.findById(cmd.orderId())
.orElseThrow(() -> new OrderNotFoundException(cmd.orderId()));
PaymentReference ref = gateway.charge(order.totalAmount(), cmd.idempotencyKey());
order.markPaid(ref); // domain logic — invariants checked inside
orders.save(order);
events.publish(order.pullEvents());
return new PayOrderResponse(order.id(), order.status(), ref);
});
}
}
// adapter/out/payment/PaymentGatewayAdapter.java
@OutboundAdapter("HTTP client to payment gateway")
@RequiredArgsConstructor
class PaymentGatewayAdapter implements PaymentGateway {
private final PaymentGatewayHttpClient client;
@Override
@Retry(name = "paymentGateway")
@CircuitBreaker(name = "paymentGateway", fallbackMethod = "fallback")
@TimeLimiter(name = "paymentGateway")
public PaymentReference charge(Money amount, IdempotencyKey idempotencyKey) {
var response = client.charge(new ChargeRequest(amount, idempotencyKey));
return new PaymentReference(response.transactionId());
}
private PaymentReference fallback(Money amount, IdempotencyKey key, Throwable t) {
throw new PaymentGatewayUnavailableException(key, t);
}
}
Сравните с первым проходом. Без выкладок:
- Типизированные value objects (
OrderId,IdempotencyKey,Money,PaymentReference) вместо String - Иерархия исключений (
OrderNotFoundException,PaymentGatewayUnavailableException) - Идемпотентность — обязательна и встроена через
executeOnce - Бизнес-логика — в
order.markPaid(ref), инварианты проверяются в домене - Resilience4j — через аннотации (
@Retry,@CircuitBreaker,@TimeLimiter) - Платёжный шлюз — за интерфейсом
PaymentGateway(порт), реализация — адаптер - Hexagonal-аннотации (
@InboundAdapter,@OutboundAdapter,@InboundPort) - DTO ответа — типизированный
record - Паттерн
UseCase + Handler + Dispatcherсоблюдён
И главное: это не результат гениального промпта. Это результат того, что Claude знает правила вашего проекта. Любой разработчик в команде получит на этот бриф то же самое решение, потому что скиллы применяются ко всем сессиям одинаково.
Размер промпта при этом не больше. «Реализуй use case оплаты заказа» — и Claude применяет всё описанное выше автоматически.
Что такое «общий контекст» технически
Возражение «зачем методология, AI и так пишет код» исходит из неявного допущения, что AI работает в одиночку. Один промпт, один ответ.
В реальности AI всегда работает в каком-то контексте. Вопрос только в том, какой это контекст:
- Без методологии: контекст = training data (среднее всех Java-разработчиков мира) + текст промпта (то, что разработчик помнит написать).
- С методологией: контекст = ваши явные правила (скиллы), ваша история решений (Memory Bank), ваш шаблон спеки (16 разделов с типизированными полями).
Это три слоя:
Слой 1: Скиллы (правила игры)
Claude Code skill — это не подсказка. Это правила игры, которые Claude применяет автоматически каждый раз, когда видит подходящий контекст.
Пример: ucp-api-design содержит примерно следующие правила (упрощённо):
- URL всегда в
kebab-case, ресурсы во множественном числе - Action-эндпоинты через POST
/{resource}/{id}/{action}, не GET - Версионирование в URL:
/v1/ - Идемпотентность через заголовок
Idempotency-Key - Ошибки по RFC 9457 Problem Details
- Алиасы для текущего пользователя:
/users/me, не/users/current
Это всё — проверяемые правила. Скилл может найти нарушение и подсветить. AI пишет API → Claude применяет эти правила → выходит API в стиле проекта, а не среднем.
Один скилл = один аспект кодовой базы. У нас сейчас 6 скиллов (REST API, Java code style, тесты, DDD-тактика, Use Case Pattern, спецификации) — каждый закрывает свой пласт. Все вместе — исполняемый стандарт команды.
Слой 2: Memory Bank (что мы решили однажды)
Для каждого сервиса в репозитории есть memory-bank/ — папка с тремя-пятью markdown-файлами, в которых описано:
- Какой Bounded Context этот сервис реализует
- Какие у него агрегаты, какие соседние сервисы
- Какие архитектурные решения уже приняты («мы используем Outbox через PostgreSQL LISTEN/NOTIFY», «idempotency хранится в Redis с TTL 24h»)
- Какие открытые вопросы и почему они открыты
Это не документация для людей (хотя люди тоже могут читать). Это контекст для AI — Claude в каждой сессии читает Memory Bank первым делом и понимает, в каком сервисе он находится.
Без Memory Bank Claude в каждой новой сессии — как новый разработчик в первый день: знает Java, не знает ваш проект. С Memory Bank — как старожил, знакомый с историей решений.
Слой 3: Шаблон спеки (структура, по которой можно проверить)
Самый недооценённый слой. Один из читателей сформулировал это лучше меня:
«У вашей методологии не самое очевидное место — это не AI-агенты, а сам шаблон из 16 разделов с уровнями детализации. Большинство наших спек проваливают тест на машинную проверяемость просто потому, что они написаны прозой. Чтобы линтер — хоть AI, хоть обычный — что-то поймал, спека должна быть структурой полей, а не сочинением.»
Точно. AI-агенты — это второй порядок производной. Они работают только когда есть структура, по которой их можно настроить. Если спека — сочинение прозой, никакой агент не вытащит из неё контракт интеграций или матрицу переходов состояний — ему попросту нечего парсить.
Use Case спецификация на 16 разделов с тремя уровнями детализации (Tier A/B/C) — это первый порядок производной. Сама структура. Скиллы и Memory Bank — настройка над этой структурой. Без структуры они бесполезны, со структурой — мощные.
Это и есть глубокий ответ на «зачем методология, если AI пишет код». Не для AI, а чтобы AI вообще мог что-то проверить.
Парадокс инверсии: чем умнее AI, тем нужнее методология
Большинство возражений к методологии в эпоху AI исходят из интуиции «AI стал умнее → разработчику не нужно знать архитектуру → методология тем более не нужна».
Эта интуиция неверна. Правильнее так:
AI применяет любые правила, какие ему дать. Без правил — берёт средние из training data. С правилами — следует вашим. Чем точнее AI, тем точнее он следует тому, что вы ему дадите. И тем важнее, чтобы то, что вы ему даёте, было хорошо продумано.
Аналогия: мощный станок ЧПУ режет что угодно по любой программе. Если программа плохая, станок точно вырежет плохо. Если программы вообще нет — станок не работает.
AI-генератор кода — мощный станок. Методология — программа для него. Чем мощнее станок, тем больше зависит от программы, не наоборот.
Без методологии вы получаете: AI рисует «среднюю Java-программу». Через 5 лет проект — зоопарк из «средних программ» разных поколений Claude.
С методологией вы получаете: AI рисует вашу программу — последовательно, согласованно, с учётом ваших архитектурных решений. Через 5 лет проект — единый организм, в котором новый разработчик за день понимает код любого сервиса.
Историческая параллель
Этот разговор уже был. Несколько раз.
Конец 1990-х: «Зачем нам Spring, когда есть Servlet API?»
Аргумент тогдашних скептиков: «Вы можете написать любой сервис на голом Servlet API, всё под рукой. Зачем добавлять фреймворк со своими IoC и аннотациями?»
Ответ оказался простым: совместимость. Spring задал общий контекст для всех Java-приложений того времени. Команда, которая писала на Spring, могла легко переключаться между проектами. Команда без фреймворка — каждый раз изобретала колесо.
Сегодня никто не спорит, что Spring был правильным шагом.
1980-е: «Зачем POSIX, если можно ядро написать заново?»
Каждый Unix-подобный продавец считал свою систему уникальной. AIX, HP-UX, Solaris, IRIX — десяток несовместимых систем. POSIX задал стандарт. Ругали его за бюрократию, за компромиссы, за «убийство гениальных идей».
Сегодня существуют ровно два направления Unix: Linux и BSD. Оба POSIX-совместимые. Без POSIX был бы зоопарк, а Linux никогда бы не стал доминирующим — переносимости приложений не было бы.
2010-е: «Зачем линтер, когда программист умный?»
ESLint, Checkstyle, Sonar. Те же возражения: «программист и так знает как писать», «линтер замедляет ревью». Ответ — масштаб. На команде из трёх человек все правила в головах. На команде из 50 — без линтера правила есть только на бумаге, и они нарушаются ежедневно.
2020-е: «Зачем методология, когда AI пишет код?»
Тот же класс возражений. Тот же класс ответа.
Не «зачем», а «затем что»: совместимость, масштаб, согласованность во времени, общий контекст. Методология не отменяет AI — она задаёт ему рамки, в которых он перестаёт быть «средним из всех Java-разработчиков мира» и становится вашим разработчиком.
Заключение
AI пишет код. Это правда.
AI без методологии хорош для одиночного MVP, прототипа, скрипта. Это правда.
Для команды, продукта и времени — AI без методологии превращается в зоопарк через год.
С методологией AI становится в десять раз ценнее, потому что выходит за рамки «среднего из training data» и работает в вашем контексте.
Use Case Pattern — это попытка такой методологии, открытой и адаптируемой. Четыре столпа:
- Pattern — слойная архитектура, в которой use case это первичная единица
- Спецификация — 16 разделов с тремя уровнями детализации (Tier A/B/C), машинно-проверяемая структура а не сочинение
- Уровни зрелости 1-4 — от MVP до Hexagonal, выбор под задачу
- AI-агенты как часть методологии — Claude Code skills, которые применяют правила автоматически
Если вы соглашаетесь с тезисом «AI без методологии хорош для MVP, для команды — нет» — добро пожаловать. Вот что предлагаю прочитать дальше:
- Use Case спецификация: универсальный шаблон — те самые 16 разделов
- 4 уровня зрелости — какой подходит вашей задаче
- Event Storming — workshop, который собирает данные для спеки
- Order Service: полная Tier C спецификация — сквозной пример как это работает на конкретном сервисе
- usecase-pattern-skills на GitHub — Claude-скиллы методологии
Если же вы остаётесь при мнении «AI и так всё сделает» — закройте эту статью, она не для вас. Не агитирую переубеждаться. Возможно, у вас именно тот контекст, в котором это правильное решение.
А если интересно как это всё работает в реальной команде на 20+ инженеров — подписывайтесь на LinkedIn, там я выкладываю наблюдения из практики.