Школа разработки интерфейсов: разбор заданий для Минска и новый набор в Москве
Сегодня открылся новый набор в Школу разработки интерфейсов Яндекса в Москве. С 7 сентября по 25 октября пройдёт первый этап обучения. Студенты из других городов смогут в нём поучаствовать дистанционно или очно — компания оплатит дорогу и проживание в хостеле. Второй, он же финальный этап продлится до 3 декабря, его можно пройти только очно.
Меня зовут Юлия Середич, этот пост мы написали вместе с Сергеем Казаковым. Мы оба разработчики интерфейсов в минском офисе Яндекса и выпускники ШРИ прошлых лет.
По случаю открытия регистрации в Москве мы публикуем разбор вступительных заданий в предыдущую Школу — здесь, в Минске.
Если проследить историю заданий ШРИ, то мы из года в год проверяли три важных для программиста навыка:
Вёрстка. Каждый разработчик должен уметь верстать. Не бывает такого, что у вас есть дядя Сережа, который верстает для всей команды, а вы только пишете скрипты. Поэтому и каждый студент должен показать, как он умеет верстать.
JavaScript. Если бы дело ограничивалось вёрсткой, то у нас была бы не Школа разработки интерфейсов, а Школа верстальщиков. Красивый свёрстанный интерфейс нужно оживить. Поэтому всегда есть задание на JS, но иногда оно же является и заданием на алгоритмы — настолько сильно мы их любим.
Решение проблем — пожалуй, главный навык разработчика. В создании интерфейсов всё очень быстро меняется. Это как у Льюиса Кэролла: «Приходится бежать со всех ног, чтобы только остаться на том же месте, а чтобы попасть в другое место, нужно бежать вдвое быстрее». Каждый день мы сталкиваемся с новыми технологиями — необходимо с ними считаться и уметь в них разобраться. Поэтому в третьем задании мы предложили разобраться в технологиях, с которыми начинающий разработчик обычно не знаком.
В разборе каждого задания мы расскажем не только о правильном порядке действий, но и о распространённых ошибках.
Задание 1: Портфолио
Над первым заданием работал дизайнер Яндекс.Коллекций Алексей Черенкевич, который умеет верстать, и его коллега по сервису — разработчик интерфейсов Сергей Самсонов.
Условие
Создайте сайт-портфолио: расскажите о себе, своих работах и ожиданиях от Школы. Сайт должен максимально соответствовать предложенному макету (ссылки на макеты: 1000px, 600px, 320px, спецификация). Нас интересует только вёрстка, поэтому JavaScript просьба не использовать.
При проверке мы будем учитывать:
размеры отступов, правильность цвета, начертание шрифтов, размер кегля;
семантическую вёрстку;
наличие различных состояний элементов: отображение кнопок и ссылок при наведении курсора, выделение активных полей ввода и т.д.;
кроссбраузерность (проверяется в последних версиях популярных браузеров).
Плюсом будет:
использование современных CSS-решений: flexbox, grid и др.;
адаптивная вёрстка;
использование пре- и (или) постпроцессоров, сборка, минификация, оптимизация выходного кода;
HTML-валидация формы, стилизованная кнопка загрузки файлов.
Задание достаточно объёмное, поэтому можно пропустить то, что не будет получаться. Это немного снизит балл, но вы всё же сможете продемонстрировать свои знания. По завершению работы пришлите нам две ссылки — на портфолио и исходный код на GitHub.
Макеты, предложенные в задании, были не просто с экранами для мобильных устройств, планшетов и десктопов, но и с настоящей спецификацией.
Чтобы внести в результат проверки первого задания как можно больше объективности, критериев этой проверки было очень много.
Критерии
Свёрстанный сайт. Это кажется очевидным, но некоторые ребята пропускали некоторые блоки целиком — то ли хотели сэкономить время, то ли не смогли сделать их. Макет условно можно поделить на четыре основных экрана: главный экран с аватаркой, блок со списком ожиданий от ШРИ, блок с портфолио и блок с контактной информацией. Их можно было делать секциями или просто с помощью div, главное — чтобы в наличии были все четыре блока.
Соответствие вёрстки макету. Дизайнер сделал отдельную спецификацию (включая цвета, типографику, состояния кнопок и прочее), чтобы кандидатам было легче. Внизу находилась подсказка по отступам и особенностям первого экрана. Очень порадовали ребята, которые учли все пожелания дизайнера: например, первый экран должен был получиться не меньше высоты вьюпорта.
Адаптивная вёрстка — это когда интерфейс не просто свёрстан так, чтобы на трёх разрешениях все было пиксель в пиксель по макету. В промежуточных состояниях вёрстка тоже не должна разваливаться. Кто-то забывал ограничить максимальную ширину контейнера и тянул всё на 1920 пикселей, кто-то напортачил с фонами, но в целом кандидаты справились с этой задачей хорошо.
Семантическая вёрстка. «Уж сколько раз твердили миру», что ссылка должна быть оформлена как <a>, кнопка — как <button>. К счастью, большинство кандидатов выполнили и это требование. Не все распознали притаившийся список в ожиданиях от ШРИ, сделав его при помощи тегов div, но это не так страшно. Был кандидат, который вставил все семантические теги, которые только знал — где надо и где не надо. Например, вместо списка — <section> и <article>. Всё-таки семантика — она про понимание состава своей странички и назначение каждого блока (здесь большинство справилось), а также про использование пре- и/или постпроцессоров (здесь справились немногие, хотя это тоже было в пунктах — чаще всего подключали less и scss).
Работающий слайдер. В задании мы написали, что JS использовать нельзя. Тут проверялась способность решать проблемы — слайдер можно было сделать с помощью связки <input> и <label for=”#id”>. Вся магия происходит на уровне селектора #button-N:checked ~ .slider-inner .slider-slides. Когда мы кликаем по одному из инпутов-чекбоксов, он переходит в состояние checked. Мы можем этим воспользоваться и назначить нужный нам translate на контейнер со слайдами: transform: translate(-33%). Реализацию слайдера можно посмотреть здесь.
Раскрывающиеся списки. Тут всё тоже сводилось к <input name=«accordion» type=«radio»> и похожему селектору: .accordion-item input:checked ~ .accordion-item__content. Реализацию можно посмотреть здесь.
Наличие состояний :hover, :active и :focu*. Очень важный пункт. От него зависел комфорт во время взаимодействия с интерфейсом. Пользователь всегда должен получать обратную связь о своих действиях. Этот пункт проверялся на протяжении всего взаимодействия с анкетой. Если я нажал кнопку «Позвоните мне» и визуально ничего не случилось (хоть запрос и отправился) — это плохо, потому что затем я нажму её вновь и вновь. В итоге отправится десять запросов и мне перезвонят десять раз. Не нужно забывать и о том, что на мобильных устройствах нет мыши — а значит, не должно быть hover. И ещё один момент, который не коснулся тех, кто выполнил пункт про семантику. Если ваш контрол не является интерактивным элементом, то при наведении на него курсор останется стандартным. Это выглядит очень неопрятно, даже если вы прописали реакцию на hover. Не стоит недооценивать cursor: pointer.
Анимации. Важно, чтобы все происходящие с элементами реакции были плавными. В жизни нет ничего мгновенного, поэтому наличия transitions на hover и active было достаточно для того, чтобы сделать интерфейс приятнее. Ну а те, кто анимировал слайдер и списки, вообще молодцы.
Использование новейших технологий. Многие использовали flex, при этом никто не выполнил задание с помощью grid. Пункт засчитывался, если flex использовался грамотно. Если где-то вёрстка из-за этих самых flex разъезжалась — увы, дополнительных баллов вы не получали.
Валидация формы. Требовалось всего лишь дописать в каждый input формы атрибут required. Мы прибавляли балл тем, кто валидировал поле электронной почты как email.
Cтилизация кнопки загрузки файлов. Мы ожидали увидеть связку вида: <input type=”file” id=”file” name=”file” class=”inputfile” /> и <label for=”file”>Выберите файл</label>. Дальше требовалось скрыть input и стилизовать label. Есть ещё один распространённый способ — сделать прозрачный input и положить его поверх кнопки. Но не все браузеры разрешают стилизовать <input type=”file”>, и такое решение нельзя назвать в полной мере кроссбраузерным. Да и семантически правильнее сделать label.
Кроссбраузерность. Мы проверяли, что всё хорошо, в двух последних версиях современных браузеров (без IE — участникам повезло), а также в Safari на айфонах и в Chrome на андроидах.
Мы, наоборот, снимали баллы, если кто-нибудь использовал JS или Bootstrap: и то и другое лишало смысла всё задание. Причём участники с Bootstrap не только получали минус, но и недополучали много баллов за семантику и реализованные элементы.
Те, кто разметил свой сайт где-нибудь в интернете, не получили особого преимущества — но проверяющие очень радовались, когда не приходилось скачивать репозитории и запускать их локально у себя на компьютере. Так что это служило плюсом в карму.
Первое задание было очень полезным в первую очередь для студента. У тех, кого мы не приняли, теперь есть свёрстанное резюме — можно гордо прикреплять его ко всем откликам или выложить на свой gh-pages.
Задание 2: Транспортный путь
Автор задания — руководитель группы поисковых интерфейсов Денис Балыко.
Условие
У вас есть карта звёздного неба. На ней указано название каждой звезды, а также расстояние от неё до других звёзд в световых секундах. Реализуйте функцию solution, которая должна принимать три аргумента: объект, в котором ключами являются названия звёзд, а значениями — расстояния до звёзд (в космосе одностороннее движение), а также названия начальной и конечной точки пути — start и finish соответственно. Функция должна возвращать кратчайшее расстояние от звезды start до звезды finish и путь, по которому нужно пройти.
Примечание: каркас решения находится в папке src/, поместите свое решение в solution.js.
Проверка второго задания была самой автоматизированной и объективной. Большинство ребят догадались, что необходимо реализовать алгоритм Дейкстры. Те, кто нашёл его описание и реализовал алгоритм на JS, — молодцы. Однако при проверке задания мы встретили много работ с одинаковыми ошибками. Мы поискали в интернете по фрагментам кода и нашли статью, откуда участники списали алгоритм. Забавно, что многие копировали код из статьи вместе с комментариями автора. Такие работы получили низкий балл. Мы не запрещаем пользоваться любыми источниками, но хотим, чтобы человек вникал в то, что он пишет.
Критерии
Основные баллы начислялись за тесты. Иногда было видно, что ребята похозяйничали в репозитории, переименовав папки, и тесты падали просто потому, что не могли найти нужные файлы. В этом году мы старались помочь таким ребятам и возвращали всё на место за них. Но в следующем году мы планируем перейти на систему контестов, и подобное уже не будет прощаться.
Были и «человеческие», ручные критерии. Например — наличие единого кодстайла. Никто не снижал баллы за то, что вы используете табы вместо пробелов или наоборот. Другое дело, если вы чередуете одиночные кавычки с двойными по одному вам известному правилу, а точки с запятыми ставите вразнобой.
Отдельно учитывалась понятность и читаемость решения. На всех конференциях мира рассказывают, что работа программиста на 80% состоит из чтения чужого кода. Даже школьники проходят кодревью — у своих кураторов и друг друга. Так что этот критерий имел значительный вес. Встречались работы, в которых не было переменных длиннее одного символа — пожалуйста, не делайте так. Очень радовали комментарии участников — за исключением тех, которые были идентичны комментариям Стеллы Чанг.
Последний критерий — наличие автотестов. Их добавили всего несколько человек, но для каждого это стало огромным плюсом в карму.
Правильное решение:
const solution = function(graph, START, FINISH) {
// Всё не бесплатно в этом мире
const costs = Object.assign({[FINISH]: Infinity}, graph[START]);
// Первая волна родительских нод
const parents = { [FINISH]: null };
Object.keys(graph[START]).reduce((acc, child) => (acc[child] = START) && acc, parents)
const visited = [];
let node;
// Ищем «дешёвого» родителя, отмечаем пройденные
do {
node = lowestCostNode(costs, visited);
let children = graph[node];
for (let n in children) {
let newCost = costs[node] + children[n];
// Ещё не оценена или нашёлся более дешёвый переход
if (!costs[n] || costs[n] > newCost) {
costs[n] = newCost;
parents[n] = node;
}
}
visited.push(node);
} while (node)
return {
distance: costs[FINISH],
path: optimalPath(parents)
};
// Возврат назад по самым «дешёвым» родителям
function optimalPath(parents) {
let optimalPath = [FINISH];
let parent = parents[FINISH];
while (parent && parent !== START) {
optimalPath.push(parent);
parent = parents[parent];
}
optimalPath.push(START);
return optimalPath.reverse();
}
// Минимальная стоимость из текущей ноды среди непросмотренных
function lowestCostNode(costs, visited) {
return Object.keys(costs).reduce((lowest, node) => {
if (lowest === null || costs[node] < costs[lowest]) {
if (!visited.includes(node)) {
lowest = node;
}
}
return lowest;
}, null);
};
};
Задание 3: Календарь событий
Его подготовили разработчики интерфейсов Сергей Казаков и Александр Подскребкин.
Условие
Напишите мини-календарь для отображения расписания. Можно взять любое расписание, которое вам понравится. Например, расписание фронтенд-конференций в 2019 году.
Календарь должен выглядеть, как список. Других требований к дизайну нет. Сделайте возможность ставить напоминания о событии за 3, 7 и 14 дней. После первой загрузки с интернетом календарь должен открываться и функционировать в офлайне.
Третье задание было самым интересным для проверки, потому что нашлось очень много вариантов решения, у каждого свой. Мы проверяли, как кандидат обращается с незнакомыми технологиями — умеет ли исследовать, тестирует ли свои решения.
Критерии
Свёрстанный календарь. Да, его всё-таки требовалось сверстать. Были и те, кто понял условие слишком буквально и не вставил ни строчки CSS-кода. Это выглядело не очень лицеприятно, но если всё работало — баллы не снижались.
Получение списка событий из источника. Это уже задание не на вёрстку, поэтому вшитый в неё список мероприятий не засчитывался. Всегда можно отменить конференцию, перенести её, добавить новую. Так что требовалось получать данные извне и рендерить вёрстку уже на основе полученного JSON. Важно было каким угодно способом (методом fetch или с помощью XMLHttpRequest) получить данные. Если человек добавлял полифилл для fetch и помечал в readme свой выбор — это засчитывалось как плюс.
Регистрация service worker без ошибок и работа в офлайне после первой загрузки. Вот пример service worker с кэшированием расписания на первой загрузке. Подробности про service workers, их возможности и способы работы с ними (стратегии работы с кешем, работу в офлайне) можно посмотреть здесь.
Возможность установить напоминание, чтобы оно действительно сработало через 3, 7, 14 дней. Нужно было разобраться в Notifications API, ссылка на который находилась прямо в задании. Мы не ждали какой-то конкретной реализации проверки того, наступило ли время для пуша. Принимался любой работающий вариант: хранение в localStorage, IndexDB или периодический опрос сервис-воркером. Можно было даже сделать пуш-сервер (вот пример), но в офлайне он бы не работал. Не менее важно было получить пуш после того, как страницу закрыли — и открыли через какое-то время. Если напоминание «умирало» одновременно с закрытием страницы, решение не засчитывалось. Круто, когда ребята думали о проверяющих и делали возможность получить пуш прямо сейчас — чтобы не ждать 3 дня.
Возможность вынести иконку на рабочий стол (PWA). Мы проверяли наличие файла manifest.json с правильными иконками. Некоторые ребята сделали этот файл (или оставили сгененерированный в CreateReactApp) — но не добавили верные иконки. Тогда при попытке установки возникала ошибка вида «нужна другая иконка».
Кодстайл и структура проекта. Как и во втором задании, мы смотрели на единый кодстайл (даже если он не совпадал с нашим). Некоторые ребята прикручивали линтеры — это здорово.
Ошибки в консоли. Если прямо в консоли был индикатор, что что-то не так, а участник не обращал на него внимание, то мы снимали баллы.
Итоги
Забавное в решениях участников:
Одна анкета содержала следующий текст: «Собрать реакт-приложение помог друг программист. Я его закидывал вопросами что как и почему, он рассказывал. Очень понравилось, хочу больше узнать об этом». Мы всем сердцем болели за эту анкету, но к сожалению, друг кандидата не очень помог ему сделать работающее приложение.
Один кандидат прислал ссылку на GitHub, где лежал RAR-архив — сложно это как-то прокомментировать. 🙂
Ещё один кандидат в комментарии первой строчки файла solution.js честно признался, что скопировал алгоритм.
Мы получили анкеты от 76 кандидатов и отобрали из них 23 человека. Нам присылали анкеты не только из Минска, но и из Москвы, Санкт-Петербурга и даже Татарстана. Некоторые ребята удивили своими нынешними профессиями: один из них судмедэксперт, а другой — студент медицинского вуза.
Получилось интересное распределение успешности выполнения заданий. С первым заданием участники справились в среднем на 60%, со вторым — на 50%, а третье оказалось самым сложным и его выполнили в среднем на 40%.
На первый взгляд, задания выглядят сложными и трудоёмкими. Причина не в том, что мы хотим отсеять как можно больше кандидатов. Во время обучения студенты сталкиваются с реальными задачами — сделать чат, Яндекс.Музыку для детей или Яндекс.Погоду для метеозависимых людей. Для этого нужна стартовая база.
Я помню, как увидела своё вступительное задание в ШРИ два года назад и подумала, что мне его никогда не решить. Главное в этот момент — сесть, хорошенько вчитаться в условия и начать делать. Оказывается, в условиях содержится практически 80% решения. Например, в условие третьего задания (самого сложного) мы добавили ссылки на service workers и Notifications API на MDN. Студенты, которые изучили содержимое ссылок, справились без труда.
Очень хочется, чтобы эту статью прочли кандидаты, которые планируют поступать в ШРИ в дальнейшем, не смогли поступить в минскую Школу или начинают делать любое другое тестовое задание. Как видите, поступить вполне реально. Нужно только верить в свои силы и внимать всем подсказкам авторов.