Опыт разработки тестового задания на React для Aviasales

в 14:11, , рубрики: experience, react.js, ReactJS

Привет, я хотел поделиться опытом разработки тестового задания для Aviasales.

Я недавно наткнулся на вакансию React разработчика в компанию Aviasales. Отправил заявку, после чего на следующий день мне ответил HR и сообщил, что я должен буду сделать тестовое задание. Я крайне не люблю делать тестовые задания, так как я должен потратить довольно много времени на их выполнение, а в случае неудачи это станет впустую. Но я согласился...

Само тестовое задание вы можете найти тут по ссылке.

Вот тут ссылка на мой репозиторий выполненного задания.

Я ограничил себя в выполнении задания в один день (правда небольшие доработки я все же делал уже после публикации: дорисовывал эскиз, так сказать).

Что я выбрал для разработки:

  1. Я выбрал NextJS как основу, так как возиться с настройкой среды под Webpack мне не хотелось, да и задеплоить сам проект можно парой кликов.
  2. Я хотел писать быстро и выбрал пакет React-IOC в связке с MobX, вместо Redux. Это пакет, позволяющий писать приложения через сервисы, напоминающие сервисы angular.
  3. Я использовал Web Worker, чтобы не было лагов в интерфейсе во время сортировки большого объема данных.
  4. Я не использовал Typescript с целью не писать дополнительный код с бесполезной растратой времени на тестовое задание.
  5. Исходя из пункта 4 я так же не писал тестов.
  6. Я добавил в проект два дополнительных пакета: debounce, RxJS. Первый нужен для создания простых callback, например смену состояния загрузки, чтобы не показывать спиннер загрузки, если загрузка занимает крайне мало времени. Второй пакет я всегда использую для создания сценария действий, например, для обработки состояний в случае ошибки при отправке запроса на сервер.

Порядок действий первой стадии разработки:

  1. Проинициализировал репозиторий.
  2. Проинициализировал проект NextJS.
  3. Добавил базовую страницу index с сообщением Hello World.
  4. Создал сервис ticket.provider, который взаимодействует с api сервером.
  5. Создал сервис ticket.service, который инжектирует ticket.provider и заполняет обсервер с массивом отображаемых билетов
  6. Создал ticket.filter.service, который хранит в себе отфильтрованные данные, инжектирующиеся из ticket.service через @computed

Вторая стадия разработки:

  1. Создал компоненты и расписал стили к ним, используя макет, предоставленный в репозиторий задании.
  2. Сделал спиннер загузки и проставил его значение из сервисов.
  3. Подсоединил всю сервисную логику с компонентами.
  4. Добавил утилиты с форматированием данных, таких как время и деньги.

Тут я решил попробовать интерфейс «на ощупь» и нашёл недоработки при использовании приложения:

  1. Имеются подтормаживания интерфейса при изменении фильтра и сортировке, поэтому я перенёс хранение, получение и фильтрацию данных в Web Worker, после чего лаги полностью пропали.
  2. Спиннер не исправлял прыгающей анимации строк, вследствие чего я заменил его на визуальное отображение строк с анимацией мерцания.
  3. Для простоты рендеринга данных я перенёс вызовы форматирования данных в 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, то можно увидеть, что билеты показываются сразу как появляется первая партия, а не дожидаемся загрузки всех.

Далее, более технические моменты.

  1. Почему filter.service знает об router и window? Он не должен управлять состоянием приложения и иметь таких зависимостей вообще.
  2. Web Workers. В чем профит? В данном кейсе тратится много времени на асинхронные операции, а сэкономленную нагрузку в main thread тратим на serialize/deserialize объектов (и observable объектов). Если речь идет об оптимизациях, то стоило начинать не с выноса в web worker, а исправить проблемы в main thread.
  3. Нет смысла добавлять в проект rxjs только для реализации retry и последовательной загрузки.
  4. Проект невозможно масштабировать и поддерживать. Конструкции типа (ticket.segments || [{}, {}]).map((ticket) => ticket) сильно громоздские.
    React:
    Сложилось впечатление, что абсолютно нет понимания как работает React под капотом. Много критичных ошибок по работе с props у компонентов, которые очень плохо влияют на производительность. Игнорируются механизмы оптимизаций в React. Тезисно о проблемах:
  5. Создание функций-обработчиков внутри render.
  6. Создание нового пустого объекта и передача его в билет. ticket-list-loading.jsx:10 или Ticket.tsx:24.
  7. Индексы массива как ключи на списке билетов.
    В общем, в React не получилось :(

Итого: для middle уровня мы ожидаем, что с react не будет критичных проблем, но в выполненной работе их довольно много.

Автор: makamekm

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js