Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Привет, я хотел поделиться опытом разработки тестового задания для Aviasales.
Я недавно наткнулся на вакансию React разработчика в компанию Aviasales. Отправил заявку, после чего на следующий день мне ответил HR и сообщил, что я должен буду сделать тестовое задание. Я крайне не люблю делать тестовые задания, так как я должен потратить довольно много времени на их выполнение, а в случае неудачи это станет впустую. Но я согласился...
Само тестовое задание вы можете найти тут по ссылке.
Вот тут ссылка на мой репозиторий выполненного задания.
Я ограничил себя в выполнении задания в один день (правда небольшие доработки я все же делал уже после публикации: дорисовывал эскиз, так сказать).
Что я выбрал для разработки:
- Я выбрал NextJS как основу, так как возиться с настройкой среды под Webpack мне не хотелось, да и задеплоить сам проект можно парой кликов.
- Я хотел писать быстро и выбрал пакет React-IOC в связке с MobX, вместо Redux. Это пакет, позволяющий писать приложения через сервисы, напоминающие сервисы angular.
- Я использовал Web Worker, чтобы не было лагов в интерфейсе во время сортировки большого объема данных.
- Я не использовал Typescript с целью не писать дополнительный код с бесполезной растратой времени на тестовое задание.
- Исходя из пункта 4 я так же не писал тестов.
- Я добавил в проект два дополнительных пакета: debounce, RxJS. Первый нужен для создания простых callback, например смену состояния загрузки, чтобы не показывать спиннер загрузки, если загрузка занимает крайне мало времени. Второй пакет я всегда использую для создания сценария действий, например, для обработки состояний в случае ошибки при отправке запроса на сервер.
Порядок действий первой стадии разработки:
- Проинициализировал репозиторий.
- Проинициализировал проект NextJS.
- Добавил базовую страницу index с сообщением Hello World.
- Создал сервис ticket.provider, который взаимодействует с api сервером.
- Создал сервис ticket.service, который инжектирует ticket.provider и заполняет обсервер с массивом отображаемых билетов
- Создал ticket.filter.service, который хранит в себе отфильтрованные данные, инжектирующиеся из ticket.service через @computed
Вторая стадия разработки:
- Создал компоненты и расписал стили к ним, используя макет, предоставленный в репозиторий задании.
- Сделал спиннер загузки и проставил его значение из сервисов.
- Подсоединил всю сервисную логику с компонентами.
- Добавил утилиты с форматированием данных, таких как время и деньги.
Тут я решил попробовать интерфейс «на ощупь» и нашёл недоработки при использовании приложения:
- Имеются подтормаживания интерфейса при изменении фильтра и сортировке, поэтому я перенёс хранение, получение и фильтрацию данных в Web Worker, после чего лаги полностью пропали.
- Спиннер не исправлял прыгающей анимации строк, вследствие чего я заменил его на визуальное отображение строк с анимацией мерцания.
- Для простоты рендеринга данных я перенёс вызовы форматирования данных в Web Worker, это уменьшает нагрузку на рендеринг компонента
Тут я закончил свою работу и под конец дня отправил задание на проверку.
Так как я ограничил себя во времени, я не стал оптимизировать приложение далее, а именно я не стал использовать React.memo(...). Я так же не стал заменять window и router на инжекторы в сервисах. За это простите меня, это недоработка.
Но на следующий день я добавил функционал запоминания состояния фильтра в браузерной строке url, после чего уже был удовлетворён проделанной работой.
Я ждал ответа от них 7 дней, они не отвечали ни на одно мое сообщение. Это был неприятный опыт, так сказать. Но все же ответили, и ответ был крайне огорчающий к прочтению. Сообщение можно увидеть ниже.
Давайте разберём замечания по пунктам:
[1]. «Работа выполнена очень не аккуратно»
Не вижу где, не аргумент, так как нет примеров.
[2]. «Кажется, при разработке преследовались цели «оно же работает», и игнорировалось «как это работает»»
Не аргумент, нет примеров.
[3]. «Если исходить из наших требований к уровням кандидатов, то реализованное задание едва дотягивает до middle.»
Какие требования? Где они?
[4]. «Мы ожидаем, что фильтры не будут захардкожены и будут подстраиваться под данные. Если зайти на aviasales, то можно увидеть, что билеты показываются сразу как появляется первая партия, а не дожидаемся загрузки всех.»
Этого требования не было в приложенном задании. А само приложение Aviasales, на мой взгляд, не обладает эталонным интерфейсом, и прыгающие билеты не являются наилучшим решением.
[5]. «Почему filter.service знает об router и window? Он не должен управлять состоянием приложения и иметь таких зависимостей вообще.»
Потому что сервисы и созданы чтобы управлять состоянием всего приложения, или модуля, где они существуют. Тут автор явно предлагает писать всю логику в компонентах.
[6]. «Web Workers. В чем профит? В данном кейсе тратится много времени на асинхронные операции, а сэкономленную нагрузку в main thread тратим на serialize/deserialize объектов (и observable объектов). Если речь идет об оптимизациях, то стоило начинать не с выноса в web worker, а исправить проблемы в main thread.»
Как можно сделать обработку больших объемов данных в main thread и при этом без лагов интерфейса? Мне будет крайне интересно узнать, если есть у кого пример, то пишите в комментах. Видимо я что-то упускаю.
[7]. «Нет смысла добавлять в проект rxjs только для реализации retry и последовательной загрузки.»
Почему нет смысла? В RxJS можно импортировать строго необходимые функции, если tree shaking пакетов и функций не работают должным образом, то это не увеличит размер приложения.
[8]. «Проект невозможно масштабировать и поддерживать. Конструкции типа (ticket.segments || [{}, {}]).map((ticket) => ...)
сильно громоздские.»
Давайте пройдёмся по критериям масштабирования и поддержки: доступность (сервисы решают эту проблему), риски (ticket.segments || [{}, {}]
— как раз пример того как обходиться со случаями, если input не содержит данных. Пример плохой, но подход nullable structure как минимум, но я стараюсь соблюдать), чистый код (ну как минимум я знаю что это :), хотя старался писать все как положено). Вроде все схвачено, опять не аргумент.
[9]. «Сложилось впечатление, что абсолютно нет понимания как работает React под капотом. Много критичных ошибок по работе с props у компонентов, которые очень плохо влияют на производительность. Игнорируются механизмы оптимизаций в React.»
Не могу понять где эти описанные проблемы. Какие именно механизмы?
[10]. «Создание функций-обработчиков внутри render.»
У меня вообще нет никаких функций обработчиков в render, кто нибудь понимает о чем тут написано? Даже обработку форматирования я перенёс в Web Worker
[11]. «Создание нового пустого объекта и передача его в билет.ticket-list-loading.jsx:10
или Ticket.tsx:24
.»
Тут нет ничего критического, кроме моей лени выносить отдельно loading-ticket компонент. Передача пустого объекта не нарушает никакой принцип программирования, кроме того что тут нужно было сделать это через ticket = ticket || {};
но это сугубо из-за того что время на разработку было ограничено одним днём и исправлять все незначительные недочеты потребовало бы больше времени.
[12]. «Индексы массива как ключи на списке билетов»
Тут скорее вопрос к тому почему api не возвращает элементы с id. Поэтому, мне при получении данных с сервера нужно было бы генерировать уникальные ключи для каждого элемента, что я делать в рамках тестового задания не стал, так как даже в реальном проекте это сомнительный подход.
Ну и напоследок вывод: «В общем, в React не получилось (
Итого: для middle уровня мы ожидаем, что с react не будет критичных проблем, но в выполненной работе их довольно много.»
Тут уже без комментариев...
Благодарю, если дочитали до конца. У меня сложилось очень негативное мнение о Aviasales, поэтому выкладываю все тут, чтобы вы могли сами оценить, стоит ли вам связываться с ними или нет.
Полный текст сообщения:
Работа выполнена очень не аккуратно. Кажется, при разработке преследовались цели «оно же работает», и игнорировалось «как это работает». Если исходить из наших требований к уровням кандидатов, то реализованное задание едва дотягивает до middle.
Работоспособность и структура:
Мы ожидаем, что фильтры не будут захардкожены и будут подстраиваться под данные. Если зайти на aviasales, то можно увидеть, что билеты показываются сразу как появляется первая партия, а не дожидаемся загрузки всех.
Далее, более технические моменты.
- Почему
filter.service
знает об router и window? Он не должен управлять состоянием приложения и иметь таких зависимостей вообще. - Web Workers. В чем профит? В данном кейсе тратится много времени на асинхронные операции, а сэкономленную нагрузку в main thread тратим на serialize/deserialize объектов (и observable объектов). Если речь идет об оптимизациях, то стоило начинать не с выноса в web worker, а исправить проблемы в main thread.
- Нет смысла добавлять в проект rxjs только для реализации retry и последовательной загрузки.
- Проект невозможно масштабировать и поддерживать. Конструкции типа (
ticket.segments || [{}, {}]).map((ticket) => ticket
) сильно громоздские.
React:
Сложилось впечатление, что абсолютно нет понимания как работает React под капотом. Много критичных ошибок по работе с props у компонентов, которые очень плохо влияют на производительность. Игнорируются механизмы оптимизаций в React. Тезисно о проблемах: - Создание функций-обработчиков внутри render.
- Создание нового пустого объекта и передача его в билет.
ticket-list-loading.jsx:10
илиTicket.tsx:24
. - Индексы массива как ключи на списке билетов.
В общем, в React не получилось :(
Итого: для middle уровня мы ожидаем, что с react не будет критичных проблем, но в выполненной работе их довольно много.