Как мы в Питерской Вышке учим Software Engineering
В предыдущих постах мы рассказывали, что наши студенты делают на стажировках: научных (например, в JetBrains Research) и промышленных. В этом посте хотим поделиться, как мы учим промышленному программированию.
Кратко: за четыре курса бывший школьник пробует десяток-другой технологий и языков, постоянно пишет и удаляет много кода, проходит code review от более опытных товарищей (не всегда с первой попытки), углубляется в какую-то тему и в итоге защищает содержательный диплом. Всё это проходит прямо в университете и даёт диплом гособразца. А летом можно либо отдохнуть, либо постажироваться в России в JetBrains, Яндексе и JetBrains Research (если хочется больше науки) или съездить за рубеж (Google, Facebook и другие). Теперь поподробнее.
О себе
Меня зовут Егор Суворов, я учусь в магистратуре ВШЭ, стажировался в Google (дважды), Asana, GSA Capital, успешно участвовал в международных олимпиадах по программированию (студенческой и школьной). В прошлом году закончил бакалавриат Академического Университета, так что сам прошёл через почти всё описанное в посте. Также участвую в разработке программы обучения software engineering и веду практические занятия по нескольким предметам (Парадигмы и языки программирования, C++).
Основные составляющие
В этом посте мы рассмотрим только поднаправление «Промышленное программирование». Хотя ещё у нас есть «Машинное обучение», «Языки программирования» и некоторые другие, программы у направлений пересекаются, особенно на первых двух курсах.
Обучение состоит из трёх основных частей. Сначала я дам общий обзор, а дальше расскажу подробнее о каждой части.
- Базовые предметы. С самого первого семестра ребята учатся работать руками. Каждые полгода все «промышленные программисты» обязательно сдают 2–4 базовых предмета (а некоторые предметы обязательны и для остальных поднаправлений). Цель: прокачать студента в более-менее всех областях программирования, чтобы можно было ходить по уровням абстракций вверх и вниз. Начиная от написания своей игрушечной ОС с user-space на Си с щепоткой ассемблера (для самых стойких) и командной строки, далее через move-семантику в C++ и вплоть до трансформеров монад. Это нужно, чтобы сформировать кругозор и опыт разного программирования. Учим не только языкам: параллельное программирование, сети, базы данных тоже есть. И, конечно, немного предметов не о коде, но всё равно по делу: например, Software Engineering (обзорный курс: зачем нужны команды/менеджеры/управление проектами и рисками) и проектирование интерфейсов (а то так и будут считать, что «формочки клепать — это просто»). Математика и алгоритмы тоже есть, но это тема для отдельного поста.
- Семестровые практики. Их нужно проходить обязательно, начиная со второго семестра. Цель: дать студенту попробовать позаниматься разными вещами покрупнее домашек и понять, что больше нравится. В начале семестра проходит ярмарка проектов, где потенциальные научные руководители рассказывают о том, чем с ними можно заниматься. Учёная степень для руководителя, кстати, необязательна — гораздо важнее то, что могут дать студенту конкретный человек и практика. В одном семестре можно сделать десктопное+мобильное приложение на Qt и понять, как вообще работать над одним проектом весь семестр без чёткого ТЗ (с трудом). В следующем — попробовать Android и проникнуться тем, что «сделать надёжный клиент для соцсети» — это, оказывается, непросто, даже если сильно ограничить функциональность. В другом — попробовать сделать какой-нибудь инструмент машинного обучения на Python и осознать, что этой темой заниматься не хочется вообще. В четвёртом — пойти допиливать компилятор Haskell, ужаснуться и к диплому вернуться обратно к милому сердцу C++. Или наоборот. Зависит от студента — в этом и смысл практик. В результате у студента либо образуется любимое направление (в котором можно работать и делать диплом), либо опыт работы в куче разных направлений. Win-win в любом случае. Кстати, если ни один из проектов не нравится, можно придумать свой. Но в этом случае надо сначала заинтересовать какого-нибудь научного руководителя или найти кого-то со стороны, а потом убедить нас, что из проекта может получиться что-нибудь содержательное и защищаемое.
- Предметы по выбору. Появляются на третьем курсе. Причина: не всем хочется копаться в ядре Linux, равно как и не всем хочется вникать в дизайн Scala Collections с тоннами implicit«ов. А так можно выбрать, в какие темы углубляться. Например, если студенту не понравилось количество способов прострелить колено в плюсах, он может взять курс по контейнерной виртуализации в Linux, писать на чистом Си и быть счастливым. И наоборот: если «Intel 64 and IA-32 architectures software developer«s manual volume 3» до сих пор снится в кошмарах, можно уйти в прекрасный мир Scala с абстракциями единорогов. В каждом модуле (половине семестра) предлагается примерно 4 курса, из которых надо выбрать два.
Кроме этого мы хотим, чтобы студентам нравилось учиться. В любой момент можно пообщаться с руководителями программы и предложить улучшения в любой области. Мы четыре раза в год собираем обратную связь и — внимание — учитываем её и постоянно улучшаем программу. Мы специально не набираем много студентов, чтобы была возможность лично поговорить с каждым. На втором и третьем курсе сейчас по 30 студентов, на четвёртом — 15.
Также мы постоянно создаём или ищем новые курсы по запросам студентов, ищем хороших преподавателей, которые понимают предмет и умеют преподавать. Так, в этом модуле в программе буквально за несколько недель удачно появился экспериментальный курс по реверс-инжинирингу от SPb CTF. А если подходящий курс найти не получается, можно по договорённости пройти что-нибудь содержательное с Computer Science Center, ШАД или Coursera.
Базовые предметы
Основные программистские предметы: C++, Unix-подобные системы, парадигмы и языки программирования, архитектура ЭВМ, Java, операционные системы, функциональное программирование, базы данных, Software Design, Software Engineering, параллельное программирование, технологии компьютерных сетей, проектирование интерфейсов, мобильная разработка.
Вместе эти предметы покрывают практически все задачи, которые могут встретиться в работе. Также они уберегают от разных «классических» ошибок вроде сравнения чисел с плавающей запятой при помощи ==, ожиданий адекватности от undefined behaviour, состояния гонки, рассказывают про существование шаблонов проектирования и не-программистских задач при разработке продуктов.
Разумеется, в процессе обучения студенты постоянно «набивают руку» на лабораторных и домашних заданиях. Возьмём что-нибудь классическое, например, реализацию архиватора на алгоритме Хаффмана. Сделать «чтобы как-то заработало» не так сложно. А вот сделать хорошую архитектуру проекта (хотя бы разнести ввод-вывод, битовое сжатие и сам алгоритм), правильно использовать возможности C++ (правило трёх или пяти в зависимости от полугодия) и вообще оформить код так, чтобы его было приятно читать и не совсем стыдно выложить в open-source — отдельное искусство, которому преподаватели учат на курсе по C++, постоянно общаясь со студентами и детально разбирая все строчки кода. На остальных предметах аналогично. Ни один курс не ограничивается теоретическими тестами. На всех предметах с написанием кода будет code review от опытного программиста.
C++. Первый год обучения. Начинаем с Си, заканчиваем C++14. Показываем RAII, Valgrind и автотесты, учим писать как библиотеки (my_vector с гарантией исключений), так и приложения (тот самый архиватор). Зачем: потому что C++ всё ещё активно используется в промышленности, плюс активно перекликается с системным программированием (отсутствие сборки мусора, можно показать раскладку данных в памяти…).
Unix-подобные системы. Первый семестр. Курс молодого бойца по работе с командной строкой и файловой системой без C:\
и D:\
. Пример контрольной работы: на паре раздаётся образ немного сломанной установленной Ubuntu, надо починить. Пример домашки: пробежаться по файлам в папке на Bash и призвать кого-нибудь полезного при помощи регулярных выражений.
Парадигмы и языки программирования. Первый семестр. Десяток тем (как минимум ООП, функциональное программирование, SQL, многопоточность), и каждую тему студенты могут попробовать на одном-двух домашних заданиях. Разумеется, довольно поверхностно, но это всё равно даёт понимание, насколько многогранно программирование и какие классные штуки можно собирать разными методами. И ООП, и ФП, и SQL будут потом подробно рассказаны, но уже на первом курсе студент знает об их существовании и может, скажем, расставить в коде assert«ы или написать парочку простых юнит-тестов, если захочет.
Архитектура ЭВМ. Первый год. Регистры, сборка чего-то компьютероподобного из логических гейтов, кэши, конвейер процессора, представления чисел и прочая теория. Зачем: связать в единую картину кусочки информации о всяких низкоуровневых вещах.
Java. Второй год обучения. Заканчивается стримами и курсовым проектом под Android (например, игра в рекурсивные крестики-нолики с ботом и сетевым режимом). Показываем Maven, IDEA, JUnit, асинхронную работу с сетью. Зачем: на JVM сейчас много чего крутится, есть тонны библиотек, полезно знать.
Операционные системы. Второй год. Хардкор. Никакой скучной возни с загрузчиками — в заготовке студентам даётся multiboot, переход в защищённый режим, а вот дальше уже можно на Си написать аллокатор памяти, потоки, процессы, файловую систему, разделение по кольцам защиты и даже загрузку ELF. Возможно, не так обширно, как «Современные операционные системы» Танненбаума, зато можно наглядно понять, интересно ли вообще в этом копаться или хочется остаться в изолированном userspace. Если интересно — добро пожаловать на специальный курс по программированию в ядре Linux. Кстати, сильно облегченная версия доступна на Stepik — там нет именно написания ОС, но есть нужная теория и проверочные задания. Чтобы не вылететь из-за неуспеваемости, достаточно пройти его.
Функциональное программирование. Два занятия на лямбда-исчисление и потом поехали в Haskell. Заканчиваем трансформерами монад. Разумеется, всё подробно: монада — это не коробочка, а просто полезная абстракция вот такого вот шаблона написания кода. Промежуточные проекты, правда, больше теоретические, чем практические — написать автоматический вывод типов в лямбда-исчислении. Но сейчас готовится продолжение курса (как дополнительный предмет магистратуры), где планируется и многопоточность, и веб-сервер. Также читается в Computer Science Center.
Базы данных. Знакомимся с реляционными СУБД (на примере PostgreSQL), SQL. Проектируем БД для некоторой предметной области, а потом устраиваем друг другу code review по руководству от преподавателя. Потом преподаватель делает code review review. Контесты вида «напишите для данной БД такой-то запрос». Задания на простое профилирование запроса (EXPLAIN). Опять же, перекликается с курсом в Computer Science Center.
Software Engineering. Рассказывает о том, как может быть устроена работа программиста, чем занимаются в компании все остальные, почему хорошие менеджеры всё-таки полезны и почему управлять проектами тоже сложно. В чём смысл планирования, почему оно не всегда работает, почему не все баги надо фиксить… Цель — чтобы было понимание, что нужно для проектов, кроме людей. Разумеется, всё подробно раскрыть за один семестр нереально, но, например, полезно знать, что после разработки проекта ещё бывает не менее важная поддержка.
Software Design. Всяческие способы моделирования реальности и (UML-диаграммы), способы декомпозиции, шаблоны проектирования. В качестве примеров в конце курса разбираются GFS, BigTable, CMake… На практике учимся не просто писать код, а ещё и описывать архитектуру и применять шаблоны, где они уместны.
Параллельное программирование. Начинаем с простых потоков и мьютексов, в конце разбираем и пишем lock-free/wait-free алгоритмы, проникаемся MESI, изучаем более высокоуровневые технологии вроде fork-join framework, OpenMP, OpenCL, Intel TBB.
Технологии компьютерных сетей. Лекции: подробный обзор основных протоколов из стэка TCP/IP: сообщения в ICMP, исторический экскурс в RIP, всякие записи в DNS, как работают FTP/HTTP/SMTP/DHCP, что такое NAT, и даже немного про IPv6. Практики: пишем на плюсах свой кроссплатформенный TCP-клиент и сервер сначала для игрушечного мессенджера, а потом UDP-клиент для DNS.
Проектирование интерфейсов. Студент не пишет ни строчки кода, зато проходит через все стадии проектирования хорошего пользовательского опыта: придумывает проект, проводит исследование (в том числе опрос реальных людей), разрабатывает и проверяет сценарии использования, а уже в самом конце можно и интерфейс в Sketch или Figma нарисовать. Цель — чтобы было понимание, что для хорошего продукта нужен не только код, а ещё куча другой подготовительной работы. Code review тут нет, зато все промежуточные артефакты с преподавателем активно обсуждаются. Сдать домашку с первой попытки, мне кажется, нереально (впрочем, и не требуется).
Мобильная разработка. Углублённый курс по разработке под Android. Пишем уже скорее на Kotlin, чем на Java, используем всякие Kotlin-специфичные штуки для Android. По сравнению с Java-проектом со второго курса, здесь приложение сложнее, больше работаем с внешними зависимостями и библиотеками, больше думаем над интерфейсом и пользователями (здесь курс перекликается с проектированием интерфейсов).
Тестирование ПО. В основном много теории, которая даёт имена всяким стандартным практикам, которые студенты уже наверняка изобрели на других предметах: тестирование потока управления или потока данных, попарное тестирование (all-pairs testing)… Немного специфичной практики тоже есть — составить тестовый план по такой-то методике, найти крайние случаи в таком-то приложении, да несколько сценариев в веб-приложении при помощи Selenium прогнать, чтобы не было скучно просто кейсы составлять.
Программная инженерия больших данных, она же Big Data Software Engineering. Читалась вместе с Computer Science Center. Соединяем базы данных, параллельное программирование, распределённые системы и прочие модные слова — это на лекциях. На практиках в прошлом году студенты писали свою распределённую телефонную книжку с нуля. В следующих запусках видится правильным сместить акцент с низкого уровня на реально используемые в индустрии инструменты вроде Zookeeper, Cassandra и прочих страшных зверей. Пока что основная сложность — как проэмулировать условия «больших данных» для студентов и оценить их решения: незачем поднимать Zookeeper, если нет наглядной демонстрации, что без него всё очень плохо.
Практики
Вторая важная часть обучения — практики. С первого же курса студент делает какие-нибудь практически полезные задачи под руководством опытного коллеги. Например, очередное приложение для управления календарём или заметками. Или новая функциональность в существующем приложении. Или изучает сложность вычислимости какого-нибудь семейства формул, если его занесло в направление Computer Science.
На первых курсах мы не требуем новизны или практичности (всё-таки цель — дать поиграться), однако к диплому требования к качеству проектов и защиты повышаются. На последних курсах помимо вопроса «что сделано?» студентам важно рассказывать, зачем сделано и почему именно так. При этом «так хочется конкретно этой компании, в которой работает мой научный руководитель» само по себе ответом не является. А вот «там жёсткие диски умирают каждую секунду, поэтому вот такой open-source не подходит, вот эта статья чисто теоретическая, а вот у Google решение есть, но закрытое» — вполне. Защитить ненужное упражнение со второго курса в качестве диплома не выйдет — на защите (и некоторых предзащитах) сидят любопытные разработчики с ноутбуками и гуглом наготове. «Такого ещё никто не делал» — практически самое опасное, что можно произнести. Кстати, защищаем не только дипломы, но и практики, регулярно с первого курса.
Вот несколько фотографий с типичной защиты. Фотограф: Дима Дроздов.
Практики позволяют научиться работать «в долгую» с большими проектами, иногда частично написанными другими разработчиками. Не всегда можно угадать с темой проекта: скажем, попробовав низкоуровневую разработку студент может заречься заниматься ей в будущем. В этом и смысл практик: понять, что нравится, а что нет, не на работе, а в условиях со ставками поменьше. Хотя последняя практика должна перерасти в содержательный бакалаврский диплом. «Содержательный» — это когда по диплому можно как минимум написать статью на Хабр и не уйти в минуса. Или, если работа совсем хороша, опубликоваться в научном журнале, выступить на конференции или хотя бы собрать плюсов.
Предметы по выбору
Третья, но тоже важная часть — дополнительные предметы. Темы специфичные, всем подряд скорее не нужны, но заинтересованные студенты могут попробовать их на вкус. На старших курсах таких предметов большинство: база есть, остаётся расширять кругозор в интересном студенту направлении. Взять все предметы, к сожалению, физически не хватает времени. Иногда набор курсов меняется, вот те, которые предлагались мне:
Альтернативные языки для JVM. Курс из двух модулей: в одном рассказывают про Kotlin, в другом — про Scala. Для Kotlin разбираем и Java interop, и написание своих DSL, и корутины. Последняя опциональная домашка — добавить в интерпретатор игрушечного языка (написанный в предыдущих домашках) отладчик при помощи корутин. Что же касается Scala… Язык большой, но implicit«ы всех видов разобрать успеваем :)
Программирование в ядре Linux. Шаг за шагом разрабатывается модуль ядра, который эмулирует виртуальное устройство хранения данных: mmap, буферы, конкурентный доступ, неблокирующий ввод-вывод. По дороге можно вспомнить прерывания и вытесняющую многозадачность из курса операционных систем и изучить внутренние структуры Linux (например, wait queue).
Компиляторы. Пишем свой компилятор микроязыка на OCaml. Промежуточная стековая машина, компиляция в x86 без всякого LLVM, интеграция с libc. Удивлённые возгласы студентов «почему у меня падает только на выражении длины сто?» (вероятно, потому что баг в аллокации регистров). Кстати, похожий курс тоже есть в Computer Science Center.
Компьютерная графика. Относительно низкоуровневый курс: изучаем OpenGL, пишем свои шейдеры для теней и отложенного рендеринга, сравниваем смешивание цветов с гамма-коррекцией и без неё.
Построение СУБД. Внутреннее устройство баз данных. Всякие алгоритмы соединений, формальные модели, колоночные СУБД. На практике можно реализовать несколько алгоритмов поблочной обработки в игрушечной СУБД на плюсах (например, doublу-pipelined hash join).
Контейнерная виртуализация. Детальное изучение контейнеров в Linux. Namespaces и cgroups, скажем — и API, и как устроено. Всякие вспомогательные инструменты для сети. В процессе пишем свой контейнер вроде Docker, а это не так просто — надо корректно ограничить кучу всего, настроить сеть, пробросить в контейнер нужные файлы… Впрочем, высокоуровневая оркестрация на примере Kubernetes тоже рассматривается.
Что хотим улучшить
И мы, и наши студенты получившейся программой скорее довольны (судя по опросам). Однако можно сделать ещё лучше, причём не только улучшив существующие предметы, но и добавив новые.
Например, до сих пор неясно, как переносить отдельные аспекты «опыта работы» в университет. Та же работа с Legacy-кодом — полезно? А то. Даже книжки и конкретные методики есть. Но чтобы из этого сделать хороший курс, надо совместить несколько факторов:
- Не отвлекать преподавателей от их основной работы надолго, чтобы они постоянно помогали студентам разобраться в большом проекте. А если есть хорошая документация — то это уже и не такое чтобы Legacy.
- Студентам должно быть интересно. «Допишите в никому не нужный проект тысячу строк кода» сюда не входит.
- Результат должен быть предсказуем. «Кажется, это неразрешимая задача, извините, не подумали» — плохая новость по результатам проверки домашки.
К сожалению, мы пока не придумали, как это сделать. Самое близкое есть в направлении машинного обучения, где каждую неделю проходят семинары, на которых студенты делают доклады по какой-нибудь из свежих статей. Возможно, этот опыт можно перенести и на промышленное программирование.
Пожалуй, единственные не покрытые сейчас направления — веб-разработка (включая фронтэнд), сложные системы автоматизации (вроде 1С или SAP) и компьютерная безопасность (в начале февраля 2019 стартовал экспериментальный курс). Возможно, мы забыли что-то ещё или вы знаете, как можно учить программировать ещё лучше — будем рады подискутировать в комментариях.
Тем не менее, мы считаем, что из бакалавриата уже сейчас выходят готовые к работе выпускники, которых если и надо чему-то специально учить, то разве что внутренним системам компании. Кстати, отдельная тема, над которой мы сейчас думаем и пробуем реализовать — чему после такого плотного набора курсов учить в магистратуре, но это тема для отдельного поста.