«Легша» рекомендація 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?
Його відповідь (кілька блоків коду скорочено):
Змінюю думку — за A.
Причина: вся суть детекції проксі у тому, щоб не довіряти свіжості ABI. Якщо тротлимо через Solid Cache, ми знову кладемо «чи перевіряли вже?» у leтке сховище:
- Після деплою / cache wipe всі контракти разом потрапляють у стан «неперевірених» → наступна хвиля трафіку запускає шторм перевірок
- Неможливо відповісти «коли цей контракт перевіряли востаннє?»
- Не зробити запланованого підстраху: холодні контракти можуть тижнями не мати трафіку, lazy-тригер ніколи не вистрелить
Що розблоковує колонка
last_proxy_check_at:
- Lazy-тригер (та сама логіка, що в B)
- Запланований підстрах через 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 зробити не може, — запланований підстрах, спостережуваність, свіжість у 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 просто не могло б існувати.
П'ять символів зустрічного питання — це приблизно один рефакторинг через тиждень.