Ведущий разработчик.
Специализация — Рельсы, фронтенд, автоматизация тестирования и разработки.
Связаться: vasily@polovnyov.ru или телеграм.
Ведущий разработчик.
Специализация — Рельсы, фронтенд, автоматизация тестирования и разработки.
Связаться: vasily@polovnyov.ru или телеграм.
Иногда у приложения есть какие-то ключевые метрики, которые могут триггерить алерты, ахтунги и жопы. Например, в условном биллинге это может быть процент неудачных списаний за последний час. Если он высокий, скажем, более 75%, пора заводить инцидент и вызванивать инженеров. Самый толковый способ мониторить такое в приложениях на Рельсах — экспорт ключевой метрики в Графану через Прометеус и Ябеду.
Вот как-то так:
require "yabeda"
Yabeda.configure do
# Где мы
group :billing
# Что мы экспортируем
gauge :failed_charges_percentage, tags: [], comment: "The percent of failed charges in last hour"
# Как собираем данные
collect do
failed_charges_percentage.set({}, ChargeMetrics.failed_charges_percentage)
end
end
Придумал лайфхак, чтобы снизить тревогу и «урон» от совещаний, созвонов и встреч: если созвон с 12 до 13, то в календарь вношу 11:45—13:15.
Буфер в 15 минут до совещания трачу на то, чтобы собраться с мыслями, подготовить повестку и перенастроиться с программирования на разговоры. Без этого буфера первые 5-10 минут совещания провожу в каком-то лимбе: мысли еще в коде, а уже нужно внимательно слушать и как-то реагировать.
Буфер в 15 минут после совещания трачу на то, чтобы разобрать замечания и конспект встречи, раскидать их по задачам и счастливо выдохнуть. Без этого буфера сложно двигаться дальше: мозг возвращается к неразобранным заметкам, злится на неудачные реплики с совещания и подкидывает «надо было сказать вот так».
Мне помогло, попробуйте и вы.
Все мы хотим добиться успеха в карьере, ремесле, бизнесе, спорте или семейной жизни. Кажется, что главные враги на этом пути — лень, низкая мотивация и слабая дисциплина: я ничего не добился из-за него, из-за нее, потому что я ленивый, слабо мотивированный, тряпка и слабак. Кажется, стоит добавить немного насилия над собой, внешней мотивации, и все закрутится-завертится, рост попрет.
Я уверен, что для большинства ребят в айти ключевая проблема — занятость, а не лень или мотивация. И занятость, и Нетфликс расходуют наше время и внимание, ничего не оставляя на целенаправленное развитие. Больше того, занятость отбирает у нас контроль за собственной жизнью и временем: мы уже не хозяева себе, нами рулит календарь с бесконечными встречами, созвонами, стендапами и тревогами.
Отдельно парит, что общество порицает лень, а занятость превозносит. Конечно, в каких-то компаниях важно выглядеть занятым, к таким претензий нет. Но чаще всего занятость — это проблема: посмотрел Gary Vee и 10x programmers, набрал себе работы на выходные, чтобы коллеги видели, как я GET AFTER IT, и сгорел в угли.
Что думаете? Как у вас?
Часто встречаю в тестах такое:
before do
Timecop.freeze(Time.zone.local(2022, 4, 21))
end
after do
Timecop.return
end
Или с ActiveSupport::Testing::TimeHelpers:
before do
travel_to(Time.zone.local(2022, 4, 21))
end
after do
travel_back
end
Такие конструкции из before с «заморозкой» времени и after с возвратом лучше упрощать с помощью around:
around do |example|
Timecop.freeze(now) { example.run }
# или
travel_to(now) { example.run }
end
Дипворк (deep work) — это промежуток времени (1,5 часа в моем случае), во время которого я сфокусирован, сосредоточен и не отвлекаюсь на соцсети, месенджеры, телефон и разговоры. Дипворк — это сессии работы над самыми сложными задачами, требующими максимум внимания, ума и сообразительности. Противоположность дипворка — работа, не требующая максимальных усилий: ответы на почту, совещания и созвоны, разговоры по телефону, заполнение форм и документов. Термин я украл у Кэла Ньюпорта из одноименной книги.
На фотке моя обычная неделя: пачка задач на неделю в ЛВУ + списки задач по дням. Возле дат нарисованы «талоны за дипворк», которые я выдаю сам себе, чтобы отслеживать производительность. Отработал 1,5 часа над чем-то сложным — выписал талон, просидел весь день на созвонах и стендапах — ничего не получил.
Талоны служат ключевым показателем: чем их больше, тем чаще я работал над сложными и важными задачами. Если талонов мало, то что-то идет не так: может, я согласился на слишком много встреч? Если талонов много, то это тоже сигнал: от чего я прячусь в работе?
В теории талоны можно собирать и обменивать на что-то прикольное, как в парках аттракционов: собрал шесть талонов за неделю, обменяй на мягкую игрушку. Но мне пока хватает и самого факта собирательства: представляю, что коплю таллоны, как фриманы копили каждую каплю воды ради рая на Арракисе.
Со мной часто бывало такое: программируешь интересное, а в голову вдруг приходит СРОЧНАЯ и ВАЖНАЯ идея. Если отвлечься на это, голова начнет раскручивать и продумывать идею, что-то планировать, писать письма и гуглить. К коду вернуться сложно: голова занята другим.
Чтобы не отвлекаться в таких случаях, я использую инбокс — текстовый файл inbox.txt, всегда открытый в Саблайме. Появилась идея, опасение, вопрос или что-то, что нельзя забыть, записал в инбокс и продолжил программировать, не отвлекаясь.
Чтобы инбокс работал, мозгу нужны гарантии, что все записанное в инбокс не потеряется. Поэтому у меня есть повторяющаяся задача «Разобрать инбокс», которая стоит сразу после разбора почты в конце рабочего дня. Сделал дело — разбирай инбоксы смело.
Короче, советую: если отвлекаетесь от работы на всякое, закидывайте его в инбокс и забывайте до вечера.
P. S. Использую Саблайм, потому что он не теряет несохраненные данные. Если не сохраню изменения в инбоксе и перезапущу комп, Саблайм их не потеряет.
Ситуация: клиенты регистрируются, а через три дня получают письмо с онбордингом. Скажем, таким кодом:
after_action :send_onboarding_email, only: :create
def send_onboarding_email
ClientsMailer
.with(recipient: user)
.onboarding_email
.deliver_later(wait_until: 3.days.from_now)
end
Чтобы убедиться, что письмо уйдет точно через три дня, есть два способа:
1. Цепочка из моков-стабов:
mailer = instance_double(ClientsMailer)
email = double(:onboarding_email, deliver_later: true)
allow(ClientsMailer)
.to receive(:with).with(recipient: user)
.and_return(email)
# ...
expect(email)
.to have_received(:deliver_later)
.with(wait_until: 3.days.from_now)
2. Встроенный матчер и тестовый адаптер для ActiveJob из rspec-rails:
# Это, конечно, лучше спрятать в хелперы
def with_test_active_job_adapter
last_adapter = ActiveJob::Base.queue_adapter
ActiveJob::Base.queue_adapter = :test
yield
ActiveJob::Base.queue_adapter = last_adapter
end
around do |example|
with_test_active_job_adapter { example.run }
end
it "schedules onboarding email in 3 days from now" do
expect { ... }
.to have_enqueued_job(ActionMailer::MailDeliveryJob)
.with("ClientsMailer", "onboarding_email", "deliver_now")
.at(3.days.from_now)
end
P. S. Использовать 3.days.from_now
без Таймкопа опасно: могут быть ложноотрицательные тесты.
Бывает, приложение общается с внешним сервисом не через интернет-сокеты, а через юникс-сокеты, считай, через локальный файл:
Excon.get("unix:///ping", socket: "/tmp/unicorn.sock")
Чтобы застабить такой запрос Вебмоком, нужно чуть извернуться:
stub_request(:get, "http://unix/ping").to_return(status: 200)
Пара ребят в комментариях к предыдущему посту ответили, что у них Faker, и думать о фейковых данных в тестах не нужно. На мой взгляд, Faker в тестах — это антипаттерн.
Во-первых, с Faker тесты становятся недетерминированными: запустил — упали, еще раз запустил — прошли. И вся отладка этого ада ложится на разработчиков.
Во-вторых, рандомные данные замедляют тесты. Отдельный ад — Faker в фабриках: я работал с проектом, в котором переход с Faker на sequence {}
в фабрике ускорил тесты на 12%.
В-третьих, чрезмерное увлечение Faker приводит к дублированию тестируемого кода. Взгляните, например, на тест метода, который возвращает инициалы пользователя:
it "returns initials"
user = described_class.new(name: Faker::Name.name)
expected = user.name
.upcase
.split(" ")
.map { |part| part[0] }
.join("")
expect(user.initials).to eq(expected)
end
Это непонятный и неподдерживаемый тест: если код помяется, изменения придется дублировать и в тесте. Лучше было бы написать так:
it "returns initials"
user = described_class.new(name: "Daniel Craig Jones")
expect(user.initials).to eq("DCJ")
end
Я, конечно, ни на что не намекаю, но советую присмотреться к Faker: точно ли он вам нужен?
Еще по теме:
Тесты — это не только инструмент автоматической проверки кода и его качества, но и калории, сила отличная документация. Каждый раз, когда в документации библиотеки мне что-то не понятно, я заглядываю в спеки и быстро нахожу ответ на свой вопрос.
В тестах лучше использовать данные, максимально приближенные к реальным: User → Иван Семенов, “ip” → “82.100.200.3”, пустой файл → картинка с котиком. Так мы получим неплохую документацию и хорошие примеры использования нашего АПИ.
Чтобы тесты в проекте были согласованы, советую для тестовых данных выбирать одну доменную область. Например, я часто использую:
1. Персонажей и цитаты из Симпсонов:
User.new(name: "Bart Simpson", email: "bart@simpson.dev")
2. Персонажей и цитаты из боевиков 90-х:
comment: "Dead or alive... you're coming with me"
3. Кусочки и персонажей из песен Эминема или Бейонсе (не спрашивайте):
do_request(text: "In my shoes, just to see what it's like to be me")
А у вас что?
Ситуация: в приложении вы опираетесь на внешний сервис для списаний с пластиковых карт. У сервиса крошечный АПИ: получить номер-маску карты по идентификатору пользователя, списать с указанного пользователя X рублей. Как проверить код, работающий с этим АПИ?
Конечно, можно не париться и реально дергать в тестах внешний сервис. Но это не круто. Во-первых, такие тесты будут тормозить. Во-вторых, такие тесты будут «мигать»: пропала сеть на мгновение, тест упал. В-третьих, это не всегда возможно: внешний сервис может не иметь песочницы или ограничивать количество запросов к ней.
Лучше в таких случаях использовать заглушки для внешних сервисов. Я использую четыре варианта заглушек, выбирая в зависимости от внешнего сервиса: свой или чужой, стабильный или часто меняющийся.
Один из вариантов — фейковый сервис на Синатре. Например, такой:
let(:fake_api) do
Class.new Sinatra::Base do
get "/users/:user_id/card" do
content_type :json
{ number: "4111...1111" }.to_json
end
end
end
before do
stub_request(:any, /api.nanocashier.com/).to_rack(fake_api)
end
В коде выше два интересных момента. Во-первых, Class.new
с родителем Sinatra::Base
в let
, чтобы не добавлять глобальную константу из теста. Во-вторых, stub_request
Вебмока, который роутит все запросы в rack-приложение.
Советую использовать заглушки на Синатре в случаях, когда нужно застабить внешний сервис, у которого нет собственных заглушек (например, stripe-ruby-mock), а сделать заглушку-адаптер не получается.
Вот есть методы тайм-менеджмента: Помодоро, матрица Эйзенхауэра, GTD, ZTD, Канбан, своя-чужая работа, Zero Inbox и прочие. Они все хороши, все работают.
К сожалению, они слабо помогают в ситуации, когда задач настолько много, что они никогда не заканчиваются. Так бывает, например, у топовых руководителей или владельцев бизнеса: всегда есть что поделать, человек физически не может сделать все задачи. В таких случаях приходится чем-то жертвовать: забивать на задачи, закрывать задачи или делегировать их. Ни Помодоро, ни матрица Эйзенхауэра, ни GTD не помогут понять, на каких задачах стоит фокусироваться, а от каких — избавиться.
Мне кажется, ключ в полезном действии: стоит фокусироваться на тех задачах, которые кратно увеличивают полезное действие. Например, полезное действие тимлида — создавать бизнес-ценность, принося больше денег бизнесу, с помощью своей команды. Как кратно увеличить пользу тимлида? Инвестировать время и внимание в:
Такая идея: если задач слишком много, сфокусируйся на тех, что кратно увеличат твое полезное действие. Что думаете?
Я всегда любил читать. Когда в школе задавали список литературы на лето, я справлялся с ним к концу июня и искал, чего бы еще почитать в бабушкиной библиотеке или у друзей. Хуже того, некоторые книги я перечитывал несколько раз за лето: например, «Тихий Дон» Шолохова или «Золотой теленок» Ильфа и Петрова.
К сожалению, в последние годы я читал (и перечитывал) все меньше. Дошло до того, что в 2020 я прочитал всего десяток книг.
Полгода назад я понял, почему не могу читать больше. В моей семье книги были чем-то сакральным. К ним нужно было относиться бережно и уважительно: если взялся читать, прочитай до конца, за другие не хватайся. На этом я и застревал: взялся читать плохую книгу → не хочу ее дочитывать → не берусь за другие книги. Приходилось буквально пересиливать себя, дочитывая откровенно плохие книги, чтобы перейти к следующим.
Решил эту проблему в лоб, разрешив себе читать книги как угодно. Если книга мне не нравится, могу полистать ее и без зазрения совести удалить. Если книга норм, но сегодня не хочется ее читать, без проблем переключаюсь: почитал Ruby Science, а пока он «переваривается» в голове, почитаю Лукьяненко. В результате за полгода прочитал 18 книг, почти в два раза больше, чем за весь 2020.
Такой лайфхак: если «застреваете» в книгах и мало читаете, разрешите себе бросать книги недочитанными и переключаться на другие.
Я провожу много встреч один на один: два дня в неделю я трачу на такие встречи. К каждой встрече готовлюсь: записываю всё, что хочу обсудить, прошу коллегу прислать свою повестку. Такая повестка не очень удобная: её нельзя расшарить, тяжело дополнять, она полностью синхронная.
Уже месяц экспериментирую с асинхронной повесткой в Телеграме. Идея простая: создаём группу в Телеграме на двоих, типа «Вася и Саня по средам», и от встречи к встрече накидываем в неё всё, что хотим обсудить на 1:1.
Мне нравится:
1. Собрали повестку, в том же чате созвонились.
2. Повестка полностью асинхронная. Можно накидывать вопросы и делиться интересным когда угодно, не нужно ждать встречи. Больше того, часть вопросов можно разобрать ещё до встречи: если вопрос короткий и простой, можно сразу ответить на него текстом.
3. Саму встречу (например, аудио) и её конспект бережно хранит Телеграм. Легко собирать, легко возвращаться.
Часто начинающие разработчики останавливаются на первой же проблеме и пытаются решить ее перебором:
— Я поменял вот эту строчку, чтобы починить баг X, а теперь у нас тесты не проходят. Я переставлял и менял эту строчку так и так, но тесты все еще падают. Как поправить?
Это, конечно, полная херня: у меня не завелась машина, я садился в нее и с пассажирского, и с водительского сиденья, но она все равно не заводится. Как поправить?
Чтобы вырасти как программист, нужно научиться решать проблемы не перебором, гаданием и случайными манипуляциями в коде, а железобетонным научным методом: задавать себе вопрос «Почему?» до тех пор, пока не станет понятна суть проблемы. Например:
— Я поменял вот эту строчку, чтобы починить X, а теперь у меня в консоли полно 404 ошибок.
— Почему полно 404 ошибок?
— Потому что мы делаем запрос, в ответ на который приходит 404.
— Почему приходит 404?
— Потому что мы либо собрали неправильный урл, либо по этому адресу запросы никто не обрабатывает.
— Окей, обработчики точно в порядке.
— Почему урл неправильный?
— Потому что мы забыли /scope/ в начале.
— Почему мы забыли /scope/ в урле, почему его там не оказалось?
— Потому что я поменял строчку с идентификатором страницы, из которого собирается и урл для запросов к АПИ.
Бум! Вот и причина проблемы, теперь решение будет легко найти.
Пожалуйста, не тратьте время на бесполезную перестановку строчек кода, задавайте вопросы и проникайте в суть проблемы.
См. также:
https://bureau.ru/soviet/20180621/
Ищу толкового стажера, который вместе со мной сделает интересную бэкендерскую задачу — систему аудита в Learning Management System. Конечно, если все пойдет хорошо, мы поработаем и над другими задачами, а стажер вырастет в сеньора или лида.
Я ищу стажёра, который:
За полтора месяца научу стажёра:
Для этого стажёр будет:
После стажировки мы либо продолжим работать вместе, либо стажер уйдёт на новогодние каникулы с планом роста и списком рекомендуемой литературы.
Если написанное выше про вас, напишите мне письмо с рассказом о себе: vasily@polovnyov.ru
В советах о дебаге я писал, что если не получается поправить баг, можно побороться с его последствиями или заменить решение. Сегодня понял, что есть и третий вариант: достроить и обойти баг.
Ситуация: Commonmark не заворачивает текст в параграф, когда он идёт с одним переводом строки после блочных элементов (заголовки, дивы и прочие). Такой код:
<h1>h1</h1>
wtf
<h1>h1</h1>
wtf???
Даёт такой ХТМЛ (в первом случае параграфа нет, а хотелось бы):
<h1>h1</h1>
wtf
<h1>h1</h1>
<p>wtf???</p>
Если чинить проблему в самом Commonmark дорого и долго, то можно достраивать исходный текст так, чтобы баг не случался. В случае выше достаточно препроцессить исходный текст, добавляя ещё один перевод строки в ситуации с блочным тегом, одним переводом строки и последующим текстом.
Бум! Можно просто достроить исходные данные так, чтобы баг никогда не случался.
Бывает, нужно поставить заглушку на определенный роут, а контроллер для этого делать не хочется. В таком случае подойдет роутинг в Rack-приложение:
# config/routes.rb
get "wp-login", to: -> (env) { [200, {}, [""]] }
Кажется, я изобрёл лайфхак для МАКСИМАЛЬНОЙ ПРОДУКТИВНОСТИ. Беру машину, паркую её в тени на парковке у местного катка. Пересаживаюсь на пассажирское сиденье, расшариваю интернет с телефона и работаю. За час успеваю сделать столько же, сколько сделал бы за четыре часа дома или в кофейне.
Это работает, потому что:
1. Интернет с телефона хорош, но недостаточно: его хватает для работы, но Ютюб уже не посмотришь. Меньше Ютюба — больше работы.
2. Нет соблазна встать, сходить на кухню или к баристе и приготовить чаёк, кофеёк или поесть. Сидишь и работаешь, идти некуда.
3. Никаких хипстеров и стартаперов за соседними столиками. Ты по-настоящему один: чаще всего в радиусе 30-40 метров никого нет.
Это, конечно, лайфхак уровня /b/, но реально работает ¯_(ツ)_/¯
Чтобы Вим фоном линтил текущий файл и показывал ошибки прямо в редакторе, нужно:
1. Установить ALE: https://github.com/dense-analysis/ale
2. Настроить его:
" Что и чем линтим
let g:ale_linters = {
\ 'javascript': ['eslint'],
\ 'ruby': ['rubocop']
\}
let g:ale_linters_explicit = 1
" Линтим Руби бинстабом, который использует Spring
let g:ale_ruby_rubocop_executable = "bin/rubocop"
" Настраиваем значки
let g:ale_sign_error = "\u2022"
let g:ale_sign_warning = "\u2022"
" Оставляем колонку под значки, чтобы левай край окна не «дрожал»
let g:ale_sign_column_always = 1
3. Вы великолепны.