В паре работает один

При совместной отладке можно показать напарнику исправленную строку, так и не объяснив, почему мы заподозрили именно этот участок и как проверили догадку. Он поймёт исправление, но не обязательно поймёт, как найти похожую ошибку самостоятельно. Для меня участие в поиске начинается с возможности предложить следующую проверку и повлиять на то, куда мы пойдём дальше.

Учебные примеры подготовлены в октябре 2026 года. В них использованы современные возможности JavaScript и платформы; архивная дата не относится к этим реализациям.

Для этого обоим нужен ход рассуждения: что мы уже знаем, какое объяснение проверяем и что заставит от него отказаться. Поровну делить время за клавиатурой необязательно. Бывает полезнее, если один выполняет эксперимент, а второй замечает в его результате то, что первый пропустил.

Возьмём учебный пример с ошибкой в истории операций. Сверху должны стоять две последние записи. При первом открытии всё верно, но после изменения оформления списка сверху появляются самые старые. Ещё одно переключение возвращает новые. Будем искать причину вдвоём: один из нас дольше пишет на JavaScript, другой лучше знает, как страница получает данные. Код, который строит список, обоим пока незнаком.

Исправление появилось раньше объяснения

У более опытного в JavaScript разработчика возникает мысль: кто-то меняет порядок записей в общем массиве. Он берёт клавиатуру, находит подходящий вызов и заменяет его. Несколько раз переключает оформление, ошибка больше не повторяется. Остаётся показать исправленную строку.

Для срочного ремонта это может быть вполне полезной помощью. Но тот, кто пришёл разобраться, видел уже проверенную догадку. Почему решили искать именно изменение массива? Почему не проверили загрузку данных? Какое наблюдение заставило бы отказаться от этой версии?

Объяснение исправленной строки отвечает на другие вопросы. Оно помогает понять код, но путь к нужному месту так и остаётся неизвестным.

Бывает и обратная крайность. Опытный разработчик решает дать напарнику подумать и спрашивает: «А что делает этот метод? А что он возвращает?» При этом его собственная догадка остаётся невысказанной. Вместо совместного поиска получается попытка угадать, к какому ответу тебя подводят.

Я бы сказал свою версию прямо. Полезное знание незачем прятать ради того, чтобы другому было чем заняться. Работы хватает и после того, как догадка названа: ещё неизвестно, объясняет ли она происходящее.

Догадка, которую можно проверить вместе

Вернёмся к началу поиска. Есть две версии: после переключения сервер возвращает другой ответ из своего кеша либо кто-то меняет массив, который уже загружен. У каждой версии можно назвать наблюдение, по которому мы поймём, что проверять дальше.

Если загружаются новые данные, при переключении должен появиться запрос. Один разработчик смотрит запросы в инструментах браузера. Второй пока смотрит, вызывается ли заново функция выбора последних записей.

Нового запроса нет, а функция вызывается при каждом построении списка. Это ещё не доказывает, что причина в ней: данные мог менять и другой код между вызовами. Но теперь можно повторить её работу отдельно от страницы.

Возьмём один массив и вызовем функцию дважды. Посмотрим, повторится ли чередование результатов без всякой сети. Оставим в примере только номера операций, расположенные от старой к новой:

function latest(records) {
  return records.reverse().slice(0, 2);
}

const records = [1, 2, 3, 4];

console.log(latest(records));
console.log(latest(records));
console.log(records);

В консоли появляются [4, 3], затем [1, 2]. Сам records после двух вызовов снова содержит [1, 2, 3, 4]. Сеть из примера убрана, а ошибка осталась.

До исправления полезно задержаться на последнем выводе. Тот, кто проверял сеть, замечает, что массив выглядит как исходный, и предлагает посмотреть на него после каждого вызова. Теперь он выбирает следующее наблюдение, а второй разработчик дополняет пример:

const records = [1, 2, 3, 4];

console.log(latest(records));
console.log([...records]);
console.log(latest(records));
console.log([...records]);

После первого вызова исходный массив содержит [4, 3, 2, 1]. После второго возвращается к прежнему порядку. В консоль передаются отдельные копии, чтобы каждый вывод показывал состояние массива именно на этом шаге.

Теперь можно сопоставить наблюдение с описанием reverse(). Метод разворачивает сам массив. Следующий slice() создаёт новый массив для результата, но прежнее изменение уже произошло. Два последовательных разворота и объясняют, почему ошибка появлялась через раз.

Оба разработчика здесь повлияли на поиск. Один проверил сеть, затем заметил странный последний вывод. Другой сформулировал версию об изменении массива и вынес функцию из страницы. Следующую проверку предлагал то один, то другой; таймер для смены ролей не понадобился.

Проверка до замены строки

Причину уже можно назвать. Теперь договоримся, какого поведения ждём от функции. В нашем примере записи приходят от старой к новой. latest() выбирает две последние, начиная с самой новой, и оставляет входной массив в прежнем порядке. Сортировать произвольные данные по номеру операции она не должна.

Проверить только результат повторного вызова недостаточно. Функция может вернуть нужные записи и всё равно изменить массив, которым пользуются другие части страницы. Поэтому отдельно проверим и исходные данные.

Для этой проверки достаточно двух файлов и Node.js 20 или новее, без установки пакетов. Сохраним исходную функцию в latest.mjs, добавив export перед function. Рядом создадим check.mjs:

import assert from 'node:assert/strict';
import { latest } from './latest.mjs';

const records = [1, 2, 3, 4];

assert.deepEqual(latest(records), [4, 3]);
assert.deepEqual(records, [1, 2, 3, 4]);
assert.deepEqual(latest(records), [4, 3]);
assert.deepEqual(latest([]), []);
assert.deepEqual(latest([1]), [1]);

Запущенная из этой папки команда node check.mjs остановится на проверке массива records. Значит, тест обнаруживает именно тот дефект, который мы собираемся исправить.

Теперь заменим содержимое latest.mjs. Вместо reverse() используем toReversed(), который возвращает новый массив с обратным порядком элементов:

export function latest(records) {
  return records.toReversed().slice(0, 2);
}

Повторный запуск проходит без ошибок. Объекты внутри массива мы не меняем, поэтому здесь достаточно копии самого массива. Остаётся вернуться на страницу и повторить переключения, с которых начали. Если отдельная проверка проходит, а список всё ещё ведёт себя неправильно, у того же поведения есть ещё одна причина. Поиск тогда продолжается.

Не обязательно всё время говорить

Совместная отладка не требует озвучивать каждое движение курсора. Пока один читает функцию, другому незачем слушать названия всех переменных. Мне важнее, чтобы перед следующим экспериментом оба понимали, что проверяют и какой результат изменит их мнение.

Можно молча читать разные участки кода и останавливаться, когда появляется новое наблюдение или меняется версия происходящего. Можно попросить паузу: «Я ещё смотрю, откуда взялся этот массив. Подожди с заменой». По такой реплике понятно, где именно потерялась нить.

Если один из участников долго молчит, я бы сначала спросил о текущей проверке: «Ты тоже ожидаешь, что второй вызов вернёт те же записи?» Возможно, он всё понял и читает дальше. А возможно, представляет происходящее иначе. Просьба просто «говорить поактивнее» этого не выяснит.

Зачем мы сели вместе

Если ошибочный список мешает людям работать прямо сейчас, я не стал бы замедлять восстановление ради равного времени за клавиатурой. Один вносит изменение, другой проверяет исходный сценарий и сохранность записей. К непонятным шагам можно вернуться после ремонта. Только присутствие при исправлении ещё не означает, что напарник научился искать такую ошибку.

Когда мы исследуем незнакомый участок вдвоём, готового маршрута нет ни у кого. Опыт помогает предложить следующую проверку, но уверенность в своей версии не заменяет её результата. Здесь полезен вклад обоих: и догадка о мутации, и замечание о массиве, который после двух вызовов выглядит нетронутым.

Если цель именно в обучении, после исправления понадобится ещё один шаг. Например, можно предложить самому добавить случай с пятью записями и объяснить, почему исходный массив должен сохраниться. При затруднении вернуться к reverse(). Ответ уже известен, и это больше не совместное открытие, а проверка понимания.

Я бы оценивал нашу работу по тому, могут ли оба объяснить, почему список менялся через раз и какое наблюдение подтвердило причину. Если один знает только заменённую строку, стоит вернуться к двум вызовам на одном массиве. Именно там осталось непонятное.