Проектируем API по TDD, разрывая шаблон

Проектируем API с помощью статической типизации и функционального программирования.

Допустим, у нас есть задача написать API для игры в шахматы по сети (через интернет). Такая своеобразная MMO, а точнее, онлайновый сервис с поддержкой PvP, а также различных сервисов шахматного анализа. Играть в ней могут только живые игроки, то есть задачу написать шахматный движок мы не рассматриваем. Наша задача — просто обеспечить корректную возможность игры и изучения шахматных позиций в интернете.

Разрешается использовать любые языки программирования — и далее я покажу, в чем заключается одна из ключевых фишек Type Driven Development. Но есть одно важное «но» — если мы ошибёмся в выборе стиля разработки, скорее всего получится классическая проблема, свойственная всем начинающим (да и не только) разработчикам. Сперва количество багов будет увеличиваться быстрее, нежели они будут фикситься, а потом система будет кое-как работать, но очень неуклюже и заторможенно.

Итак, вот техническое задание на этот API.

— Каждая функция API, вызываемая с одними и теми же параметрами, всегда должна возвращать один и тот же результат.

— Когда клиент вызывает функцию API, она всегда должна возвращать значимый результат (не прерывание, не null).

— Различные игровые функции API (например, «на доске пат?» или «вызывать игровой движок для позиции на доске и получить её оценку») можно свободно вызывать для некоторой позиции игры независимо от того, в каком состоянии сама игра (закончена, продолжается, отложена и т. п.).

— Невозможно сделать ход вне очереди.

Но это была разминка. А вот уже ключевые пункты ТЗ, из которых идея TDD становится более понятной:

— Если попытаться вызвать функцию «сделать ход» в позиции, когда партия завершена, должна возникнуть ошибка компиляции (несоответствие типов). Другими словами, вызов такой функции для партии в недопустимом для неё состоянии запрещён системой типов.

— Если попытаться вызвать функцию «взять ход назад» в ситуации, когда ходов ещё не было сделано, должна возникнуть ошибка компиляции.

— Если попытаться вызвать фунцию «кто выиграл? (или ничья?)», пока игра не закончена, должна возникнуть ошибка компиляции.
В качестве дополнительного упражнения можно реализовать и запрет хода вне очереди также с помощью системы типов.

Наша глобальная задача: полностью исключить возможность использования API в обход нашей системы типов. Клиент не должен получать null или сам обрабатывать прерывания от ошибок, и уж совершенно точно, у него не должно быть возможности самостоятельно выполнять приведение типов.

Безусловно, проще всего решить упомянутые задачи на Haskell, а лучше (с учётом распространённости в продакшене) на Scala. Резко — на порядок — сложнее, но тоже можно, причём достаточно элегантно — на C#. И тоже возможно, хотя и очень неуклюже (из-за идиотской и невероятно раздутой системы типов, хотя и корректной) — на Java.

В итоге, очевидно, в случае возникновения ошибок времени выполнения пространство их поиска резко сужается. Мы всегда можем быть уверены, что играется корректная шахматная партия, а если она завершена, то в ней либо выиграла одна из сторон, либо ничья. Причём подобные гарантии распространяются на весьма большое количество проектных инвариантов!

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

Другими словами, даже тривиальная задача, робастно реализуемая с помощью математически правильной методики, оказывается крайне сложной практически для 100% мэйнстримовских программистов, независимо от их опыта! Потому что они практиковались в основном в алгоритмистике programming in small с помощью трёх операторов (присваивания, цикла, условный), да пытались выстроить нечто в жанре programming in large с помощью ООП, декомпозиции-наследования, получая в итоге бесконечные ошибки времени выполнения, и тратя кучу времени на автоматические тесты.

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

С помощью Type Driven Development мы получаем надёжнейший API с минимальными усилиями — сравнительно с усилиями по обеспечению робастности стандартными средствами.

Поделиться статьей ...Share on Facebook0Share on Google+0Tweet about this on TwitterShare on LinkedIn0Share on VKPrint this page

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *