Согласитесь, что от хорошего образовательного курса мы ждём не только интерактива и большого объёма практических заданий, но и возможность быстро получать обратную связь. А ещё лучше — молниеносно.
Человеческий фактор никто не отменял: как бы ни старались наши эксперты проверять работы по программированию и аналитике быстрее, это всё равно происходило не моментально. Так как скорость обработки заданий влияет на результаты учеников, нам очень хотелось эту скорость увеличить. Не менее важным было сократить трудозатраты проверяющих экспертов, чтобы они могли сосредоточиться на качественной обратной связи.
Именно поэтому у нас появилась идея создать онлайн-тренажёр. Студенты могли бы сами писать код, работать с ошибками, брать подсказки, получать мгновенную обратную связь и приступать к следующим заданиям.
Привет, меня зовут Мария Выволокина, и я — product manager Нетологии. Рассказываю, как мы создавали тренажёр по программированию с автопроверкой задач на разные языки программирования.
Почему мы решили создать тренажёр по программированию
Практика — то, чего ждут студенты для отработки теории. Но некоторые базовые навыки необходимо закреплять решением большого количества задач. Это значит, что преподаватели, эксперты вынуждены тратить десятки часов на проверку однотипных заданий. Ученикам удобнее, когда все тренировочные задания находятся на одной платформе и не приходится искать тренировочные задания на сторонних ресурсах.
Также студенты ждут моментальной обратной связи. Это значит — быстро получать реакцию на своё задание, чтобы двигаться дальше. Так сохраняется мотивация, интерес к дальнейшей учёбе.
Тренажёр по программированию — это среда, встроенная в платформу, в которой студент может писать код, получать вывод, а также получать мгновенную обратную связь, благодаря автопроверке задач на разные языки программирования.
Задачи, которые должен был решить наш тренажёр:
→ уменьшить число рутинных проверок заданий преподавателем;
→ повысить эффективность образовательного процесса.
Что учли и не учли при проектировании
Важным фактором, который повлиял на решении о запуске проектирования тренажёра, было качество образования. Нужно было понять, не снизит ли он качество образовательной составляющей практических заданий. Нам было важно, чтобы с появлением тренажёра студенты не стали хуже справляться с практикой.
В качестве целевого показателя мы выбрали соотношение количества студентов, которые успешно справлялись с тренажёром к тем, кто к нему приступил. Этот показатель мы сравнивали с показателем успешного выполнения домашних заданий на потоках, где тренажёра не было. Нам было важно не уронить эту метрику.
Мы исследовали рынок и учли опыт конкурентов. Провели анализ существующих на тот момент решений как среди иностранных обучающих платформ, так и среди отечественных. Наиболее интересным нам показались продукты отечественных компаний — тренажёры Хекслета и Яндекс.Практикума. Но копировать чужое решение в лоб не хотелось, поэтому решили разработать свой уникальный продукт.
Собрали фидбэк от преподавателей и поняли, что самый трудоёмкий процесс — проверка синтаксиса кода. Где-то забыли двоеточие, где-то запятую, выбрали не ту встроенную функцию, не там поставили отступ и так далее.
Максим Сахаров
Старший консультант по Data Science в «БазисСофт», кандидат технических наук. Преподаватель на курсах «Python для анализа данных» и «Математика для Data Science»
Можно долго смотреть, как пишут программы преподаватели, но как только начинаешь пробовать писать самостоятельно, такие ошибки обязательно появляются. Поэтому тренажёр стал своего рода набором дидактических материалов, чтобы помочь отработать механические навыки. Задания в нём простые: определить требуемый размер выборки или построить доверительный интервал для среднего значения.
За месяц мы подготовили MVP тренажёра.
Мы ожидали, что внедрение тренажёра будет проходить быстро: адаптируем задания под тренажёр или разрабатываем новые, добавляем, готово. Но оказалось, задания для тренажёра были составлены недостаточно корректно, были ошибки в формулировках. Это оказалась одна из основных сложностей.
Формулировки заданий должны быть простыми и очевидными. Потому что машина — это инструмент, который проверяет задание по определённым критериям, чтобы студент понял, как её нужно решить. Именно после этого мы стали уделять больше внимания, основательно подходить к разработке таких заданий. Это кропотливая работа методиста в связке с преподавателем.
На чём собрали тренажёр
Первоначальная версия тренажёра была для Python. В дальнейшем добавляли примерно по одному языку программирования в месяц. Продолжаем это делать и сейчас. После Python добавили Java, JavaScript, SQL, HTML, Bash и Swift.
Александр Мищенко
Тимлид разработки отдела «Контент и методология»
Реализация тренажёра основана на технологиях виртуализации и изолированных контейнерах. Для этого мы используем Docker и Yandex.Cloud. Часто такие решения называют «песочница». Суть в том, что код, который пишет студент, выполняется изолированно и не может случайным или намеренным образом внести какие-то изменения в чужой процесс.
Когда мы начинали проектировать техническую реализацию тренажёра, у нас был очень ограниченный бюджет и мало людей. Чтобы не закапываться в экзотические технологии, мы взяли то, что используем в своей работе каждый день.
В этот момент мы готовились к переезду в Yandex.Cloud, и наш разработчик написал маленький тестовый сервис для отдела SRE. На нём мы совместно отрабатывали все основные процессы по CI/CD.
На первый взгляд всё выглядело очень просто:
Подготовили docker image с необходимой средой;
Взяли код, который надо выполнить;
Поместили код в контейнер;
Запустили код;
Получили результат.
Использование Docker для нашей задачи позволяет ещё и подготавливать огромное разнообразие сред исполнения для любых заданий. Мы можем почти без доработок, настроив один раз, использовать готовый образ для разных заданий, с одной стороны. С другой — с лёгкостью можем использовать разные среды исполнения в рамках одного курса. Мы получили очень гибкий инструмент. Но всем этим оркестром надо было как-то управлять.
Первое, что попробовали в качестве оркестратора — Kubernetes. И он нам не подошёл. Kubernetes — это программное обеспечение для оркестровки контейнеризированных приложений, автоматизации их развёртывания, масштабирования и координации в условиях кластера. Его задача — обеспечить работу сервиса, а нам нужно было запускать много краткосрочных процессов с разными, что важно, средами исполнения (docker image). Да, и его API для нас оказался не очень удобным. Kubernetes — замечательное программное обеспечение, но не для нашей задачи.
Следующим был — Swarm. Спойлер: и от него мы отказались, но не сразу. У Swarm был один существенный плюс — его API полностью совместимое с API Docker. Мы понимали, что это временное решение, но были очень ограничены в ресурсах. У нас не было разработчиков, чтобы написать то, что надо, в приемлемые сроки.
Недостатки выявились уже после запуска проекта в продакшн. Точнее, его основной недостаток — нестабильность системы.
Александр Мищенко
Тимлид разработки отдела «Контент и методология»
Один запущенный образ с «плохим» кодом мог положить весь кластер, и нас это не устраивало. Swarm помог нам запустить тренажёр с минимальными затратами и, что важно, подсветил узкие места, показал многие «подводные камни».
Это был хороший опыт, благодаря которому мы теперь намного лучше понимаем, каким должен быть наш продукт с точки зрения неубиваемости. В итоге мы пришли к выводу, что нам надо писать свой оркестратор, полагаясь на полученный опыт.
В итоге мы заменили Swarm на собственную разработку балансировщика и оркестратора для запуска докер-контейнеров. Наши контейнеры, в отличие от того же k8s, не долговечны и должны работать ограниченное время с ограниченными ресурсами. Код, написанный студентом, потенциально опасен. Система отслеживает «зависшие» контейнеры и уничтожает их.
Далее мы имеем несколько независимых докер-хостов и вывод из строя одного или нескольких в целом не должен влиять на работу тренажёров. Объясню принцип на примере:
У нас есть 3 отдельных докер-хоста [h1, h2, h3] и у нас есть очередь заданий [t1, t2, t3, t4, t5, ..., tn].
Распределяем задания:
t1->h1
t2->h2
t3->h3
t4->h1 Допустим, это задание убило хост.
t5->h2
t6->h3
t7->h1 Получаем ответ, что хост недоступен.
t7->h2
и т. д.
Упавший h1 будет перезагружен или очищен от лишних процессов, либо произойдёт освобождение дискового хранилища. Принимаемые меры зависят от ситуации. Всё это в автоматическом режиме. Как только хост будет в рабочем состоянии, он опять будет автоматически доступен для запуска контейнеров.
Тренажёры будут работать даже при одном работающем докер-хосте, но с большими временными задержками. Также этот подход позволяет нам использовать «прерываемые виртуальные машины», что хорошо экономит наши затраты.
Что отличает наш сервис от swarm. Если в кластере Swarm хотя бы одна нода начнёт тормозить, это скажется на всём кластере. В своей разработке мы постарались сделать хосты максимально независимыми.
Что отличает нашу разработку от k8s. Kubernetes в первую очередь должен обеспечивать постоянную работу запущенного сервиса. У нас каждое запущенное задание конечно и ограничено по времени. Также каждый запущенный контейнер может быть запущен с разными параметрами. Например: нужно выполнить код, написанный студентом, или нужно выполнить тесты.
Как мы интегрировали тренажёр в платформу
Ок, мы сделали систему, которая умеет запускать код и проверять результат. Далее встал вопрос, как это теперь всё заставить работать в рамках основной платформы.
На тот момент, когда был готов прототип для запуска кода, он представлял собой веб-сервис с rest api, написанный на python, который умел:
запускать код в контейнере;
получать результат;
проверять правильный ответ;
вести журнал выполнения кода.
Пришла пора как-то это интегрировать в монолит, который написан на ruby. Ситуация, мягко скажем, сложная — новые сотрудники-питонисты не были сильно погружены в предметную область, не были знакомы с внутренним устройством платформы-монолита, которая написана на ruby.
Александр Мищенко
Тимлид разработки отдела «Контент и методология»
Единственный способ запустить в срок MVP, что мы к своей гордости сделали, — шеринг ресурсов. Когда сотрудник, работающий на другом проекте, на время приходит поработать над чужими задачами. Однако сразу скажу, у этого подхода есть и недостатки, его стоит избегать — это источник технического долга, костылей и сломанных велосипедов.
Дальше по классике «делай, что должно, и будь что будет». Благодаря тому, что при проектировании тренажёра заложили микросервисную архитектуру — это значит, что внутренняя реализация сервиса не влияет на другой сервис — взаимодействие с монолитом прошло хорошо. Некоторое количество кода пришлось переписать, но в основном это было связано с особенностями работы основной платформы.
Что есть в тренажёре, что он умеет
В создании тренажёра мы шли от простого к сложному: от поддержки отдельных языков программирования — Python, JS, — к серверу БД и веб-разработки; от проверки результата сравнением с эталоном до Unit- и End-to-End тестирования.
На данный момент тренажёр — это совокупность нескольких подсистем. Все они активно развиваются и в разной степени готовности уже присутствуют в продакшене.
Балансировщик. Основная задача — распределение запуска контейнеров между независимыми docker-хостами. Такой подход позволяет нам запускать код без долгих ожиданий. Если возникнет проблема с одним из хостов на время его перезапуска, нагрузка будет распределена между рабочими хостами. Как только «упавший» хост будет доступен, он автоматически будет добавлен в рабочий стек.
Сборщик мусора. Отслеживает «зависшие» контейнеры и уничтожает их, следит за свободным местом и прочим «мусором», который возникает в результате работы docker-контейнеров. Работает внутри каждого docker-хоста.
Проверка решений. Проверяем и принимаем решение, выполнено задание или есть ошибки. Подходы зависят от задания. В одном случае достаточно убедиться, что программа выводит «Hello world!». В другом — проверить результат SQL-запроса. В третьем — анализ результатов unit-тестов. Подходы зависят как от языка программирования, так и от специфики предметной области.
Важно, что мы имеем дело с людьми, которые только учатся. С одной стороны, мы стараемся подготовить их к реальным рабочим условиям, таким как умение читать консольный вывод компилятора, интерпретатора или тестового фреймворка. С другой — избавить от лишней служебной информации. Дать им более удобную подсказку.
Подсказки. Студенты не остаются один на один со своей проблемой. В тренажёре есть функция подсказки. Если не получается справиться самостоятельно, можно взять помощь эксперта.
Взаимодействие с преподавателем. Подсистема внутри монолитной платформы. Она позволяет попросить помощи у преподавателя, если решить задание не получается, а подсказки непонятны.
Нам было важно, чтобы в тренажёре присутствовала обратная связь от эксперта. Для этого мы сделали раздел с вопросами. В нём отвечают преподаватель или аспирант. Здесь же можно посмотреть, какие вопросы уже задавали по этому заданию.
Управление тренажёрами. Админка, которая позволяет настраивать тренажёр через пользовательский интерфейс.
Тренажёры в личном кабинете. Студент использует тренажёр посредством только одного браузера. Установка дополнительного ПО не требуется, независимо от языка программирования и курса. Вся магия происходит под капотом облачных технологий.
Ещё мы добавили проверку тестами, многофайловость. Добавили тренажёр в правила завершения наших курсов, чтобы можно было делать его обязательным или дополнительным на усмотрение команды курса.
Новые вызовы для команды разработки
Соотношение количества студентов, которые успешно справлялись с тренажёром, к тем, кто к нему приступил, выросло с 82% до 88%. Тренажёр уже воспринимается как часть практики, как должное — это хорошо. В ближайшем будущем займёмся анализом CSI (Customer Satisfaction Index — показатель удовлетворённости). На основе обратной связи поймём, что нравится студентам, а что нет, и что надо улучшить в работе тренажёра.
Важно понимать, что тренажёр не может заменить преподавателей полностью. Это не было нашей целью. Именно поэтому мы вводили его постепенно, чтобы ни у кого из экспертов не возникло ощущение, что их хотят заменить. Мы не планируем переводить всю практику платформы в тренажёр.
Одна из любопытных идей от наших экспертов — разработать сценарный тренажёр. В нём 4-5 независимых шага являются этапами решения практического кейса. Возможно, даже с нелинейной логикой. Это даст возможность не просто осваивать инструменты, но и познакомиться с этапами решения практических задач. А на разборе домашних заданий преподаватель уже может ответить на те самые творческие вопросы: почему именно такая последовательность шагов, можно ли было применять другие методы или другой порядок анализа, какие есть варианты интерпретации результатов и так далее. Проверка множества заданий превратилась бы в живую беседу со студентами.
Сейчас тренажёр работает в 54 программах, это около 25 модулей. Далее планируем развивать тренажёр в нескольких направлениях — интернет-маркетинг, дизайн, бизнес и управление. Здесь тоже часть практики можно автоматизировать. В то же время направление анализа данных — творческое и плохо поддаётся автоматизации. Это новые вызовы для команды разработки.
Наша цель — добавить максимальное количество языков программирования. Но мы решили развивать тренажёр постепенно и аккуратно, опираясь на пользовательский опыт и обратную связь. В лучших традициях agile — малыми ресурсами, но качественно — даём нашим пользователям именно тот продукт, который им нужен. Это рабочее решение, которое помогло нам быстро зарелизить MVP и получить положительный образовательный результат. А дальше будем улучшать архитектуру по потребностям.