Весь исходный код доступен в публичном репозитории на GitHub по этой ссылке. Он не претерпевал никаких изменений с момента публикации, несмотря на то что в нём есть ряд моментов, которые я бы довел до ума, но намеренно не стал этого делать. Всегда есть некая договоренность с самим собой относительно времени, которое мы тратим на задание. Пытаемся расставить приоритеты, исходя из личных предпочтений, если иное не было подчеркнуто в требованиях к заданию: кто-то предпочтёт написать документацию на свои API, а кто-то решит увеличить покрытие тестами. В моём случае, как вы можете заметить, нет e2e тестов и unit тестов UI компонентов, также CSS накидан на скорую руку и не следует никакой методологии. Вполне вероятно, вы можете продолжить этот список и другими недочетами.
Будучи под впечатлением от профиля одной компании, подкрепив это отзывом одной бывшей коллеги, работавшей там когда-то, я решил отправить туда своё резюме и начать процесс собеседования. Через неделю-две со мной связался рекрутер и предложил пообщаться с лидом одной из команд. Разговор с лидом больше сводился к общим вопросам: что вам нравится, какие технологии используете, чем гордитесь, какие проблемы решали, также было оставлено место и для моих вопросов, — совершенно обычный разговор, вполне типичный для IT компании. Спустя некоторое время этого же дня мне сбросили задание. Ниже я напишу требования этого задания. Текст задания сохранен в оригинале:
Вам предлагается написать сервис для совместной игры в крестики-нолики.
Игра содержит несколько игровых полей, каждый игрок может либо присоединиться к игре на существующем поле, либо создать новое поле.
Начальный экран содержит список игровых полей и их краткое описание (сколько игроков сейчас играет, когда был последний ход). Данные обновляются в реальном времени.
Игровое поле имеет фиксированный размер 10 x 10. На каждом поле могут играть несколько игроков. Каждый игрок может сделать ход в любой момент, но не может сделать несколько ходов подряд.
Игровой сервер может хранить состояние игрового процесса в памяти.
Решение вы можете реализовать на любых удобных вам технологиях, но будет здорово если вы сделаете их на [обозначены технологии проекта]
Я решил делать на удобных мне технологиях. В качестве языков были выбраны: Java и Javascript. В качестве фреймворков: Spring и React. Хранение состояния осуществляется в памяти как и позволено в задании.
Буквально за неделю до этого, я делал техническое задание для другой компании, где так же фигурировала игра, но с другой логикой. Игра называлась Калах. Архитектура, которую я заложил для неё, и принципы, которым следовал, показались мне вполне уместными и здесь. И вообще, кажутся уместными для реализации простых игр в целом. Какие основные приоритеты я ставил для себя при решении задачи:
- Наличие документации для API. Сниппеты с семантикой Rest API будут генерироваться интеграционными тестами — убиваем двух зайцев. И здесь на помощь к нам приходит Spring Rest Docs.
- Использование websockets для построения интерактивного дашборда и самой игровой сессии. Long polling — уже давно не модно. Берем stomp и sockjs, рекомендуемые нам инфраструктурой spring. Как дополнительное преимущество — если сервер не поддерживает websockets, библиотека делает fallback на long polling незаметно для клиента. Почему бы и нет?
- Инкапсуляция правил игры в отдельных классах. Так, для Калах, например, у меня было три правила: правило хода, правило захвата и правило конца игры. Такой подход мне показался очень уместным, т.к. каждое правило можно тестировать отдельно, можно убрать из игры или можно усложнить игру, добавив новый класс с реализацией другого правила. В крестиках и ноликах у меня таких правил оказалось два — правило хода (TurnRule), которое так же взяло на себя ответственность за валидацию (всё же, валидацию я бы вынес на уровень выше) и правило конца игры (EndGameRule), задача которого сводилась к определению победителя или ничьи. Следует также отметить, что порядок правил я определяю с помощью аннотации Order спринга, которая указывает контейнеру в каком порядке инжектить бины. Это решение может показаться спорным, но наличие теста, в котором мы проверяем что задан именно такой порядок, делает его вполне жизнеспособным.
- Алгоритм нахождения победителя я решил реализовать, сделав ставку на композицию, при этом думая об эффективности, не забывая что нельзя выходить за пределы линейной сложности. Я написал некоторый набор кастомных итераторов, каждый из которых занят идентификацией выигрыша — по горизонтали, по вертикали, основная и побочная диагонали. Как мне кажется читать такой алгоритм проще и в любой момент можно включить/выключить конкретный итератор, что делает архитектуру достаточно гибкой.
- Фронтенд будет реализован тройкой react/redux/saga — это тот набор, который мы используем в своих проектах, и показавший свою эффективность на длительной дистанции. Я не уверен что имеет смысл комментировать зачем redux и почему мы берём его на вооружение. Но, на счёт redux-saga, часто слышу критику и, зачастую, эта критика исходит от людей, не достаточно хорошо ознакомившихся с предметом этой критики. Скажу лишь то, что я тоже изначально настороженно относился к этой библиотеке, но когда изучил её получше, осознал что с таким подходом работать очень удобно, как и с точки зрения тестирования, так и с точки зрения чтения кода.
— это и есть основные принципы, которые получили своё отражение в реализации.
Из особенностей серверного кода, отмечу, что для защиты от race conditions используется мапа ReentrantLock’ов — на игровую сессию свой ReentrantLock. Реализацию синхронизации с клиентским временем я решил не делать, а просто генерировать время хода на сервере.
После первого фидбека относительно UX с предложением поправить и добавить некоторые вещи, я также решил переписать фронтенд на функциональных компонентах с хуками. Давно хотелось прощупать этот подход и он мне понравился. Авторы reactjs в документации подчеркивают, что этот подход минимизирует кол-во ошибок совершаемых программистами, связанных с жизненным циклом компонента при использовании стиля, основанного на классах и callback'ах — и, мне кажется, это важный момент. И если вы ещё не используете новый подход в своей работе — я рекомендую вам его попробовать.
Нижеприведенный фидбек я получаю неделю спустя:
Мы с коллегами внимательно посмотрели новый вариант задания.
По результатам я решил дальше не продолжать процесс.
Ниже фидбек по заданию от нас:
— код запутан, понимать сложно, гораздо сложнее, чем могло бы быть
- оверинжиниринг в части frontend, цитирую нашего frontend разработчика
…
Видимо задание сделано на тех технологиях, которые знал кандидат, хотя мы никакого redux и redux-saga не просили при этом CSS, который руками написан – там просто заоверрайжены стили из библиотеки это плохой способ писать CSS
…
С учетом сложности codebase в нашем продукте нам важно, чтобы простые вещи кандидат делал просто.
Спасибо Вам за время и усилия!
Конечно, хотелось бы иметь концептуальную дискуссию, иметь возможность защитить те или иные решения. Я принимаю комментарий относительно CSS, но совершенно не понимаю остальных. Оверинжиниринг ли это? Или я столкнулся с технической незрелостью интервьюеров? В принципе, здесь любая оценка будет очень субъективна и по-своему имеет место на существование.
Задумываясь над смыслом вопроса — Что такое сложный код? Мне кажется, что ответ на этот вопрос очень простой: это код который сложно читать и сложно менять. Я, как и многие, предпочитаю думать, что мы пишем код для людей, а не для машин. И стараюсь оставить место для мыслительного процесса о композиции кода перед тем как садиться за его написание. Действительно ли проделанная мною композиция усложняет чтение этого кода и претендует на клеймо — оверинжиниринг? Мнение по этому вопросу я бы с интересом увидел в ваших комментариях.
modestguy
Проблемы в сфере IT — они ведь такие же как и во всех остальных) Иногда достаточно одеть купальник, работая врачом — и ты уже модель :) А иногда врач-профессионал со стажем за копейки будет сидеть и горбатится до конца жизни… пытаясь научить молодых специалистов, как делать операции (а им может это и не надо — а надо стать моделями).
Не смотрел код, но могу сказать только одно — рассматривайте по жизни такой кейс ещё и с обратной стороны. Если Вы не прошли собеседование — возможно это проблема компании. Устраиваясь на работу — вы заключаете двухсторонний договор. И как правило, все проблемы всплывают только, когда вы соблюдаете этот договор долгое время.
Кто его знает, чем они руководствовались. Может теперь сядут и будут внимательно изучать ваш код и перенимать ваш опыт. Может рынок исследуют. Может у фронтэндера жена в это время рожает… а ему там ваш код дали;)
Вообще, мой совет — не заморачиваться на этот счёт. Выше нос!