Автоматизация разработки курсов: путь от рутины к игре
Первый прототип будущей HTML Academy Саша Першин показал в 2012 году. У ученика были редакторы HTML и CSS, образец и картинка, которую браузер получал из его кода. Мы сравнивали изображения и показывали, насколько результат близок к заданному.
Так можно отрабатывать навыки, которые уже есть. Но у многих людей, пришедших попробовать прототип, этих навыков ещё не было. Прежде чем предложить человеку сверстать образец, нужно было научить его хотя бы отдельным действиям.
Поэтому появился второй интерфейс. Редакторы и маленький браузер остались, а вместо одного большого результата мы предложили несколько небольших целей: изменить размер, сдвинуть элемент, задать свойство. Проверка каждой цели отвечала «да» или «нет» сразу после изменения кода. Ученик видел, что именно у него получилось.
Как устроено задание
К началу 2014 года у нас было 16 курсов. Почти два года мы делали проект в свободное от основной работы время, пробовали разные идеи и постепенно поняли, каким хотим видеть обучение.
В курсе обычно от 10 до 30 заданий и от одного до трёх испытаний, где нужно применить изученное самостоятельно. Сложность растёт и в коде, и в формулировках. Вначале можно сказать: «Задайте элементу h1 размер шрифта 14 пикселей». Человек понимает, куда вставить значение, и сразу видит результат. Позже достаточно попросить увеличить заголовок в три раза: уже нужно самому определить, какой элемент менять и что именно вычислить.
Есть и скрытые цели. Мы не показываем их в списке, но любознательный ученик может найти такую цель, экспериментируя с кодом. Это дополнительная игровая механика для тех, кому интересно исследовать задание дальше обязательного решения.
Технически первые задания целиком работали на клиенте. Для редакторов мы выбрали Ace. Автор готовил HTML и CSS, а к каждой цели писал JavaScript-проверку. Она запускалась в реальном времени: не нужно было нажимать кнопку и ждать ответа. Цели можно выполнять в любом порядке.
При этом создавать сами задания было неудобно. Прототип вырос из институтского проекта: содержимое хранилось в базе данных, а авторы заполняли текстовые поля в административном интерфейсе. В эти поля попадали и описание, и код. Работать так с одним экспериментом можно, с сотнями заданий — уже тяжело.
С начала 2014 года мы смогли заниматься Академией постоянно. На момент выступления у нас 28 курсов, около 500 заданий, 55 испытаний и почти 2000 целей. За этими числами стоит много кода, который необходимо проверять и менять.
Курсы переехали в файлы
Масштаб проблемы стал особенно заметен после разговора с Вадимом Макеевым. В заданиях мы привычно писали «хедер», «футер», «бордер», «паддинг». Вадим предложил говорить по-русски: шапка, подвал, рамка. У сообщества «Веб-стандарты» уже был словарь терминов. Мы согласились с подходом, но впереди оказалась правка сотен заданий через неудобную админку.
Тогда мы посмотрели на содержимое курсов иначе. Почти всё это статические данные, которые затем выполняются на клиенте. Зачем держать их в форме, неудобной для работы с текстом и кодом?
Мы завели папку для каждого курса, внутри — папки заданий. Настройки хранятся в JSON. Рядом лежат описания, HTML, CSS и JavaScript-проверки, каждый в своём обычном файле. Автор открывает их привычным редактором. Если конкретному курсу нужна новая настройка, её можно добавить, не перестраивая ради этого форму и структуру базы.
Так содержимое стало доступно обычным инструментам разработки. Найти термин во всех заданиях, заменить его, проверить код или обработать изображения теперь можно было сразу во всём наборе файлов.
Но одной файловой структуры недостаточно. Автору нужно видеть, как задание работает, а нам — собирать из исходников версию для сайта.
Рабочее место автора
Мы сделали отдельный режим разработки на Node.js. Он собирает курсы и цели на лету и показывает интерфейс, похожий на ученический: редакторы, результат в небольшом браузере, список целей. Автор меняет файл и проверяет задание без отдельного ожидания сборки.
Код можно поправить и прямо в браузере, попробовать решение и сохранить изменения. Когда автор переходит к следующему заданию, которого ещё нет, система создаёт его по образцу предыдущего: копирует описание, HTML и CSS. Это удобно для курса, где каждое новое упражнение продолжает уже начатую работу.
В режиме разработки видны дополнительные сведения. Например, какие значения на самом деле сравнивает проверка. Между кодом ученика и условием цели могут сработать функции нормализации, поэтому автору важно видеть конечные значения, а не гадать, что произошло внутри.
Для повторяющихся действий мы добавили команды: создать курс или задание по шаблону, переместить их, удалить. Возможно, позже вокруг этих команд появится графический интерфейс. Пока основная работа доступна через командную строку.
Выбор инструмента здесь не должен зависеть от моего вкуса. Я, например, не особенно люблю препроцессоры HTML, но это рабочее место авторов. Если им будет удобнее готовить материалы через Jade или другой инструмент, смотреть нужно на их работу. Мы сначала перевели содержимое в файлы, а дальнейшие улучшения ещё впереди.
Единый код без одинаковых привычек
При сборке мы применяем привычные для фронтенда инструменты: оптимизируем изображения, HTML, CSS и JavaScript. Описания проверяем через Яндекс.Спеллер и обрабатываем типографом Муравьёва. Ошибки в тексте тоже доходят до ученика, поэтому проверять только программу недостаточно.
Когда авторов стало несколько, обнаружилась ещё одна проблема. Каждый оформлял код по-своему. В одном задании ученик видел один порядок свойств и отступы, в следующем — другой. Нужно было отделить различия в привычках автора от того, что мы хотим показывать в учебном материале.
HTMLHint и CSSLint помогают находить ошибки и нарушения нужных нам правил. ESLint проверяет JavaScript. CSScomb приводит CSS к общему оформлению и порядку свойств уже при подготовке публикации. Во время работы автор может оставлять привычные отступы; ученик получит код, оформленный по правилам Академии.
Ошибки в исходном HTML или CSS неприятны, но ещё хуже ошибка в проверке. Человек всё сделал правильно, а цель не засчитана. Иногда тест учитывает только один вариант решения, иногда неверно сравнивает результат. При таком объёме заданий рассчитывать исключительно на ручную проверку уже нельзя.
Сколько кода показывать ученику
Первые упражнения обходились несколькими строками. Потом появились меню, интерфейсные элементы, игровые миры для курсов о трансформациях и анимации. Чтобы одна учебная задача выглядела понятно и интересно, вокруг неё требовалось много оформления.
Если показать весь этот код в редакторе, ученик будет искать нужное изменение среди десятков посторонних правил. Поэтому всё, что не относится к текущей задаче, мы стали выносить во внешние ресурсы. Часть кода всё же нужна, чтобы понимать устройство примера; такие фрагменты можно свернуть в редакторе, оставив возможность раскрыть их.
Оформление часто повторяется. Например, человек собрал страницу в курсе по разметке, затем видит её в заданиях про формы или селекторы. Ему приятно продолжать работу в знакомой обстановке, но для нас копирование означает ещё несколько мест, где придётся исправлять одну ошибку.
Из повторяющихся элементов постепенно складывается библиотека компонентов. Общее оформление курса выносится в тему, которую можно подключать к заданиям. Мы сохраняем знакомый ученику мир и перестаём хранить множество независимых копий его кода.
Проверка в браузере ученика
Долгое время все проверки выполнялись на клиенте. Обычно жалоба «я всё сделал, а цель не засчитывается» приводила к ошибке в нашем тесте. Но однажды ученик сообщил, что не может выполнить задание с рамкой. Мы взяли ту же версию Chrome, проверили разные варианты и не смогли воспроизвести проблему.
Дальше можно было бы просить человека показать экран и вместе разбираться в его окружении, но нам не хотелось делать это обычным способом поддержки. Тогда мы впервые всерьёз задумались о серверной проверке.
Мы брали HTML и CSS ученика, помещали их в подготовленный шаблон и открывали через PhantomJS. Там выполнялись те же проверки целей, после чего результат возвращался ученику. Это сняло много проблем, связанных с конкретным браузером и его окружением.
Однако PhantomJS тоже использует браузерный движок. Перенос проверки на сервер не избавляет от ограничений этого движка.
Три месяца на линейные градиенты
Обычно курс занимал у нас около месяца, а линейные градиенты мы делали три. За время развития CSS их синтаксис успел несколько раз измениться, причём браузеры уже реализовали разные промежуточные варианты. Даже версии WebKit понимали градиенты по-разному.
Мы хотели учить актуальной записи: linear-gradient, направление вроде to top. Объяснять новичку историю префиксов и старых вариантов только ради прохождения задания не хотелось. Серверная проверка казалась выходом, но WebKit в PhantomJS тоже не понимал нужный нам синтаксис.
Попробовали SlimerJS на движке Firefox. В наших проверках он не помог и добавил другие проблемы. Мы уже думали отложить курс, когда вспомнили про Autoprefixer: у нас новый CSS и старый WebKit, как раз тот случай, для которого нужен перевод в поддерживаемую запись.
Перед серверной проверкой мы стали пропускать CSS ученика через Autoprefixer. PhantomJS получал код со старыми префиксами, а ученик продолжал писать современный вариант. Для курса о градиентах этого оказалось достаточно.
На клиенте у нас работает PrefixFree. Из зала предлагают добавить Modernizr, но проверка наличия возможности сама по себе не научит браузер её выполнять. Она сообщит, что градиент не поддерживается; ученик всё равно захочет решить задачу в своём браузере.
Остаётся и более глубокое ограничение: если старый движок вообще не умеет нужную возможность, преобразование записи не спасёт. Мы ждём PhantomJS 2, но за время ожидания его WebKit тоже успевает устареть. Есть и другие похожие инструменты, включая TrifleJS для Internet Explorer, однако подходящего решения для себя мы пока не нашли.
Мне очень нужен Chrome без графического интерфейса, который обновлялся бы вслед за обычным браузером. Пусть даже с задержкой в месяц: тогда было бы понятно, когда новая возможность станет доступна в серверных проверках. В 2014 году такого инструмента у нас нет.
API, который знает о различиях браузеров
Каждое дополнительное преобразование усложняло жизнь автору. Ему уже требовалось понимать, когда отправлять код на сервер, как подготовить CSS и какие особенности браузеров учитывать. Поэтому мы собрали эту работу в HTML Academy JavaScript API.
Автор описывает проверку, а API берёт на себя нужный способ выполнения и подготовку кода. Заодно в нём появились операции, которые помогают сравнивать стили на клиенте.
Например, при чтении вычисленного transform браузер возвращает матрицу. Для нас это сначала было хорошей новостью: вместо отдельного разбора scale, rotate и skew можно сравнивать один вид результата. Но затем выяснилось, что браузеры по-разному округляют числа. Отличались Firefox, Chrome 35, следующая версия Chrome и WebKit на сервере. Визуальный результат один, строковое представление — разное.
Мы добавили нормализацию чисел. Автору не нужно заново разбираться в погрешностях каждый раз, когда он готовит цель на трансформацию.
Составные свойства создают другую трудность. Ученик пишет border, а проверять приходится ширину, стиль и цвет каждой стороны. С padding, font и background тоже нельзя рассчитывать, что вычисленные стили вернут ровно ту сокращённую запись, которую ввёл человек. Цвет red или шестнадцатеричное значение, например, превращается в запись через RGB.
Для таких сравнений мы создаём невидимый эталонный элемент и задаём ему ожидаемые стили. Затем получаем getComputedStyle для него и для элемента ученика. Браузер преобразует обе записи одинаковым способом, и мы сравниваем результаты по нужным свойствам. Автору достаточно описать ожидаемый CSS в привычном виде.
Вспомогательный элемент позволяет поручить самому браузеру разбор и нормализацию того CSS, который он уже умеет понимать.
DOM может скрыть ошибку в HTML
Проверять только получившееся дерево документа тоже недостаточно. Мы просим ученика обернуть заголовок в h1. Он пишет открывающий тег, забывает закрывающий, а браузер всё равно строит DOM и создаёт элемент. Если тест ищет только его наличие, цель может оказаться выполненной.
Но мы хотели научить человека писать разметку, а не рассчитывать на исправления браузера.
В некоторых заданиях можно дополнительно сравнивать текст внутри элемента: незакрытый тег захватит лишнее содержимое. Однако такой способ не годится на все случаи. Иногда текст заранее неизвестен, иногда слишком велик, чтобы автор вручную описывал его в проверке.
Мы взяли HTMLHint, который уже проверял авторский код, и подключили его к проверке ученического HTML на клиенте и сервере. В API появился строгий режим: автор указывает, что нужна проверка исходной разметки, и инструмент подключается автоматически. Теперь удачно построенный DOM не скрывает ошибки, которые мы хотим научить замечать.
Проверить все цели и найти непонятное задание
К этому моменту у нас уже есть файлы с заданиями и сервер, способный запускать их проверки. После небольшой подготовки мы смогли выполнять проверки автоматически в PhantomJS. Это даёт возможность убедиться, что задания технически решаются, без ручного прохождения каждой цели.
Но технически исправное задание всё равно может быть непонятным. Для таких случаев нужны другие сведения.
Мы смотрим, какая доля начавших курс доходит до каждого следующего задания. В самом первом курсе постепенное уменьшение числа учеников ожидаемо: люди знакомятся с Академией и решают, подходит ли им такой способ учиться. До первого испытания доходит 69%. По ровному снижению трудно выбрать одно задание, с которого нужно начать разбор.
В курсе о трансформациях картина другая. После четвёртого задания теряется сразу около 15% учеников, а к середине из начавших остаётся примерно половина. Здесь уже есть конкретное место, которое стоит изучить.
Ещё полезнее оказалось время выполнения. Мы собираем его по заданиям и смотрим медиану в секундах. В одном из первых курсов большинство заданий занимало сопоставимое время, а на двенадцатом медиана доходила примерно до 400 секунд.
Прочитали само задание и комментарии учеников. Оказалось, люди не понимали, чего от них хотят: код проверки был исправен, затруднение вызывала формулировка. Мы переписали её и посмотрели на следующую группу проходивших курс. Время вернулось к обычному для соседних заданий уровню.
Что теперь можно менять
Файловая структура позволяет собирать из одного подхода разные интерфейсы. Для курса об адаптивной вёрстке понадобится показывать результат на нескольких экранах. Мы сможем заменить шаблон конкретного курса и оставить подготовку содержимого в знакомом виде.
Можно менять и редакторы. Вместо исходного HTML дать Haml или Jade, вместо CSS — Less или Sass, а затем проверять результат преобразования теми же целями. Есть идея заменить редактор CSS редактором SVG: в HTML останется место для изображения, во втором окне ученик будет работать с самой векторной графикой.
Отдельная задача — запускать автотесты в новых версиях настоящих браузеров. Пока успех проверки в PhantomJS говорит о его WebKit. Он не гарантирует, что все наши цели работают в свежем Chrome или Firefox. Хотим включить такие прогоны в непрерывную интеграцию, чтобы узнавать о несовместимости до писем от учеников.
Это планы, а не уже готовые возможности. Мы лишь с начала года занимаемся проектом постоянно. Но перенос материалов в файлы дал нам возможность применять к ним обычные приёмы автоматизации фронтенда. Та же работа с исходниками оказалась полезна для производства учебного содержимого.
Для кого мы это делаем
Академия выросла в том числе из опыта агентства. Большие компании могли предложить нашим разработчикам больше денег; нам приходилось брать стажёров и снова объяснять основы новым людям. Хотелось сохранить эту работу и сделать обучение воспроизводимым.
Сейчас в команде около пяти человек. Мы развиваем платформу, инструменты для авторов и сами курсы. Смотрим на другие образовательные проекты: мне интересны решения Code School и Code Avengers. Но исходной задачей для нас было собственное обучение людей.
Мне близка мысль, что базовое понимание вёрстки становится частью компьютерной грамотности для работающего в ИТ человека. Это наше представление об аудитории, а не требование ко всем вокруг. Среди учеников могут быть школьники, студенты и взрослые специалисты. Мы также добавили поддержку Internet Explorer 11: познакомились с его возможностями и решили, что с этой версией можно работать.
Первые 16 интерактивных курсов бесплатны. Продвинутые в 2014 году доступны по месячной подписке за 300 рублей. Основной доход нам дают месячные онлайн-интенсивы стоимостью около 10 тысяч рублей.
На интенсиве человек занимается на базе курсов, участвует в вебинарах и верстает выбранный макет. За ним закреплён наставник, работающий в индустрии, который консультирует его в течение месяца. Минимальный практический результат — один собственный свёрстанный макет; кто успевает больше, может продолжить с другими. В этот момент автоматические задания встречаются с живой работой: у ученика появляется проект и человек, к которому можно обратиться с вопросом.