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-івськи. Але щось не складалося.

Набрав п'ять символів і натиснув enter:

ти за a?

Claude перерахував

Його відповідь (кілька блоків коду скорочено):

Змінюю думку — за A.

Причина: вся суть детекції проксі у тому, щоб не довіряти свіжості ABI. Якщо тротлимо через Solid Cache, ми знову кладемо «чи перевіряли вже?» у leтке сховище:

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

Що розблоковує колонка last_proxy_check_at:

  1. Lazy-тригер (та сама логіка, що в B)
  2. Запланований підстрах через 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 зробити не може, — запланований підстрах, спостережуваність, свіжість у UI. Якщо дивитись лише на шар тротлінгу, виграє B. Якщо важливо, навіщо цей job існує, виграє A.

Claude не побачив цей шар у перший раз не тому, що не вміє. Просто дефолтний фрейм «порівняй ці варіанти» — це шар синтаксису/Rails. Одне зустрічне питання витягло його на шар проблеми, і звідти він уже сам прорахував витрати B.

Перетворити це на звичку

Кілька дрібних коригувань у мій workflow:

Коли Claude закінчує на «простіше / легше / більш натівно», подивись ще раз. Це слова естетичні, а не оцінні. Вони описують те, що гарно читається, а не те, що витримає навантаження.

Ціна перепитати — нульова. «ти за X?» — п'ять символів. Claude змушений оцінити з кута, на який ще не ставав. Навіть якщо приземлиться на ту саму рекомендацію, обґрунтування стане міцнішим — досить, щоб ти вирішив, чи довіряти.

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

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

П'ять символів зустрічного питання — це приблизно один рефакторинг через тиждень.