JS: Программирование, управляемое данными
2026-02-26 17:14 Diff

Конспект урока

Проблема: код не поддаётся тестированию

Наша игра работает, но обладает ограничением — мы не можем её полноценно тестировать.

Если колода содержит две и более карты, то ход игры и её результат заранее определить и протестировать НЕ получится.

Ведь в процессе игры карта выбирается случайным образом:

Вызов random(cards) возвращает случайную карту. Этот код располжен внутри функции с игрой, поэтому делает недетерминированной всю игру.

Решение: инвертирование

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

Рассмотрим простой пример: функция принимает на вход колоду карт cards, внутри происходит случайный выбор карты и какие-то другие дальнейшие манипуляции. Это принципиальная схема, если отвлечься от несущественных деталей.

Эта функция НЕ является чистой, она недетерминирована. И это нормально для игры, но не для тестов.

Применим к функции технику инвертирования, реализовав передачу процесса выбора карты снаружи, через параметры:

Теперь мы можем управлять процессом выбора карты, передавая (в зависимости от ситуации и наших целей) ту или иную функцию в параметр customRandom.

Тесты

Для тестирования нам не подойдёт обычный random. Поэтому определим и передадим в функцию игры специальную функцию выбора карты, обеспечивающую предсказуемое поведение:

Мы передали в игру (вторым параметром) анонимную функцию:

Её ядро заключается в строчке кода, определяющей текущий индекс:

Значение переменной cardIndex функция берёт из переменной, определённой во внешнем окружении:

Это важно, так мы можем смоделировать нужное нам предсказуемое поведение. При каждом новом вызове значение cardIndex циклически меняется с нуля на единицу и наоборот (индексирование в списке карт начинается с нуля!). Это как раз то, что нужно для ситуации колоды, состоящей из двух карт.

Обязательно проанализируйте процесс выбора карт в модуле с тестами в практике к этому уроку!

На примере простой функции продемонстрируем принцип определения циклического (а значит предсказуемого!) изменения величины:

Для колоды из трёх или какого-нибудь другого количества карт надо будет модифицировать функцию. Можно сразу написать более универсальный вариант:

Выводы

С помощью техники инвертирования мы добились следующих преимуществ:

  • Предсказуемого поведения — код стало возможным тестировать. Теперь можем управлять процессом выбора в зависимости от целей: для игр передавать обычный random, для тестов — кастомный.
  • В целом, добились расширения возможностей программы посредством делегирования части функциональности внешнему коду. Программа стала более гибкой в использовании.