JS: Программирование, управляемое данными
Теория: Инверсия
Конспект урока
Проблема: код не поддаётся тестированию
Наша игра работает, но обладает ограничением — мы не можем её полноценно тестировать.
Если колода содержит две и более карты, то ход игры и её результат заранее определить и протестировать НЕ получится.
Ведь в процессе игры карта выбирается случайным образом:
Вызов random(cards) возвращает случайную карту. Этот код располжен внутри функции с игрой, поэтому делает недетерминированной всю игру.
Решение: инвертирование
Сейчас выбор карты осуществляется внутри игры, и мы не можем на это никак повлиять. Но ситуация изменится, если сделать так, чтобы алгоритм выбора карты игра получала "снаружи". Это легко реализовать с помощью передачи параметра.
Рассмотрим простой пример: функция принимает на вход колоду карт cards, внутри происходит случайный выбор карты и какие-то другие дальнейшие манипуляции. Это принципиальная схема, если отвлечься от несущественных деталей.
Эта функция НЕ является чистой, она недетерминирована. И это нормально для игры, но не для тестов.
Применим к функции технику инвертирования, реализовав передачу процесса выбора карты снаружи, через параметры:
Теперь мы можем управлять процессом выбора карты, передавая (в зависимости от ситуации и наших целей) ту или иную функцию в параметр customRandom.
Тесты
Для тестирования нам не подойдёт обычный random. Поэтому определим и передадим в функцию игры специальную функцию выбора карты, обеспечивающую предсказуемое поведение:
Мы передали в игру (вторым параметром) анонимную функцию:
Её ядро заключается в строчке кода, определяющей текущий индекс:
Значение переменной cardIndex функция берёт из переменной, определённой во внешнем окружении:
Это важно, так мы можем смоделировать нужное нам предсказуемое поведение. При каждом новом вызове значение cardIndex циклически меняется с нуля на единицу и наоборот (индексирование в списке карт начинается с нуля!). Это как раз то, что нужно для ситуации колоды, состоящей из двух карт.
Обязательно проанализируйте процесс выбора карт в модуле с тестами в практике к этому уроку!
На примере простой функции продемонстрируем принцип определения циклического (а значит предсказуемого!) изменения величины:
Для колоды из трёх или какого-нибудь другого количества карт надо будет модифицировать функцию. Можно сразу написать более универсальный вариант:
Выводы
С помощью техники инвертирования мы добились следующих преимуществ:
- Предсказуемого поведения — код стало возможным тестировать. Теперь можем управлять процессом выбора в зависимости от целей: для игр передавать обычный random, для тестов — кастомный.
- В целом, добились расширения возможностей программы посредством делегирования части функциональности внешнему коду. Программа стала более гибкой в использовании.

