«Более лёгкая» рекомендация 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?
Его ответ (несколько кодовых блоков урезаны):
Передумываю — за A.
Причина: вся суть детекции прокси в том, чтобы не доверять свежести ABI. Если троттлим через Solid Cache, мы снова кладём «проверяли уже?» в энергозависимое хранилище:
- После деплоя / cache wipe все контракты разом уходят в состояние «не проверены» → следующая волна трафика триггерит шторм проверок
- Невозможно ответить «когда этот контракт перепроверяли в последний раз?»
- Нельзя сделать запланированный fallback: к холодным контрактам неделями нет трафика, ленивый триггер так и не сработает
Что разблокирует колонка
last_proxy_check_at:
- Ленивый триггер (та же логика, что у B)
- Запланированный fallback через recurring.yml: пакетно ставит в очередь контракты, не проверявшиеся больше 24 часов — независимо от пользовательского трафика
- Свежесть в UI: «Implementation verified 12 min ago»
Цена: одна миграция + один
update_columnна job (без колбэков, практически бесплатно).Кратко: B выглядит «более Rails-native, легче», но в нём не хватает целого слоя возможностей. A — это вариант, который реально реализует «мы не доверяем свежести ABI».
Заметь, что произошло между двумя рекомендациями:
Между двумя проходами не появилось новых фактов. Объём кода, идиомы 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 просто не мог бы существовать.
Пять символов встречного вопроса стоят примерно одного рефакторинга через неделю.