В заметке будет приведен краткий обзор задания из анкеты Екатеринбургской Yandex школы разработчиков интерфейсов 2013. Мне бы хотелось остановиться на некоторых его тонкостях и предложить критерии его оценки.
Данная заметка из серии – «Выбора тестового задания для разработчиков. Критерии оценки». Она является подготовкой почвы для разговора с будущим разработчиком.
Замечу, что оригинальное задание должно быть выполнено на JavaScript, но, не смотря на это, данную заметку можно рассматривать без привязки к языку.
Задание
Напишите алгоритм карточной игры в «Пьяницу» на JavaScript. Интерфейс и возможности игры мы оставляем на ваше усмотрение и фантазию. В качестве ответа на вопрос пришлите ссылку на ваш код на jsfiddle.net или на github.com.
Правила игры
Происхождение игры в «Пьяницу» неизвестно. Есть мнение, что свое название она приобрела из-за простых правил и еще потому, что победа в игре зависит исключительно от везения. Играть в «Пьяницу» очень просто. На старте колода из 52 карт тасуется и делится между всеми игроками поровну. Далее каждый игрок кладет свою стопку карт перед собой «рубашкой» вверх. Игроки одновременно переворачивают верхнюю карту. Тот, у кого карта оказывается больше по достоинству, забирает все открытые карты себе и кладет их под свою стопку. Самой большой картой в «Пьянице» считается туз, самой маленькой — двойка.
Если две карты оказываются одного достоинства, то начинается «война». Игроки кладут «рубашкой» вверх следующую карту, а следом за ней — карту лицом вверх. Игрок с большей картой забирает все карты на кону. Если карты снова оказываются равными, то «война» продолжается таким же образом. Снова одну карту кладут «рубашкой» вверх, а за ней — лицом вверх. И так далее до определения победителя. Выигравший «поединок» забирает все карты на кону. Игра продолжается, пока у одного игрока не окажутся все карты — он и считается победителем.
Требования и критерии оценки
Я не знаю какие критерии оценки решений в компании Яндекс, но это безусловно было бы интересно услышать.
На мой взгляд, самыми важными критериями оценки качества кода является:
- полнота выполнения задания
- простота и понятность кода (читаемость)
- легкость внесения изменений (расширяемость, продуманность архитектуры, качество декомпозиции)
Начнем разбираться по порядку.
Полнота выполнения задачи
В данной игре есть тонкие моменты, на которые стоит обратить внимание. Естественно, они должны обрабатываться корректно.
- Во время спора у одного из игроков могут кончиться карты.
- В игре может быть несколько спорных ситуаций подряд.
- Кол-во игроков, участвующих в споре может уменьшиться (например, изначально спор был между тремя игроками, после выполнения следующего хода спор будет продолжен, но уже только между двумя игроками).
- Во время спора, у всех спорящих игроков могут закончиться карты (в споре не будет победителя!)
- В игре вообще может не быть победителя (пожалуй, самая маловероятная ситуация, желающие могут посчитать ее вероятность).
Четвертый случай можно пометить звездочкой, поскольку в правилах данная ситуация никак не регламентирована. Но, естественно, можно считать плюсом обработку подобной ситуации.
Пятый случай я бы пометил двумя звездочками.
Читаемость кода
Под читаемостью я больше понимаю не следование стайлгайду конкретного языка, а правильность названия переменных и функций и отделение логических блоков. Так же понятно, что в коде не должно быть глубокой вложенности конструкций. Все это более – менее фундаментальные правила (фундаментальные – в смысле что относятся они не к конкретному языку и принятым в нем соглашениям)
Понятно, что стайлгайд – это хорошо, но ведь код можно переформатировать под него и автоматически. А вот правильное название переменных, файлов, названий функций (из названия должно быть понятно, что хранится в переменной, о чем написано в файле, что делает функция) и выделение блоков (отделение переводом строки некоторых частей кода) – это уже задачи, которые решаются только разработчиком.
Расширяемость
Легкость внесения изменений
Данный пункт, прежде всего, предполагает хорошую декомпозицию задачи. Выделение логических частей решения.
Пункт может быть оценен по следующему критерию – легко ли будет внести изменения в код, если техническое задание изменится.
Приведем пример простых изменений:
- Изменилось кол-во игральных карт (например, стало 36).
- Кол-во игроков не должно быть фиксировано (зашито в коде), играть могут хоть 13 игроков одновременно.
- Изменились правила игры – в игре появилось правило, что «2-ка бьет Туза».
- Добавилось правило игры: «Если в споре нет победителя (у участников закончились карты), то спор продолжается среди всех игроков имеющих карты для начала нового спора» или «Если в споре нет победителя, то все спорные карты выигрывает самый правый / левый / случайно выбранный игрок»
- После 20-ти игровых раундов каждый участник перемешивает свою колоду.
- Выигранные карты складываются наверх/под низ колоды, а выкладываются на стол сверху/снизу/случайно выбираются из середины колоды. Ну, или все выигранные карты перемешивается и кладутся под низ колоды (аналог того, как мы складываем карты в жизни — мы не задумываемся о порядке сбора выигранных карт)
- Другие интересные ситуации …
В идеале, мы должны будем поправить одну – две функции. Ну никак не перепиливать всю игру.
Отделение логики игры от ее визуализации
- Хорошим плюсом является выделение игрового ядра. Понятно, что «игровая логика» должна быть отделена от «логики представления игры» (визуализации игрового процесса).
- В частности, для JS характерно то, что игровая логика должна (при нормальном разделении) работать и на node.js.
- Недопустимо смешивание кода отвечающего за внешнее представление игры и игровой логики. Я бы посчитал это грубой ошибкой.
Давайте рассмотрим на примере схемы то, как должна быть выполнена начальная декомпозиция задачи:
Постарался все наглядно изобразить на схеме, надеюсь что получилось.
- Отмечу так же то, что во VIEW желательно не допускать ничего связанного с шаблонами, особенно кусков HTML`я.
- Можно оценивать то, насколько решение соответствует приведенной схеме.
Логика ядра
В задании упор делается на логику работы игры – это видно из его формулировки. И это правильно, поскольку это самая главная часть работы, от ее архитектуры зависит весь проект.
Поговорим немного об архитектуре ядра.
Это очень тонкий и местами спорный момент, поэтому я не могу говорить, что должно быть именно так и никак иначе. Приведу лишь свои рассуждения в качестве почвы для дискуссии.
Давайте попробуем понять, что такое игра в «наших» (программистских) терминах. Игра – это последовательность раундов (ходов), каждый из которых как-то изменяет ее состояние. Зная состояние игры визуализировать сам процесс не сложно.
У меня, после этих рассуждений, в голову приходит модель конечного автомата. Ну, или более формально – конечный автомат с памятью (магазинный автомат).
Итак, у нашей игры есть состояние, которое мы будем использовать для ее отображения. Ну и естественно, должна быть процедура, которая это состояние обновляет. Выше мы уже определили игру как последовательность ходов, а значит, формально, процедура должна выполнять этот самый ход.
Попробую представить в виде схемы процесс работы приложения (игры) и то, как она взаимодействует с ядром:
Названия для состояния игры я бы выбрал исходя из результата раунда, то есть выделил бы следующие названия для состояний:
- Начало
- Карты выиграл какой-то игрок
- Раунд закончился спором
- Раунд закончился победой игрока — игра закончена
- Раунд закончился спором, но у спорщиков нет больше карт
- Раунд закончился спором, но карт нет ни у кого – победителя нет, игра закончена
- Такие названия очень хорошо показывают то, что можно визуализировать и то, на каком состоянии сейчас остановилась игра.
Так же, можно выделить основные сущности для данной игры: Карта, Колода (стопка) карт, Игра.
Объяснения по поводу выбора сущностей писать не буду, поскольку это очень спорный момент. Я привел их исключительно для примера. В любом случае об этом нужно беседовать с каждым разработчиком индивидуально, что бы выслушать его позицию и аргументацию в пользу его выбора, и уже после этого оценивать его доводы.
Пожалуй, это все основные критерии, по которым я бы оценивал работу.
Итог
Заметка получилась немного сумбурной, но я постарался изложить некоторые критерии оценки работы, немного коснулся того, как бы это реализовал я.
С первого взгляда задание кажется глупым и тривиальным, но при более глубоком рассмотрении в нем видны тонкие моменты.
Оффтоп
Можно посмотреть на результаты выполнения задания в сети, они легко гуглятся, среди них есть интересные. Особенно любопытные, могут, применив свои СИ способность, узнать победителей. Мне было интересно посмотреть как одну и ту же задачу решили разные люди.
А вообще, было бы интересно узнать, по каким критериям работы оценивали в Яндексе.
Автор: pahaz