Товарищи инженеры, докладываю вам об успехах в подготовке научно-технических кадров в области программной инженерии на Факультете ИВТ в Киевском политехническом институте и публикую интересные примеры кода, которые были написаны для учебного курса, но будут, надеюсь, интересны и с практической точки зрения. Идея, внедрить JavaScript и Node.js в учебный процесс, вызревала у меня уже несколько лет. Но для освоения базовых вещей в программировании мне больше нравится C, чтобы люди прочувствовали машину, научились контролировать себя и свой код. А вот для прикладных задач, в которых уровень абстракции C уже не достаточно иллюстративен, мультипарадигменный и гибкий JavaScript прижился. При помощи мощного и простого API Node.js можно писать концептуальный код прямо на паре. Кроме того, знания JavaScript обязательно пригодятся на практике любому инженеру, работающему в ИТ. Часть кода, разработанного студентами курса, уже попала в серьезные Open Source проекты и это прекрасная практика, которую может повторить каждый, ведь лабораторные работы мы постепенно выкладываем на github и будем делать это и дальше, снабжая их методическими указаниями и не заботясь о том, что студенты будут списывать из форков, ведь все это нужно в первую очередь им самим. Эти материалы были использованы при подготовке порядка 300 студентов политехнического ВУЗа за 2015-2016 учебный год. Примеры я еще раз разложу по полочкам на летней школе, которая проходит с 9 по 26 августа 2016 года в Киеве, и расписание которой можно найти тут. Итак, переходим к самым показательным примерам кода. Написание нечто на подобие живых таблиц Google Spreadsheets из гуглдоксов, может вдохновить даже двоечника. Пусть это будет маленькая электронная таблица 6x5, но в нее может зайти несколько человек и введенные ими данные будут синхронизироваться по сети в реальном времени. Это наглядно, эффектно и применимо на практике. Кроме того, в этом задании скрыты дополнительные темы, применение WebSocket и EventEmitter, написать его простую реализацию самостоятельно, расширить возможности EventEmitter для подписки на все события. Основная задача этой работы, рассчитанной на 1 лекцию и 2 практики, это освоить событийную реактивную модель вычислений, вместо прохода по циклу и трансляцию событий по сети. Начинать можно уже не с нуля, базовый код сервера 41 строка, и 53 строки клиент написан и выложен вместе с заданием в гитхабе: https://github.com/HowProgrammingWorks/EventDrivenProgramming Как увлечь студентов распределенными вычислениями? Я думаю, что им просто нужно дать что-то простое, что они смогут реализовать за одну-две пары. Ни кто, конечно, в здравом уме не будет писать больших вычислительных задач на Node.js, но он очень краток и показателен в том смысле, что JavaScript имеет однопоточную модель исполнения. Мы можем наглядно распараллелить вычисления, переведя их в асинхронную парадигму и распределив между несколькими процессами. Для этого предлагается перейти от циклов к итераторам. В этом случае исчезают переменные циклов и состояние, т.е. исходный набор данных уже можно резать и передавать в разные процессы. Это подходит для задач, в которых последовательность обработки элементов набора данных не важна, а таких задач очень много. Кроме того, итераторы можно переопределить, перехватить или написать свои. Мы можем реализовать межпроцессовое взаимодействие и сетевой обмен, скрыв это за абстракцией итератора. Прикладной код не изменится, только реализация итератора будет резать задачу на части, хранить у себя индексы частей, а потом, при получении результатов (пусть даже в другом порядке), их можно будет склеить в нужной последовательности. Начинать тоже можно не с нуля, есть заготовки кода на гитхабе https://github.com/HowProgrammingWorks/InterProcessCommunication содержащие два варианта обмена между процессами: через IPC (встроенный в операционную систему и имеющий обертку в Node.js API) и через TCP сокеты. Последний можно применять не только для распараллеливания в пределах одного сервера, но и для построения кластера из нескольких многоядерных серверов. Эту задачу можно развивать сколь угодно долго, если студенту мало и у него есть способности и желание копнуть глубже, то он может написать менеджер ресурсов для вычислительного кластера, в котором будет держать состояние каждого вычислительного процесса и распределять вычислительные задания на свободные ресурсы или вообще, пропорционально их производительности. Можно собирать статистику о производительности для разных задач и оптимизировать балансировку нагрузки. Можно реализовать вычисления на сервере, а запросы слать с многих клиентов, а можно наоборот, иметь много воркеров и слать им запросы из одного мастера. Можно даже сделать брокер запросов, тогда у нас выходит один TCP сервер, который имеет два типа клиентов: заказчик и исполнитель. Заказчики шлют задания, а брокер (сервер) распределяет их на исполнителей, потом собирает и них части решения и отправляет заказчикам объединенные решения. Еще одна важная тема, это экранирование прикладного кода в песочницах. В этом смысле Node.js дает нам доступ к API виртуальной машины V8 и это позволяет создавать контексты исполнения кода динамически. Песочницы имеют свой глобальный контекст и совершенно не имеют доступа к главному глобальному контексту приложения, если мы специально не пробросим в них ссылки на те или иные объекты. Благодаря этому мы можем продемонстрировать принципы инверсии управления и внедрения зависимостей для модулей, а не только для классов. Мы можем взять ссылку на объект или функцию из одной песочницы, и внедрить ее в другую песочницу. Можем обернуть любой API в песочнице, добавив в его поведение логирование, замеры скорости, распределенные вычисления, функции безопасности и что угодно. Песочницы, это одна из самых мощных возможностей ноды, но, к сожалению, пока мало используемая в прикладных приложениях. Вообще говоря, управление зависимостями в ноде из коробки такое себе, все делается через DL (dependency lookup), реализацией которого и является пресловутый require. Это способ, при котором модули сами подгружают в свой контекст другие модули, указывая полный путь к файлу или имя модуля в репозитории npm. При этом, подгружаемые модули получают полный доступ к глобальному контексту приложения и могут изменить в нем что угодно, например, удалит setTimeout, переопределить метод Array.prototype.forEach() или заменить require своей функцией. Вообще, библиотеки JavaScript часто модифицируют базовые классы языка и это приводит к конфликтам кода. От этого нет спасения, кроме как запускать конфликтующий код в песочницах и реализовать экранирование, а зависимости внедрять из основного приложения, создавая ссылки на них в глобальном контексте песочниц. Лучше всего перейти к внешнему декларативному описанию зависимостей, на подобие того, что мы имеем в package.json. Но require в ноде дублирует в императивном стиле, то декларативное описание, которое содержится в package.json. В больших проектах часто случается, что два разных компонента приложения зависят от разных версий одной библиотеки (это ужасно конечно, но как есть), а в package.json можно описать только одну. Оформлять все эти компоненты как отдельные npm пакеты и подымать свой npm сервер или выкладывать их в приватный репозиторий, можно конечно. Но значительно красивее решить сразу две задачи при помощи песочниц и внедрения зависимостей. Главный модуль может считать файл описания зависимостей и подгрузить нужные версии зависимостей в нужные песочницы, одновременно защищая их контексты. Ссылка на репозиторий https://github.com/HowProgrammingWorks/InterProcessCommunication и в нем есть 3 папки: В следующий раз я покажу еще больше примеров кода из наших лабораторных работ, например: веб-чат через вебсокеты, практическое применение метапрограммирования, расширяемый HTTP сервер для ветхого веба, асинхронная композиция функций, повышение уровня абстракции кода, разработка специализированных протоколов, сериализаторов и серверов на базе TCP и UDP, синхронизация каталогов по сети, построение DSL языков, например, языка запросов к структурам данных в оперативной памяти и т.д. Спасибо за Ваше внимание. Chief Technology Architect at Metarhia