Ночь, прод, пять тикетов от саппорта. Сервер задач глючит: дубли, пропажи, 500-ки. Найди баги по симптомам и почини минимальным диффом.
🔴 ALERT: tasks-api — error rate 4.2% (порог 1%)
5 открытых тикетов от саппорта
Дежурный: ТЫДобро пожаловать в on-call — дежурство по продакшену. В большинстве команд инженеры по очереди «носят пейджер»: неделю ты — тот, кому звонит мониторинг, когда что-то ломается. Сегодня твоя ночь.
Сервис — API задач для внутренней команды. Писал его стажёр, тесты писать не успел, уехал в отпуск. Код работает: сервер поднимается, отвечает на запросы. Но саппорт завален жалобами. Классика: не «всё упало», а «работает, но неправильно» — самые противные баги.
Тикет — это не диагноз, это симптом глазами пользователя:
«Создаю две задачи — вторая затирает первую!!!»
Пользователь не знает про твой код. Он видит следствие. Твоя работа — пройти путь от симптома к строке:
| Этап | Вопрос | Пример |
|---|---|---|
| 1. Воспроизведи | Могу ли я увидеть баг сам? | два POST подряд → смотрю ответы |
| 2. Локализуй | Какой маршрут? Какая строка? | оба ответа с одинаковым id → смотрю POST |
| 3. Почини минимально | Какое НАИМЕНЬШЕЕ изменение убирает баг? | одна строка, а не рефакторинг |
| 4. Закрепи тестом | Как не дать багу вернуться? | регрессионный тест на этот случай |
Если баг не воспроизводится — ты чинишь вслепую. Если не локализован — правишь наугад. Пропускать этапы дороже, чем идти по ним.
Соблазн велик: код стажёра корявый, хочется переписать начисто. Ночью в проде так не делают:
fix: increment nextId after create объясняет, ЧТО было сломано. «Rewrote everything» не объясняет ничего.Рефакторинг — днём, отдохнувшим, с тестами. Ночью — минимальный дифф.
Справа — весь сервер, как его оставил стажёр. Не чини ничего сейчас — просто прочитай и составь мысленную карту: 4 маршрута, память в массиве tasks, счётчик nextId. В пяти следующих шагах придут пять тикетов. Каждый шаг: тесты уже написаны и красные — они описывают, как ДОЛЖНО быть. Твоя задача — найти баг и сделать их зелёными минимальным изменением.
Кофе налит. Открывай первый тикет ☕
const app = express();
app.use(express.json());
// Сервер задач. Автор: стажёр (уехал в отпуск, тестов нет)
let tasks = [
{ id: 1, title: 'Настроить мониторинг', done: false },
{ id: 2, title: 'Написать постмортем', done: true },
{ id: 3, title: 'Обновить зависимости', done: false },
];
let nextId = 4;
// GET /api/tasks — список задач, фильтр ?done=true|false
app.get('/api/tasks', (req, res) => {
let result = tasks;
if (req.query.done === true || req.query.done === false) {
result = tasks.filter(t => t.done === req.query.done);
}
res.json(result);
});
// POST /api/tasks — создать задачу
app.post('/api/tasks', (req, res) => {
const title = req.body.title.trim();
const task = { id: nextId, title: title, done: false };
tasks.push(task);
res.status(201).json(task);
});
// PATCH /api/tasks/:id — отметить выполненной / снять отметку
app.patch('/api/tasks/:id', (req, res) => {
const matches = tasks.filter(t => t.title === req.body.title);
if (matches.length === 0) {
return res.status(404).json({ error: 'Задача не найдена' });
}
matches.forEach(t => { t.done = req.body.done; });
res.json(matches[0]);
});
// DELETE /api/tasks/:id — удалить задачу
app.delete('/api/tasks/:id', (req, res) => {
const idx = tasks.findIndex(t => t.id === Number(req.params.id));
if (idx === -1) {
return res.status(404).json({ error: 'Задача не найдена' });
}
tasks.splice(idx);
res.json({ ok: true });
});
app.listen(3000, () => console.log('✅ http://localhost:3000'));Задача — починить прод по симптому минимальным диффом, а не «переписать всё ночью».
«Вторая задача затирает первую в списке» — все элементы будто с одним ключом.
id: nextId читает счётчик, но nextId никогда не увеличивается → все задачи получают один и тот же id (4).
let nextId = 4;
function createTask(title) {
tasks.push({ id: nextId, title });
// nextId не меняется — всегда 4
}let nextId = 4;
function createTask(title) {
tasks.push({ id: nextId++, title });
// ^^ увеличиваем
}Фикс — одна строка: nextId++. Каждая задача получает уникальный id.
Один симптом → одна причина → одна строка. Не «переписать всё» — прод чинят точечно.
Постмортем: что сломалось, почему прошло в прод, какой тест теперь ловит это. Без поиска виноватых.