Ведущий разработчик.
Специализация — Рельсы, фронтенд, автоматизация тестирования и разработки.
Связаться: vasily@polovnyov.ru или телеграм.
Ведущий разработчик.
Специализация — Рельсы, фронтенд, автоматизация тестирования и разработки.
Связаться: vasily@polovnyov.ru или телеграм.
Раньше, когда мы встречали что-то неправдоподобное или удивительно хорошо работающее, мы писали в комментариях «Это Фотошоп или Инстаграм». Теперь пишем «Это ИИ или нейросеть». Сегодня поясняю по хардкору за эти и другие термины, от которых уже тошнит.
Начнём с ИИ, AI — Искусственного Интеллекта, Artificial Intelligence. ИИ — это зонтичный бренд, а не что-то конкретное. ИИ — это семейство технологий, которые делают что-то похожее на твою интеллектуальную работу. Распознать речь, отфильтровать спам, предложить следующий трек, сгенерировать картинку, написать диплом, переписать всё на Расте или Тайпскрипте — это всё ИИ.
AGI — Artificial General Intelligence, универсальный искусственный интеллект. Это цифровой коллега, который одинаково хорошо справится с письмом, интерфейсом, кодом, переговорами и планированием. Но в отличие от обычного коллеги, не спит, не ест, не ходит на перекуры, и ему не нужно скидываться на ДР.
Про AGI говорят много, потому что идея яркая и офигенная. Именно такого уровня ИИ нам нужен для освоения вселенной с помощью зондов фон Неймана. Отправляем самовоспроизводящийся зонд под управлением ИИ в соседнюю систему, он там добывает всякое, строит свои точные копии и отправляет их в соседние системы.
К сожалению, от AGI мы пока далеки. Весь полезный ИИ сегодня неуниверсальный, узкий.
ANI — Artificial Narrow Intelligence, узкий искусственный интеллект. Это не универсальный мегамозг, а очень сильный помощник для узкого класса задач. Он может хорошо справляться с одним классом задач, но быть бесполезным во всём остальном. Например, Google Translate, рекомендательная система Озона, распознавание лиц, генерация субтитров в Ютюбе или LLM.
LLM — Large Language Model, большая языковая модель. Это узкий ИИ, который работает с языком: пишет, сокращает, объясняет, суммирует, извлекает данные, генерирует варианты.
Вы наверняка слышали, что LLM — это очень умное автодополнение, Т9 на стероидах и всё такое. А на самом деле LLM — это просто миллиарды и триллионов ифов. Шучу, это просто миллиарды и триллионы матриц, которые друг на друга умножаются. Учёные и программисты покруче поправили бы меня: «это миллиарды и триллионы параметров (весов), которые участвуют в вычислениях (в том числе через умножение матриц)».
LLM — это не отдельная магия, а частный случай нейросетей, обученных на огромных объёмах текста. Нейросети — это такая хитрая математическая модель и её воплощение в коде, которая умеет накапливать опыт и опираться на него, чтобы что-то предсказать или классифицировать.
GPT, ГПТ — Generative Pre-trained Transformer, Генеративный Предобученный Трансформер. Звучит охеренно, но это просто отдельный тип LLM, построенный на хитрой архитектуре.
Чтобы представить, как всё это соотносится друг с другом, запомните вот что: ИИ — это автомобили, электросамокаты и велосипеды, нейросети — автомобили, LLM — электромобили, а GPT — именно Теслы.
Чаще всего интерфейсом к ГПТ-модели служит окно чата: ChatGPT, Midjourney, DeepSeek, NanoBanana, Perplexity и прочие. Чаты просто дают ответы на ваши вопросы, гоняют текст и картинки туда-сюда. А настоящая крутизна начинается тогда, когда мы заворачиваем модель в процесс: получи контекст → реши, что делать дальше → сделай → повторяй, пока задача не будет сделана. Вот этим занимаются ИИ-агенты.
ИИ-агент — это LLM + тулы + контекст и бесконечный цикл. ИИ-агент больше похож на помощника-исполнителя, который умеет не только отвечать, но и что-то делать у вас на компе с помощью тулов.
При этом LLM — лишь часть ИИ-агента. Скажем, ИИ-агенты для программирования состоят из LLM, упряжки (harness) и UI. LLM отвечает за думанья. Упряжка — за всё вокруг модели: цикл, тулы, окружение и управление контекстом. UI — то, что вы видите.
Например, вы можете взять OpenCode для harness, выбрать в нём DeepSeek в качестве LLM и открыть всё это в браузере (UI). Аналогично и в других агентах:
Codex (console UI) + gpt5.3-codex high (model) + codex (harness)
Codex App (UI) + gpt5.4 (model) + codex (harness)
Claude Code (console UI) + sonnet 4.5 (model) + claude (harness)
Зачем об этом знать? Затем, что в 99% случаев модели показывают лучший результат с упряжкой от этого же провайдера.
Начну с камингаута. Я использую LLM для программирования полтора года. За это время я попробовал почти всё: Cursor, локальные модели, Claude, Codex, Qwen, Gemini, Copilot, Zed, Amp и OpenCode. В 2026 году я не написал ни строчки кода с нуля. Даже в пет-проектах вместо крафтового кода, написанного и наполированного мною вручную, теперь сияет бездушный код, написанный киборгом-убийцей из будущего. В итоге, я пришёл к простому выводу: не надо экономить на моём здоровье, лучше брать самые сильные доступные модели и агенты.
Во-первых, чем умнее, вдумчивее и осмотрительнее виртуальный коллега, тем приятнее с ним работать. Модели поумнее читают мысли, а моделям поглупее приходится разжёвывать одно и то же по нескольку раз. Именно поэтому появился паттерн (на мой вкус, анти-) «план пишет умная модель, реализует глупая».
Во-вторых, модели поумнее пишут код, который можно принять с первой итерации. С моделями поглупее приходится делать большее число итераций, пока код достигнет того уровня качества, что я хочу. Да, умная модель дольше генерирует код, но это окупается: меньше правок, меньше циклов, меньше усталости.
В-третьих, тулы и упряжка у агентов подороже реально круче. Попробуйте подебажить утечку памяти или найти источник высокой нагрузки в логах с помощью Codex или Claude. Офигеете, как агент пользуется тулами, как проверяет гипотезы однострочниками и как пишет мини-программы для решения задач. У хороших агентов с хорошими тулами есть чему поучиться.
В-четвёртых, чем умнее агент, тем больше пользы за единицу времени он приносит, тем быстрее вы пилите новые фичи. Чем больше фич вы сделаете, тем больше опыта получите, тем быстрее прокачаетесь в агентской разработке.
При этом никто не заставляет вас пилить в 10 раз больше фич. Пилите в 5 раз больше, а освободившееся время потратьте на что-то другое. Например, на все те рефакторинги, о которых вы мечтали и которые задвигали в бэклог последние 3 года. Поручите их агенту и сделайте за день, а не за итерацию.
Короче, если вы все еще экономите, потратьте 500 рублей, оформите подписку на ChatGPT, скачайте Codex, поменяйте модель на gpt-5.3-codex high и делегируйте ему все на протяжении месяца.
Я как-то пропустил, а в Руби 3.2 завезли класс Data для создания value objects:
Recipient = Data.define(:name, :email, :role)
vasyan = Recipient.new(name: "Vasyan", email: "vasyan@stark.com", role: :cc)
vasyan.email # vasyan@stark.com
vasyan.role # :cc
# При этом
vasyan.email = "foo" # undefined method 'email='
Пример посложнее с кастомными методами:
Measure = Data.define(:amount, :unit) do
def <=>(other)
return unless other.is_a?(self.class) && other.unit == unit
amount <=> other.amount
end
include Comparable
end
Measure[3, 'm'] < Measure[5, 'm'] #=> true
Measure[3, 'm'] < Measure[5, 'kg'] # ArgumentError
Отличается от Struct двумя ключевыми моментами. Во-первых, Data не генерирует сеттеры (attr_writer) для атрибутов. Во-вторых, Data не включает Enumerable, поэтому в нем нет each, each_pair, filter и прочих. Короче, Data — это идеальный вариант для минималистичных неизменяемых value objects.
Последние три недели хожу вокруг одной идеи, но никак не могу ее нормально доформулировать. Все время чего-то не хватает. Начну здесь, а вы помогайте.
Недавно понял, что у программиста две задачи. Первая — приносить прибыль и снижать издержки, пописывая код и тесты у себя в квартале. Ну вы знаете: писать код и тесты, пилить фичи, чинить баги и резолвить инциденты. Назовем ее — крафт, мастерство. Вторая — быть легким и приятным в работе с собой. Назовем ее — АПИ.
Я всегда фокусировался на мастерстве, забывая об АПИ. Мой недостижимый идеал — ХАКЕРМЭН, злобный гений, колдун-программист в комнате, затянутой сигаретным дымом, парами алкоголя и сумраком. (Если вы сильно моложе меня, вспомните Гилфойла из «Кремниевой долины»)
Оказывается, быть злобным гением — не лучшая стратегия. С ним никто не хочет работать, к нему приходят, когда совсем припрет. Лучше забить на идею или потерпеть проблему, чем пойти к сисадмину колдуну-программисту и поесть фруктовой смеси из презрения, стыда и брезгливости. Так самые интересные идеи и сложные задачи проходят мимо него: их отдают менее скилловым, но более приятным в работе.
Важно этот момент признать и посмотреть на себя со стороны как на интерфейс. Какой АПИ я отдаю коллегам? Насколько легко и приятно им пользоваться? Стал бы я сам таким пользоваться? Что можно улучшить в работе с моим АПИ?
Справедливо и обратное. Никакой самый приятный и удобный АПИ не заменит сделывания задач. Если программист весь такой внимательный, инициативный и замечательный, но выдает по строчке кода в месяц — это очковтирательство, а не работа.
Последний Ruby Weekly принёс статью о том, что let! лучше заменять на let + before блок:
https://allaboutcoding.ghinda.com/rspec-and-let-understanding-the-potential-pitfalls
Мол, такое:
RSpec.describe Thing do
let(:account_a) { build(:user, email: email) }
let(:account_b) { build(:user, user: email2) }
let(:organisation) { build(:organisation, account: account) }
let(:team) { build(:team, account: organisation) }
before
account_a
account_b
end
it 'returns that specific value that we want' do
# test
end
end
Лучше, чем такое:
RSpec.describe Thing do
let!(:account_a) { build(:user, email: email) }
let!(:account_b) { build(:user, user: email2) }
let(:organisation) { build(:organisation, account: account) }
let(:team) { build(:team, account: organisation) }
it 'returns that specific value that we want' do
# test
end
end
Мне кажется, что это какая-то ерунда. Во-первых, все мы любим Руби за то, что код на нем, читается почти как книга. А тут у нас «сначала акаунт а и б». Это че вообще?
Во-вторых, зачем вообще использовать let, если ни в последующих let, ни в тестах, мы не обращаемся к этим переменным по имени? Если они реально нужны только для того, чтобы создать окружение (скажем, нужны два акаунта, пользователь создаёт третий), то лучше сразу создать их в before блоке:
before do
create(:account)
create(:account)
end
В-третьих, на мой вкус, let! такой же заметный, как и before. А если хочется сделать позаметнее, сделай себе декоратор LET!!!11111.
В-четвертых, мне кажется, что стоит отделять зависимости тестируемого объекта и сетап окружения. Грубо говоря, в let зависимости (пользователь –> организация –> акаунт), а в before — действия, приводящие систему в нужное нам состояние. А размазывать их по let и before — странновато.
Представим, что у нас есть тупейший мейлер с пачкой методов:
class OnboardingMailer < ApplicationMailer
default to: -> { @user.email },
from: "marketing@foo.bar"
before_action :load_user
def welcome; end
def onboarding; end
def intro; end
def survey; end
def goodbye; end
private
def load_user
@user = params[:user]
end
end
Раньше я тупо перечислял и делегировал каждый метод в мейлер в превьюшке:
class EventsMailerPreview < ActionMailer::Preview
delegate :welcome, :onboarding,
:intro, :survey,
:goodbye, to: :mailer
private
def mailer
OnboardingMailer.with(user: User.first)
end
end
Получается не очень круто. Если появляются новые письма, приходятся дополнять список методов в превьюшке. Чтобы избавиться от этого гемороя, лучше использовать public_instance_methods(false):
class EventsMailerPreview < ActionMailer::Preview
available_emails = OnboardingMailer.public_instance_methods(false)
delegate(*available_emails, to: :mailer)
private
def mailer
OnboardingMailer.with(user: User.first)
end
end
public_instance_methods возвращает имена всех публичных и протектед методов класса, а false просит вернуть только те, что определены в самом классе, игнорируя родителей.
Делюсь моим набором правил для Курсора, которые помогают писать вменяемые тесты с RSpec. Я пользуюсь ими около месяца, в 9 из 10 случаев получается то, что надо. Больше того, я использовал эти правила, чтобы сделать домашку в Тестовом курсе, и получил хорошие тесты, требующие минимальной доработки.
---
description:
globs: **/*_spec.rb
alwaysApply: false
---
# <Project> Tests Style Guide
## General
- Always use English in tests.
- Always test edge, valid, and invalid cases.
- Set up RSpec config in `.rspec` and `spec_helper.rb`.
- Follow DRY principles and avoid duplication. Don't over-DRY at the cost of clarity; duplication is acceptable if it improves readability.
- Use shared examples for repeated behaviors.
- Do not test private methods.
- Do not test trivial methods.
- Do not test external dependencies.
## Layout
- Do not leave empty lines after `describe`, `context`, or `feature` declarations.
- Leave one empty line between example groups (`describe`, `context`, `feature`).
- Leave one empty line after `let`, `before`, and `after` blocks.
- Group all `let` blocks together, separate from `before`/`after`.
- Leave one empty line around each `it`/`specify` block.
- Separate test phases (setup, exercise, assertion) with one empty line.
## Example Group Structure
- Do not use `subject`; prefer `let`/`let!`.
- Use `let`/`let!` for setup, not instance variables or `before` for data.
- Order: `let`/`let!`, then `before`/`after`.
- Use `describe` for methods/classes. Use `.` for class methods, `#` for instance methods.
- Use `context` to describe different conditions, starting with 'when', 'with', 'if', 'unless', 'for', 'and', 'but', 'without', or 'otherwise'.
## Example Structure
- One expectation per example (`it`). If an example description contains 'and', it probably contains more than one expectation.
- When an example consists of multiple linked expectations, consider using `have_attributes` or a custom matcher.
- Keep example descriptions short and clear.
- Use `expect` syntax for assertions.
- For change assertions, prefer `expect { ... }.to change { ... }.from(...).to(...)` or `.to(...)`.
- Extract a `context` if an `it` description contains conditions: when, with, if, unless.
## Naming
- Contexts should describe conditions, forming readable sentences with nested blocks.
- Do not use 'should' in example descriptions. Use present tense, third person.
- Be explicit in `describe` blocks about what is being tested.
## Stubbing & Mocks
- Mock/stub only when necessary. Prefer real objects for integration tests.
- Always use `expect(...).to have_received(...)` to verify method calls on double/spy instead of `expect(...).to receive(...)`.
- Stub HTTP requests (e.g., with webmock) instead of hitting real services.
- Use `Timecop` for time, not stubbing `Time.now`.
Вот, как мне кажется, из каких этапов состоит процесс внедрения ИИ и LLM в работу простого советского программиста. Пишу схематично, чтобы не забыть. Если интересно, напишите — разберу каждый этап отдельно со всеми нужными ссылками.
Мне кажется, всё, как в Железном Человеке. Начинаем с бестолковой Алисы, переходим к аугументации, костюму, который тебя делает круче, и наконец, к автономным костюмам-агентам (см. «Протокол семейный праздник» из ЖЧ3).
Прогрессия:
0. Отрицание, гнев, торг, депрессия, принятие. Да, как прежде уже не будет.
1. Время от времени захаживаешь в Дипсик или ЧатГПТ. Просишь что-то посоветовать, объяснить, написать за тебя функцию. Получаешь опыт, учишься формулировать рабочие промпты, снова и снова копипастишь кусочки кода туда-обратно.
2. Задалбываешься копипастить. Добавляешь автокомплит — ставишь GitHub Copilot или Курсор. Автокомплитишь табами, отдаешь LLM бойлерплейт.
3. Покупаешь Курсор. Начинаешь немножечко вайб-кодить через чат. Пишешь и воруешь рулы, делаешь мини-фичи с помощью LLM. Учишься формулировать требования к решению задачи, натыкаешься на ерунду, прокачиваешь рулы. Обжигаешься и понимаешь, что тесты не стоит всегда доверять LLM.
4. Переходишь на программирование агентом. Сетапишь ВПН, покупаешь Claude Code. Программируешь фичи и рефакторишь с помощью разговора с консолью. Делегируешь агенту всю работу с репозиторием от создания и чтению ишьюс до форс-пушей и создания ПРов.
5. Начинаешь использовать чат с LLM для постановки задач другой LLM. Роботы программируют друг друга, а ты таскаешь промпты туда-сюда.
6. Переходишь на флот агентов. Запускаешь несколько инстансов агентов в вебе в Курсоре или локально с помощью Claude Code и git worktree.
7. Прекрасное светлое будущее. События «Мстители: Финал», уходишь в плотники или пивовары.
Так вижу. Пожалуйста, не воспринимайте это как руководство к действию и обязательно проконсультируйтесь с безопасниками, чтобы не получить по жопе за код, слитый в сеть.
Зафиксирую для истории. Сегодня ровно месяц, как я не могу работать в обычном режиме «сел за комп утром, встал вечером, зато без перерыва». Теперь работаю короткими интервалами по 20-25 минут с перерывами по 5 минут. Соответственно, поменялся и формат работы с LLM. Вот, что я поменял:
1. Написал системный промпт для Курсора, который просит LLM сначала сформулировать план решения задачи, утвердить его со мной, выполнить, а затем прогнать тесты и линтеры:
## Development cycle
1. Before writing any code, come up with a good plan, review the plan, and then ask the user for permission to execute the plan.
2. After you have executed the plan, run: `bin/rspec` and `bin/rubocop`
3. If there are any linting errors, run `bin/rubocop -A`
4. To run tests: `bin/rspec <path to file>`
Теперь я ставлю задачу LLM, проверяю и, если нужно, корректирую план, отправляю его на выполнение и иду отдыхать свои 5 минут.
2. Стал писать задачи на русском. Если я правильно понимаю, как работает LLM, то никаких проблем с этим быть не должно. car и «машина» в их векторном пространстве смыслов должны быть совсем рядом.
3. Стал писать задачи по формату. Открываю все нужные файлы, делаю /Add Open Files to Context. Кратко формулирую задачу в один абзац, а затем пишу список (буквально!) требований. Часть требований и технических деталей пишу прямо кодом:
...
С вот такими требованиями:
...
- определяет методы в мейлере, используя `define_method` и `@email_chain.steps`
...
- находит нужную цепочку и пользователя, опираясь на переданный mailing_list_subscription и АПИ EmailChain:
def load_data
@mailing_list_subscription = params[:mailing_list_subscription]
@user = @mailing_list_subscription.user
@email_chain = EmailChain.find_by(mailing_list_subscription: @mailing_list_subscription)
end
4. Стал бить задачи так, чтобы диф изменений, сгенерированный LLM, был не больше 300-400 строк. Так их проще проверить и верифицировать. Если вайб-кожу, конечно, не смотрю на это. Пусть генерит сколько угодно, все равно на выброс.
5. Перестал доверять LLM в написании тестов. LLM — мастера находить самые простые и рабочие пути. Они очень часто пишут тесты, которые либо тестируют ненужное, либо тестируют только «золотой путь», игнорируя все краевые случаи. Ощущение, будто LLM пишут тесты, чтобы они проходили с первого раза, а не для того, чтобы найти побольше багов и задокументировать АПИ.
На прошлой неделе смотрел, как другие ребята пользуются Курсором и ИИ в нем. Заметил, что не все понимают, как получать от него максимум пользы.
Во-первых, первое, что нужно сделать — включить YOLO, он же Enable auto-run mode. Эта фича разрешит агенту запускать код локально. Соответственно, можно будет делать крутейшие штуки:
Запусти эти тесты и поправь тестируемый класс так, чтобы тесты проходили
Напиши тесты для функции foo, которая ... Затем напиши функцию foo и исправь ее, если потребуется, так, чтобы тесты проходили
Во-вторых, в повторяющихся задачах необязательно описывать нужные изменения: переименуй так, сконвертируй в класс, перенеси в метод X, удали старый файл. Можно сделать один раз самому и использовать изменения в качестве примера:
Взгляни на мои изменения в модуле видео для Кинескопа и повтори эти изменения для модулей видео в Вимео, ВК и Ютюбе
Аналогично с помощью примеров можно подсказать, как должна работать новая функция:
Напиши функцию на JS, которая группирует свойства по ключам во вложенные объекты. Покажу на примерах:
{ vkSrc: 'http://vk.com/123' } превращается в { vk: { src: 'http://vk.com/123' } }
{ vimeoId: '123', kinescopeSrc: 'http://ki.io' } превращается в { vimeo: { id: '123' }, kinescope: { src: 'http://ki.io' } }
В-третьих, чат можно использовать как справочник. Выделили нужный фрагмент кода, например, resolitions в package.json и попросили объяснить, как это работает.
В-четвертых, во вкладке Source Control в поле с текстом коммита есть магическая кнопка. Нажмите на нее, чтобы ИИ сгенерировал нормальный коммит-месседж на основе ваших (или не совсем ваших) изменений.
В-пятых, используйте правила для Курсора. Как минимум, возьмите хороший системный промпт:
https://cursor.directory/
Как максимум, понапишите собственных правил для проекта:
https://ghuntley.com/stdlib/
Короче, если вы используете в Курсоре только автокомплит, присмотритесь к YOLO auto-run mode и .cursorrules. И считайте чат не хитрым интерфейсом к Гуглу, а диалогом со стажером, с которым вы программируете в паре.
С уходом Дискорда и непреднамеренным старением я стал записывать замечания со встречи прямо в тот же чат в Телеграме. Так как замечаний много, отвечать на каждое из них — безумство. Любого выбесит 20-30 сообщений «Увидел», «Поправил», «Записал». Вместо этого использую реакции на сообщения с замечаниями. Принцип такой:
👌 — поправил, сделал, починил
✍️ — записал, в бэклог, разберусь позже
👀 — увидел
🤔 — че-т подозрительно, проверим
👍 — супер, одобряю, нормас
🔥 — клево
Если в заметках со встречи все в эмодзи, значит, я все разобрал. Если у сообщения реакции нет, значит, пропустил. Изи, ничего не теряется.

Тут DHH написал пост, да такой, что ПРЯМО В ЧУВСТВА. В нем пара клевых моментов.
Во-первых, Джейсон Фрид на заре Бейскемпа отвечал на 150 писем клиентов в день. Еще раз: сто пятьдесят писем клиентов в день. А мы инбокс разгрести не можем.
Во-вторых, политика «Все работают в техподдержке» помогает фиксить мелкие баги-заусенцы, до которых никогда не доходят руки. А все потому, что программистам проще починить проблему, чем извиниться за то, что она там есть. Думаю, это еще и для эго менее болезненно.
Пост целиком:
https://world.hey.com/dhh/stick-with-the-customer-4942402f (нужен ВПН)
Иногда никак не хочется подходить к задаче. Уже все дела попеределал, книгу почитал, зубы почистил, на балконе прибрался, даже в машину омывайку подлил, а до задачи руки никак не доходят. Прокрастинация на максимум, не те вайбы.
В таких случаях важно не путать дизмораль с трением. В первом случае к задаче не подойти, потому что все уныло, какой в этом смысл, это ни на что не повлияет, уже три года это говно пилим. Во втором случае к задаче не подойти, потому что она реально сложная и непонятно, с какого конца за нее взяться. Соответственно, и решения разные. Дизмораль можно победить действиями вне задачи, ну или просто затащить ее на морально-волевых и дисциплине. А сложную задачу — разбить на пачку задач попроще, которые постепенно рассеивают «туман войны», спрятавший ее решение.
Поэтому первое, что я спрашиваю у себя в понедельник утром, когда чувствую непреодолимое желание пойти подлить омывайки: это дизмораль или трение?
Иногда нам достаются задачи, которые не закрыть в течение одного дня. Скажем, вы исследуете сложный баг, проявляющийся только у 0,0001% пользователей. Или вместе с архитектором пишете спеку нового сервиса, встречаясь каждую неделю на час. Или подбираете и пробуете self-hosted альтернативы Sentry. Или работаете над большой миграцией данных на новую схему.
Вдруг наступает пятница, а за ней выходные. Вы возвращаетесь к работе в понедельник и полчаса вспоминаете, на чем остановились.
В таких случаях стоит оставлять артефакты — заметки, комментарии, вопросы, кусочки кода, сравнительные таблицы, текущие проблемы и проверенные гипотезы. Словом, все, что поможет вернуться к задаче:
Проверии гипотезу с браузерным и системным зумом. В эмуляторе проблемы нет.
Как будем авторизовывать клиентов? Может, пойдем в сторону API Gateway?
Sentry — опенсорсный, но поднимает 6 контейнеров и требует кучу ресурсов. Как вариант — GlitchTip. У него АПИ, совместимый с Sentry и минимум лишнего функционала.
Перенесли все уиды в алиасы. Остается грохнуть колонку и убрать ссылки на mailing_list_uid.
Такие зарубки помогают ничего не забывать, быстрее возвращаться в контекст задачи и двигаться дальше в правильном направлении. Отсюда тупое правило:
Бывает, вижу в проектах именованные сабжекты в качестве хелперов для испытаний:
subject(:process) { described_class.new.process }
before do
allow(EventProducer).to receive(:emit).and_return(true)
end
context "when run twice" do
it "emits only one event" do
process
process
expect(EventProducer).to have_received(:emit).once
end
end
Это плохой, ложноположительный тест. Что бы мы не написали внутри process, EventProducer.emit всегда будет вызываться точно один раз.
Проблема в хелпере subject. Как и let, он кеширующий, мемоизирующий. После первого вызова он запоминает результат блока и в последующие вызовы сразу возвращает результат, не тригеря код в блоке.
Это легко проверить мини-тестом:
class EventProducer
def self.emit
end
end
describe "subject(:process)" do
subject(:process) { EventProducer.emit("foo") }
before { allow(EventProducer).to receive(:emit) }
context "when run twice" do
it "emits both events" do
process
process
expect(EventProducer).to have_received(:emit).twice
end
end
end
Результат:
Failures:
1) subject(:process) when run twice emits both events
Failure/Error: expect(EventProducer).to have_received(:emit).twice
(EventProducer (class)).emit(*(any args))
expected: 2 times with any arguments
received: 1 time with any arguments
# ./spec.rb:17:in `block (3 levels) in <top (required)>'
Пожалуйста, не используйте именованные сабжекты для испытаний. Лучше сделайте старый добрый метод:
def process
described_class.new.process
end
Я сразу родился айти-дедом. Когда появился Руби, я бухтел, что непонятно, куда тело цикла вставлять. Когда появлялись ЦСС-переменные, бухтел, что они нам «не нужоны», достаточно вот так и так сгруппировать селекторы. Бухтел, когда переезжали с Propotype на jQuery, а потом на Реакт. Бухтел, когда все носились с Монгой и Нодой. Бухтел с каждым релизом Рельс. Блин, да я даже из-за синтаксиса хэшей в Руби бухтел: ракеты рулят, джейсон отстой.
Сейчас в программировании все переезжают на AI-, LLM-помощников, которые «помогают» писать код. И вот тут я бухтеть не готов. Уверовал мгновенно, как попробовал Курсор.
В Курсоре по сути два основных помощника. Первый — стандартное автодополнение в процессе набора кода, к нему вопросов нет. Прикольно, но ничего особенного. Второй — чат с редактором. И вот этот режим — бомба, потому что обсуждать и делать можно как отдельные кусочки кода, так и проект целиком.
В какой-то момент я попросил в чате собрать мне консумер, аналогичный уже существующему, но с несколькими ключевыми изменениями. К нему же попросил тесты. Прошло 10 секунд и Курсор предложил мне два файла с кодом, в котором толком нечего было исправлять. Я просто принял изменения и пошел открывать 100% AI-generated Pull Request. И вот этот момент стал определяющим. Не о чем тут бухтеть, будущее уже здесь.
Короче, если вы еще не используете LLM-помощников в разработке, попробуйте Курсор. Просто перетерпите ВС Код и дайте ему неделю:
https://www.cursor.com/
Если вам интересно, как там у них все под капотом, послушайте подкаст с Лексом Фридманом:
Сейчас не хватает сил и внимания на что-то большое и осмысленное, поэтому короткая заметка на бегу.
Часто в удаленной работе не хватает чувства причастности к команде, чувства плеча, вот этого ощущения «да я в команде X». И вроде работаешь месяц, два, три, а до сих пор ощущение, будто, ты одинокий мститель в маске.
По моему опыту это ощущение команды появляется после совместных страданий, трудностей, например, какой-то адухи с пуском. Ребята, совместно преодолевшие весь геморрой, связанный с пуском, превращаются в братство. Они начинают доверять и полагаться друг на друга, потому что знают, что когда в очередной раз shit hits the fan, их коллега не сольется. Думаю, именно на страданиях, трудностях и совместном их преодолении строятся самые сплоченные команды в армии и спорте.
P. S. В августе прочитал всего ничего. Feel Good Productivity — херня. Автомобильная династия — часть про нацисткую Германию интересная, остальное херня. Типа, ничего себе, фантастически богатые еще сильнее богатеют. Неслыханно!
Ситуация: вы тестируете контроллер, который отправляет тестовое письмо со сводкой по вчерашним продажам. Под капотом контроллер обращается к классу DailySummaryEmail и вызывает метод #test. Вы пишете тест:
it "calls DailySummaryEmail#test" do
Это плохое, «машинное» описание проверки. Во-первых, тесты — это примеры использования кода, документация. Это пример чего? Чем он будет полезен читателю?
Во-вторых, это детали реализации, считай, приватный интерфейс. Если переименуем метод или класс, придется поправить и в теле проверки, и в ее описании.
В-третьих, это бесполезные детали. Я из тела проверки вижу, что вызываем DailySummaryEmail#test. Делаем-то это зачем? Чтобы что?
Лучше писать для людей, описывая то, что должно происходить в мире читателя:
it "sends previous day summary email to marketing department"
Год назад вышел подкаст Лекса Фридмана с Хикару Накамурой. Хикару — крутой шахматист-стример, специализирующийся на быстрых шахматах. Когда-то побеждал Магнуса Карлсена.
В подкасте есть интересный эпизод с обсуждением читинга в шахматах:
На 1:15:55 Лекс задает самый важный технический вопрос: сколько информации тебе нужно, чтобы читерить? Накамура отвечает, что ему хватит одного «бжж», если текущая позиция отличная, и двух «бжж», если текущая позиция обычная или нормальная. То есть ему не нужны конкретные ходы, оценки или предсказания. Ему достаточно знать ответ на вопрос: текущая ситуация на доске отличная (для меня) или нет?
Я переформулировал этот вопрос в «это … улучшает ситуацию или нет?» и стал бесконечно спрашивать себя:
Такой странный вопрос помогает мне оценивать последствия решений и изменений в долгосрочной перспективе. Помогает взглянуть на проблему в контексте времени и позиции, добавляет еще одну точку зрения и заставляет стремиться к улучшению «позиции». Спасибо, Хикару!
Есть гипотеза. Мы охереваем от перегруза, потому что понабрали слишком много обязательств. Эти обязательства тянут за собой планирование, коммуникацию и переключение доменных областей. Это и дает нам чувство разбитости и усталости.
Попробую показать на примере. Представим, что я подписался запрограммировать встраиваемый платежный виджет, сделать код-ревью в соседнем проекте о барсуках, выполнить мастера спорта по тяжелой атлетике, самостоятельно вести бухгалтерию в ИП и оптимизировать билд на CI, чтобы он укладывался в 3 минуты.
Каждый из этих проектов тянет за собой свою доменную область, которую надо восстанавливать в памяти. Тут у нас мерчанты и платежи. Тут барсуки, передержки и ветеринарки. Тут рывок, толчковая тяга и подрыв. Тут УСН, ПСН, счета, акты и ЭДО. Тут CI, yaml и профайлер. Любой, кто пробовал быстро переключить внимание между барсуками и УСН, знает, что это непросто. Мозг тупит и кипит минут 15-20. Есть риск задекларировать барсука.
Также каждый из этих проектов тянет за собой коммуникацию и планирование. Что там по дедлайнам у виджета? Нужно попросить у дизайнеров фотки барсуков на передержке. Не забыть зарегаться на соревнования по ТА. Акт не прошел в СБИС через роуминг, надо написать в техподдержку. Надо показать ПР с ускорением билда ребятам, чтобы покритиковали. Это тоже работа, которая также требует времени и внимания.
И кажется, что мы понабрали так много всего, потому что это «легко». Легко ревьюить соседний проект, это же не код писать. Легко выполнить мастер спорта по ТА, просто делай, что в программе написано. Легко вести ИП, в Эльбе уже все есть, только кнопки нажимай. Легко оптимизировать билд на CI, ты же программист.
Сколько обязательств вы взяли, потому что это казалось «легко»?