Free

Как заставить Claude передумать

«Более лёгкая» рекомендация Claude скрывала реальные издержки. Один встречный вопрос их вскрыл.


Я ковырял smarts — Rails-приложение, которое отрисовывает Ethereum-смарт-контракты как «документацию для разработчиков». На днях наткнулся на старую проблему: контракты обновляются. Если система рендерит обновлённый прокси-контракт по протухшей ABI, она врёт пользователю.

Я попросил Claude помочь спроектировать обнаружение апгрейдов прокси. Он выдал плотный технический план с поддержкой EIP-1967, Beacon-прокси и ZeppelinOS. Дойдя до «когда триггерить проверку», предложил два варианта:

# Вариант A: добавить last_proxy_check_at в БД, троттлинг 5 мин
if @contract.last_proxy_check_at.nil? || @contract.last_proxy_check_at < 5.minutes.ago
  CheckProxyUpgradeJob.perform_later(@contract)
end

# Вариант B: троттлинг через Solid Cache, легче
unless Rails.cache.exist?("proxy_check:#{@contract.id}")
  Rails.cache.write("proxy_check:#{@contract.id}", true, expires_in: 5.minutes)
  CheckProxyUpgradeJob.perform_later(@contract)
end

Завершающая фраза:

B проще, колонка не нужна.

Я несколько секунд смотрел на оба фрагмента. B действительно проще — без миграций, только cache, очень в духе Rails. Но что-то не клеилось.

Набрал пять символов и нажал ввод:

ты за a?

Claude пересчитал

Его ответ (несколько кодовых блоков урезаны):

Передумываю — за A.

Причина: вся суть детекции прокси в том, чтобы не доверять свежести ABI. Если троттлим через Solid Cache, мы снова кладём «проверяли уже?» в энергозависимое хранилище:

  • После деплоя / cache wipe все контракты разом уходят в состояние «не проверены» → следующая волна трафика триггерит шторм проверок
  • Невозможно ответить «когда этот контракт перепроверяли в последний раз?»
  • Нельзя сделать запланированный fallback: к холодным контрактам неделями нет трафика, ленивый триггер так и не сработает

Что разблокирует колонка last_proxy_check_at:

  1. Ленивый триггер (та же логика, что у B)
  2. Запланированный fallback через recurring.yml: пакетно ставит в очередь контракты, не проверявшиеся больше 24 часов — независимо от пользовательского трафика
  3. Свежесть в UI: «Implementation verified 12 min ago»

Цена: одна миграция + один update_column на job (без колбэков, практически бесплатно).

Кратко: B выглядит «более Rails-native, легче», но в нём не хватает целого слоя возможностей. A — это вариант, который реально реализует «мы не доверяем свежести ABI».

Заметь, что произошло между двумя рекомендациями:

  • Первый проход: перечислить варианты → сравнить объём кода → выбрать B («проще»)
  • Второй проход: оттолкнуться от зачем мы это делаем → заметить, что B кладёт состояние в энергозависимое хранилище, а это противоречит «не доверять свежести ABI» → выбрать A

Между двумя проходами не появилось новых фактов. Объём кода, идиомы Rails, семантика кэша — всё то же самое. Изменился только слой, на котором он оценивал.

Первый проход сравнивал объём кода. Второй сравнивал «решает ли это изменение проблему, которую мы вообще ставили?».

Что сделали эти пять символов

Когда я набирал «ты за a?», я никуда его не толкал. Я просто застрял на коде B и инстинктивно переспросил его предпочтение.

Но эффект оказался такой: я вытянул его из режима «выбери одно из двух» обратно в «зачем мы это делаем?». Этот разрыв — и есть весь смысл.

Когда LLM выдают рекомендации, они почти всегда стартуют от локального оптимума: какой diff меньше, какой сниппет идиоматичнее, какая реализация чище читается. Это всё реальные оси. Но они часто не стыкуются с тем, ради чего вообще существует этот PR.

A и B эквивалентны по оси «троттлинг 5 минут». Но A заодно разблокирует три вещи, которых B сделать не может, — запланированный fallback, наблюдаемость, свежесть в UI. Если смотреть только на слой троттлинга, выигрывает B. Если важно, зачем существует этот job, выигрывает A.

Claude не увидел этот слой в первый раз не потому, что не умеет. Дело в том, что дефолтный фрейм «сравни эти варианты» — это слой синтаксиса/Rails. Один встречный вопрос вытащил его на слой задачи, и оттуда он сам пересчитал издержки B.

Превратить это в привычку

Несколько мелких корректировок в свой рабочий процесс:

Когда Claude заканчивает на «проще / легче / нативнее», посмотри ещё раз. Это эстетические слова, не оценочные. Они описывают то, что хорошо читается, а не то, что выдержит нагрузку.

Цена переспросить — нулевая. «ты за X?» — пять символов. Claude вынужден оценить с угла, на который ещё не вставал. Даже если он приземлится на ту же рекомендацию, аргументация будет крепче — этого хватит, чтобы решить, доверять ей или нет.

Заставь его расписать издержки, а не только рекомендацию. Хороший исход в этот раз случился не из-за того, что я продавил A — а из-за того, что Claude сам выписал скрытые издержки B (шторм после cache wipe, отсутствие наблюдаемости, тупик для запланированных задач). Я их сам не формулировал, пока он этого не сделал.

В итоге выкатил A: добавил колонку last_proxy_check_at, ленивый триггер плюс 24-часовой fallback, в UI теперь строка Implementation verified 12 min ago. Через три дня, в совершенно другой задаче, эта колонка позволила мне написать запрос Contract.where("last_proxy_check_at < ?", 24.hours.ago).find_each — запрос, который с B просто не мог бы существовать.

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