Переключение контекста (англ. context switch) — в многозадачных ОС и средах — процесс прекращения выполнения процессором одной задачи (процесса, потока, нити) с сохранением всей необходимой информации и состояния, необходимых для последующего продолжения с прерванного места, и восстановления и загрузки состояния задачи, к выполнению которой переходит процессор.
В процедуру переключения контекста входит т. н. планирование задачи — процесс принятия решения, какой задаче передать управление.
Содержание
- Описание
- Переключение контекста и производительность
- Переключение контекста и ОС
- Особенности процедуры планировщика
- Реализации переключения контекста в современных ОС
- Замечания о терминологии
- Ссылки
- Алексей Малеев, проректор МФТИ по международным программам и цифровым инновациям и основатель Moscow Workshops
- Для всех
- Для студентов
- Для школьников
- Как готовиться к контесту: 10 советов от МФТИ
- Что такое функция
- Зачем нужны функции
- Что такое чистые функции
- Один и тот же результат
- Побочные эффекты
- Как этим пользоваться
Описание
При переключении контекста происходит сохранение и восстановление следующей информации:
- Регистровый контекст регистров общего назначения (в том числе флаговый регистр)
- Контекст состояния сопроцессора с плавающей точкой / регистров MMX (x86)
- Состояние регистров SSE, AVX (x86)
- Состояние сегментных регистров (x86)
- Состояние некоторых управляющих регистров (например, регистр CR3, отвечающий за страничное отображение памяти процесса) (x86)
В ядре ОС с каждым потоком связаны следующие структуры:
- Общая информация pid, tid, uid, gid, euid, egid и т. д.
- Состояние процесса/потока
- Права доступа
- Используемые потоком ресурсы и блокировки
- Счетчики использования ресурсов (например, таймеры использованного процессорного времени)
- Регионы памяти, выделенные процессу
Переключение контекста и производительность
Кроме того, что очень важно, при переключении контекста происходят следующие программно-незаметные аппаратные действия, влияющие на производительность:
- Происходит очистка конвейера команд и данных процессора
- Очищается TLB, отвечающий за страничное отображение линейных адресов на физические.
Кроме того, следует учесть следующие факты, влияющие на состояние системы:
- Содержимое кэша (особенно это касается кэша первого уровня), накопленное и «оптимизированное» под выполнение одного потока, оказывается совершенно неприменимым к новому потоку, на который происходит переключение.
- При переключении контекста на процесс, который до этого долгое время не использовался (см. Подкачка страниц), многие страницы могут физически отсутствовать в оперативной памяти, что порождает подгрузку вытесненных страниц из вторичной памяти.
Переключение контекста и ОС
С точки зрения прикладного уровня переключение контекста можно разделить на добровольное (voluntary) и принудительное (non-voluntary): выполняющийся процесс/поток может сам передать управление другому потоку либо ядро может насильно отобрать у него управление.
- Ядро ОС может отобрать управление у выполняющегося процесса/потока при истечении кванта времени, выделенного на выполнение. С точки зрения программиста это означает, что управление могло уйти от потока в «самый неподходящий» момент времени, когда структуры данных могут находиться в противоречивом состоянии из-за того, что их изменение не было завершено.
- Выполнение блокирующего системного вызова. Когда приложение производит ввод-вывод, ядро может решить, что можно отдать управление другому потоку/процессу в ожидании, пока запрошенный данным потоком дисковый либо сетевой ввод-вывод будет выполнен. Данный вариант является самым производительным.
- Синхронизирующие примитивы ядра. Мьютексы, Семафоры и т. д. Это и есть основной источник проблем с производительностью. Недостаточно продуманная работа с синхронизирующими примитивами может приводить к десяткам тысяч, а в особо запущенных случаях — и к сотням тысяч переключений контекста в секунду.
- Системный вызов, явно ожидающий наступления события (select, poll, epoll, pause, wait,…) либо момента времени (sleep, nanosleep,..). Данный вариант является относительно производительным, так как ядро ОС имеет информацию об ожидающих процессах.
Особенности процедуры планировщика
Разницу между операционными системами реального времени и разделения времени особенно хорошо видно в различии логики планирования при переключении контекста: Планировщик систем разделения времени старается максимизировать производительность всей системы в целом, возможно, в ущерб производительности отдельных процессов. Задача планировщика систем реального времени — обеспечить приоритетное выполнение отдельных критических процессов, причем не важно, насколько жесткими накладными расходами для всей остальной системы в целом это обойдется.
Реализации переключения контекста в современных ОС
Как видно из вышесказанного, переключение контекста является очень ресурсоёмкой операцией, причем, чем более «навороченым» является процессор, тем более ресурсоёмкой эта операция становится. Исходя из этого, ядро использует ряд стратегий, чтобы, во-первых, сократить количество переключений контекста, а во вторых, сделать переключение контекста менее ресурсоёмким.
Методы уменьшения количества переключений контекста:
- Существует возможность конфигурирования выделяемого потоку кванта процессорного времени. При сборке ядра Linux возможно указать Server/Desktop/Low-Latency Desktop. Для серверных конфигураций этот квант больше.
Методы снижения ресурсоемкости переключения контекста:
- При переключении контекста между потоками, разделяющими одно адресное пространство в пределах одного процесса, ядро не трогает регистр CR3, тем самым сохраняя TLB
- Во многих случаях ядро располагается в том же адресном пространстве, что и пользовательский процесс. При переключении контекста между user-space и kernel-space (и обратно), что, например, происходит при выполнении системных вызовов, ядро не трогает регистр CR3, тем самым сохраняя TLB
- Производя планирование, ядро старается минимизировать перемещение процесса между вычислительными ядрами в SMP-системе, тем самым улучшая эффективность работы кэша второго уровня.
- Реальное сохранение/восстановление контекста регистров сопроцессора плавающей точки и MMX/SSE контекста происходит при первом обращении нового потока, что оптимизировано под случай, когда большинство потоков производит только операции с регистрами общего назначения.
Вышеприведенные примеры относятся к ядру Linux, однако прочие операционные системы так же применяют сходные методы, хотя в случае проприетарных ОС доказать/опровергнуть использование этого является проблематичным.
Замечания о терминологии
Этот раздел не завершён. Вы поможете проекту, исправив и дополнив его. |
Ссылки
Алексей Малеев, проректор МФТИ по международным программам и цифровым инновациям и основатель Moscow Workshops
Спортивное программирование — отличная стартовая площадка для карьеры. Кто-то после побед становится наставником и готовит других чемпионов, а кто-то устраивается в крупные IT-компании. Призёры олимпиад также работают в Google, Facebook, Mail.ru Group, ВКонтакте и Яндексе. По некоторым оценкам, 5–10% общего числа сотрудников в крупных IT-компаниях — это бывшие участники крупнейших студенческих чемпионатов по программированию ICPC.
По словам участников, контесты учат работать быстро и эффективно, писать чистый код, а также тренировать иностранный язык, поскольку решать задачи и общаться с организаторами приходится на английском. В то же время прокачиваются гибкие навыки (soft skills): стрессоустойчивость, тайм-менеджмент и умение работать в команде. Ещё один плюс — знакомство с коммьюнити и возможность объективно оценить своё мастерство. Причём не обязательно доходить до финала — извлечь пользу можно ещё на стадии подготовки или во время сборов.
Каждый год в мире проходят сотни чемпионатов по спортивному программированию — от локальных турниров для новичков до масштабных фестивалей с тысячами участников со всего мира. Вместе с МФТИ и проектом Moscow Workshops разбираемся, какие соревнования существуют, зачем в них участвовать, а главное, как извлечь из контестов максимальную пользу. Заодно определим 10 универсальных советов, которые пригодятся на соревновании любого уровня.
Для всех
Google Code Jam
Что: ежегодное международное соревнование по программированию от Google.
Среднее число участников: почти 60 000.
Условия: возраст старше 13 лет, но для участия в финальном очном турнире нужно быть старше 18.
Задачи: за фиксированное количество времени нужно решить набор алгоритмических задач. При тестировании задачи участник скачивает входные данные, запускает у себя на компьютере, после чего отправляет ответ. При этом разрешается использовать любой язык программирования и среду разработки.
Зачем участвовать: для Google соревнование — это инструмент хантинга лучших молодых специалистов. За победителями действительно охотятся работодатели, поэтому Code Jam — отличная возможность попасть на международный рынок труда ещё будучи студентом. Денежные вознаграждения от Google тоже радуют — первой тройке победителей выплачивают от $1000 до $15000.
Интересный факт: с 2014 года первое место на соревнованиях ежегодно занимает аспирант ИТМО Геннадий Короткевич. Он также получал первые награды на соревнованиях ICPC, Russian Code Cup, Facebook Hacker Cup и Topcoder Open.
Аналогичный проект от другого IT-гиганта — Facebook Hacker Cup.
Hash Code
Что: ежегодный международный контест по программированию от Google.
Среднее число участников: более 37 000 на первом этапе.
Условия: возраст старше 16 лет, но для участия в финальном очном турнире нужно быть старше 18. Для начала нужно зарегистрироваться в системе, пройти подготовку и онлайн-отбор — после этого лучших пригласят в штаб-квартиру компании на заключительный этап.
Задачи: главное отличие Hash Code от Code Jam — это отсутствие абстрактных задач. Участникам предлагают реальный челлендж, с которым столкнулись разработчики Google. Контест больше похож на хакатон, поскольку одного правильного ответа не существует — нужно изобрести самое эффективное решение.
Зачем участвовать: чтобы прожить один день из жизни разработчика Google, посетить штаб-квартиру крупнейшей IT-компании и примерно понять, что ждёт программиста, который достиг такого уровня.
Интересный факт: на квалификационном раунде в 2020 году разработчикам нужно было помочь проекту Google Books: найти самый быстрый способ отсканировать как можно больше книг в библиотеках по всему миру. Google действительно работает над этой задачей и, возможно, использует лучшее предложенное решение на практике.
Topcoder Open
Что: ежегодный профессиональный турнир по программированию.
Среднее число участников: 4 000.
Условия: возраст старше 18 лет, аккаунт на платформе Topcoder и успешное прохождение отборочных соревнований.
Задачи: турнир проводится по нескольким номинациям. В 2019 году это были Algorithm, UI Design, Development, Marathon Matches, QA и First2Finish. К спортивному программированию относятся Algorithm и Marathon Matches.
Зачем участвовать: бывшие участники турнира называют Topcoder Open «Диснейлендом возможностей». Турнир открывает доступ к сообществу лучших разработчиков со всего мира. Можно не только найти единомышленников и познакомиться с IT-легендами, но и получить предложение о работе.
Интересный факт: Китай, Польша и Россия стабильно лидируют по числу победителей в разных номинациях Topcoder One. Участники соревнования могут применять тактику взломов и челленджей, то есть можно получать баллы за нахождение ошибок в чужом коде.
VK CUP
Что: индивидуальный чемпионат по программированию, организованный
ВКонтакте совместно с платформой Codeforces и ML Bootcamp.
Среднее число участников: более 3000 команд.
Условия: возраст старше 14 лет. Но для участия в финале нужно быть старше 16.
Задачи: конкурс включает четыре трека: ML, Engine, Mobile и Design. Каждый состоит из трех последовательных раундов: квалификационного, отборочного и финального. На каждом из этапов участникам нужно решить одну или несколько задач.
Зачем участвовать: как и большинство крупных IT-компаний, VK использует чемпионат для хантинга специалистов. Но параллельно с этим ВКонтакте выполняет просветительскую миссию: помогает разбирать задания отборочных туров, публикует лекции, выкладывает библиотеки компонентов и полезную документацию.
Интересный факт: До этого в течение ряда лет VK Cup разыгрывался для команд из двух человек, причём участвовать могли только участники не старше 23 лет. Финал VK Cup 2019-2020 должен был состояться в апреле, но его перенесли на лето из-за эпидемии коронавируса. Точные даты пока не определили.
Сodeforces
Что: соцсеть и платформа для соревнований по алгоритмике, на которой регулярно проводят турниры и образовательные раунды.
Среднее число участников: по данным Codeforces, с 2013 года платформа опережает Topcoder по количеству активных пользователей. В 2018 на ней было зарегистрировано 600 тыс. аккаунтов.
Условия: ограничения устанавливают организаторы соревнований, а Codeforces лишь предоставляет им платформу.
Задачи: всегда разные. Обычно участникам предлагают решить 5 задач за два-три часа. Тоже работает тактика взломов и челленджей: можно получать баллы за нахождение ошибок в чужом коде.
Зачем участвовать: Codeforces устроена как социальная сеть для разработчиков, которые увлекаются спортивным программированием. Здесь решают нестандартные задачи, прокачивают скиллы, проходят курсы и интенсивы. За победу на крупных контестах победителям вручают призы, а за первые места на регулярных «раундах» повышают рейтинг. Работодатели часто оценивают соискателя по рейтингу на Codeforces — хороший показатель повышает шансы при устройстве на работу или стажировку.
Интересный факт: Telegram и лично Павел Дуров уже много лет поддерживают деятельность Codeforces. Кроме того, платформа сотрудничает с другими компаниями, в том числе с Microsoft, Mail.Ru, JetBrains и Huawei.
Другие онлайн-площадки для индивидуальных соревнований:
- AtCoder: здесь собраны более сложные задачи, созданные в традициях японской школы спортивного программирования. Контесты делятся на три категории: ABC предназначена для новичков, ARC — для опытных специалистов, AGC — для экспертов.
- CodeChef: некоммерческая образовательная инициатива индийской компании Directi. Ежемесячно на платформе проводятся онлайн-контесты, а раз в год площадка организует очный чемпионат SnackDown.
Rucode Festival
Что: всероссийская программа интенсивной подготовки по спортивному программированию и искусственному интеллекту, созданная МФТИ в партнёрстве с Фондом развития Физтех-школ и при поддержке Фонда президентских грантов.
Условия: онлайн-курс «Быстрый старт в спортивное программирование» доступен всем бесплатно. Он знакомит с базовыми линейными алгоритмами, теорией чисел, динамическим программированием. Дальше, до 2 апреля, можно попасть на отборочное тестирование для участия в трёхдневных интенсивах по спортивному программированию. В конце апреля проект Rucode завершится онлайн-чемпионатом для участников из России и студентов других стран.
Задачи: занятия проводятся для специалистов с базовыми знаниями С/С++, Java, Pascal/Delphi и Python.
Зачем участвовать: Rucode позволяет бесплатно изучить основы спортивного программирования и познакомиться с инструментами на базе машинного обучения. Это поможет новичку понять, в каком направлении двигаться дальше. Ещё один плюс — возможность поработать в команде и выйти на сообщество разработчиков со всей России. Кроме того, на чемпионате можно попрактиковаться на реальных датасетах ведущих IT-компаний — трек по искусственному интеллекту подготовлен совместно со Сбербанком — и познакомиться с передовыми разработками в области искусственного интеллекта.
Интересный факт: RuCode станет первой ступенью масштабного проекта «Сеть интенсивов по подготовке IT-талантов», который до 2024 года позволит в онлайне обучить спортивному программированию более 100 тысяч человек в России.
Для студентов
ICPC
Что: ежегодный студенческий командный чемпионат мира по программированию. Проводится с 1977 года.
Среднее число участников: более 52 тысяч студентов из 3 тысяч учебных заведений.
Условия: возраст не старше 24 лет, статус студента и аспиранта, сформированная команда под руководством тренера.
Задачи: чемпионат состоит из нескольких этапов: в некоторых регионах — ⅛ (квалификационный тур региона), региональный финал (четвертьфинал), суперфинал региона (полуфинал) и международный финал. На каждом этапе проводят пятичасовой контест, который состоит из 8–14 задач. Соревнование командное, команде из трёх студентов выдаётся один компьютер. Разрешено использовать языки программирования C/C++, Java, Python и Kotlin.
Зачем участвовать: ICPC считается одним из самых престижных чемпионатов по программированию. Победа на первенстве — большой плюс к резюме. Но подготовка и участие — тоже ценный опыт. Например, на сборах можно прокачать навыки работы с новыми системами, инструментами и технологиями.
Интересный факт: в июне 2020 ICPC впервые пройдет в Москве — его организует МФТИ. Ещё один интересный факт — в 2000 и 2001 году победителем ICPC стал Николай Дуров, брат Павла Дурова, он отвечал за техническую разработку VK и Telegram. Во время финала работает специальная студия ICPC Live, освещающая на нескольких языках (в том числе и на русском) ход финала.
В преддверии финала ICPC обычно проводится двухдневный чемпионат по спортивному программированию Moscode Festival, организованный Центром развития IT-образования МФТИ. На фестивале за 5 часов нужно решить 12 алгоритмических задач по стандартам ICPC. Участие в фестивале бесплатное, а обучение и соревновательная часть проходят на английском, так что это отличная возможность попрактиковать язык накануне международных соревнований.
Студентам, а также выпускникам сообщества, также можно поучаствовать в OpenCup — открытом кубке им. Е.В. Панкратьева по программированию. Соревнования проводятся по трём категориям: SCHOOL для школьников, ACM для студентов и OPEN для всех остальных. За победу в этом сезоне борются команды из медалистов ICPC прошлых лет из Польши, США и России. Сезон OpenCup состоит из нескольких этапов, которые проводятся в двух дивизионах — основном и учебном, поэтому первенство подойдёт для программистов разного уровня. Соревнование проходит по секторной схеме с более чем 200 зарегистрированных площадок, а в связи с ограничениями из-за борьбы с коронавирусом командам рекомендовано участвовать в соревнованиях, не собираясь физически в одном месте, но общаясь между собой через средства коммуникации.
Для школьников
IOI
Что: ежегодная международная олимпиада по информатике среди школьников.
Среднее число участников: более 300 школьников из 87 стран.
Условия: возраст не старше 20 лет и отсутствие высшего образования. На момент участия в конкурсе программист не должен числиться в вузе. Тренировочные сборы перед IOI проходят дважды в год, с 2018 — на базе МФТИ. В качестве подготовки школьники участвуют в международных соревнованиях, например, ездят на турниры в другие страны.
Задачи: в течение двух дней нужно решить и запрограммировать несколько алгоритмических задач, составленных опытными разработчиками. Основные языки — C++ и Java. В отличие от соревнований ICPC, возможны частичные баллы — если задача проходит некоторый набор тестов, удовлетворяющий более слабым ограничениям, участник получает частичный балл.
Зачем участвовать: участие в крупнейшей школьной олимпиаде по информатике считается бонусом при поступлении в университет. В России сборную формируют по итогам Всероссийской олимпиады по информатике и другим заслугам, призёры которой автоматически попадают в вузы без экзаменов. Более того, они могут претендовать на президентскую стипендию.
Интересный факт: обычно не более 50% участников получают медали, а золото достается лишь 1/12 части школьников. В среднем соотношение золота, серебра, бронзы и отсутствия медалей составляет примерно 1:2:3:6
ВКОШП
Что: всероссийская командная олимпиада школьников по программированию.
Среднее число участников: более 300 команд по три человека в каждой.
Условия: участвовать могут только школьники, которые предварительно прошли отборочный тур. В остальном это командное соревнование по тем же правилам, что и ICPC.
Задачи: в течение одного пятичасового компьютерного тура нужно решить несколько задач. Используются языки программирования Pascal, C++14, C#, Java, Python, D и Kotlin.
Зачем участвовать: участие в ВКОШП — это первый опыт сопоставления своих навыков с навыками других начинающих программистов. При этом даже не придётся выезжать из страны.
Интересный факт: в 2019 году отборочный тур на ВКОШП в Москве школьники писали одновременно и на тех же задачах, что и столичные студенты. Тогда как раз проходила первая ступень отбора на ICPC — соревнование Moscow Programming Contest. Всего в нём поучаствовали 2284 человека, мероприятие зафиксировали в Реестре рекордов России как самое массовое соревнование по программированию в стране.
Технокубок
Что: ежегодная олимпиада по программированию для учащихся 8–11 классов, организованная МФТИ, МГТУ им. Н.Э. Баумана и компанией Mail.ru Group.
Среднее число участников: 7 500 человек, из которых более 550 доходят до финала.
Условия: допускаются только школьники. Для попадания в очный финал нужно пройти отборочный тур онлайн. Тур проходит по правилам раундов Codeforces.
Зачем участвовать: олимпиада входит в перечень РСОШ и относится к соревнованиям первого уровня. Это позволяет победителям и призерам поступать в российские вузы без экзаменов. Участники «Технокубка» также получают привилегии при поступлении на образовательные проекты Mail.ru Group. А ещё можно выиграть iPad или AirPods.
Интересный факт: организаторы «Технокубка» креативно подходят к написанию задач. Например предлагают подсчитать разбросанные по комнате носки или помочь клонам выбраться из подземной лаборатории.
Как готовиться к контесту: 10 советов от МФТИ
- Определите, достаточно ли у вас базовых знаний для участия в соревнованиях. На раннем этапе необходимо:
- Знать хотя бы один язык программирования. Лучше всего подойдут С, С++ или Java. В некоторых случаях можно обойтись и Python, но этот язык медленнее других. Также обязателен опыт алгоритмического программирования.
- Обладать математической подготовкой: уметь решать нестандартные задачи и строить математические модели.
- Знать английский достаточно, чтобы понимать лекции и задачи.
- Организуйте индивидуальные тренировки. Даже если вы будете выступать на командных соревнованиях, уделите время самостоятельной прокачке навыков — как практических, так и теоретических.
- Изучите теорию. Почитайте справочник «Олимпиадное программирование», разберите материалы на бесплатном портале E–maxx — там собраны алгоритмы, электронные пособия и новости спортивного программирования.
- Решайте задачи — как можно больше и чаще. Ищите задания с прошлых чемпионатов и олимпиад. Их публикуют на Timus Online Judge, Codeforces, TopCoder, Spoj, CodeChef, Codingame и C Puzzles (для языка C). На русском языке доступен Сервис дистанционной подготовки к информатике.
- Для начала решайте задачи без учёта времени. Потом уже ставьте таймер. Не останавливайтесь на самых лёгких вариантах — постепенно двигайтесь от простого к более сложному.
- Организуйте командные тренировки. Это поможет наладить отношения в коллективе и определить сильные и слабые стороны каждого. Для этой цели подойдут как локальные тренировки, так и сборы по спортивному программированию. В России регулярно проводятся кэмпы для подготовки к крупным соревнованиям. География обширная: Москва, Петрозаводск, Ижевск, Иркутск, Уфа, Владивосток. Есть и международная программа Moscow Workshops, которая зародилась на кампусе Физтеха, а сейчас проводит сборы по всему миру — в Пекине, Сингапуре, Максате, Риге и других городах. В рамках проекта есть и лагерь для учеников 9–11 классов — Moscow Workshops Juniors. Он подойдет для уже прокачанных школьников. Участники каждый день тренируются, посещают лекции, но также занимаются спортом и участвуют в интеллектуальных играх, чтобы поддерживать баланс.
- Не забывайте про техническую подготовку компьютера. ICPC предоставляет устройства с установленным программным обеспечением. На региональных чемпионатах могут потребоваться свои ноутбуки — заранее уточните, какие требования к настройкам предъявляют организаторы.
- Не пренебрегайте пробным туром перед соревнованием. Это поможет протестировать систему, проверить базовую функциональность и оценить дополнительные возможности.
- Помните о гибких навыках. Вы можете идеально знать язык программирования и великолепно решать задачи, но при этом не уметь планировать время или управлять процессами. Эти навыки нужно прокачивать точно так же, как навыки работы с Java. Изучите принципы тайм-менеджмента и командообразования. Тренируйте креативность, когнитивную гибкость и критическое мышление. Учитесь вести переговоры и решать конфликты, а также грамотно распределять ресурсы.
- Не геройствуйте — отсутствие сна и отказ от еды ещё никому не помогал победить.
Хороший программист старается делать свои функции чистыми. Если знать, что это такое, можно сойти за своего, а заодно написать читаемый код.
Если вы не совсем понимаете, что такое функция и зачем она нужна — добро пожаловать в наш кат:
Что такое функции и зачем они нужны
Что такое функция
Функция — это мини-программа внутри вашей основной программы, которая делает какую-то одну понятную вещь. Вы однажды описываете, что это за вещь, а потом ссылаетесь на это описание.
Например, вы пишете игру. Каждый раз, когда игрок попадает в цель, убивает врага, делает комбо, заканчивает уровень или падает в лаву, вам нужно добавить или убавить ему очков. Это делается двумя действиями: к старым очкам добавляются новые, на экран выводится новая сумма очков. Допустим, эти действия занимают 8 строк кода.
Допустим, в игре есть 100 ситуаций, когда нужно добавить или убавить очки — для каждого типа врага, преграды, уровня и т. д. Чтобы в каждой из ста ситуаций не писать одни и те же восемь строк кода, вы упаковываете эти восемь строк в функцию. И теперь в ста местах вы пишете одну строку: например, changeScore(10) — число очков повысится на 10.
Если теперь изменить, что происходит в функции changeScore(), то изменения отразятся как бы во всех ста местах, где эта функция вызывается.
Зачем нужны функции
Функции нужны, чтобы заметно упрощать и сокращать код, адаптировать его для разных платформ, делать более отказоустойчивым, легко отлаживать. И вообще порядок в функциях — порядок в голове.
Возьмём тот же пример с подсчётом очков. Что если при добавлении очков нужно не только выводить их на экран, но и записывать в файл? Просто добавляете в определении функции дополнительные команды, связанные с файлами, и они теперь будут исполняться каждый раз, когда функцию снова вызовут в основной программе. Не нужно ползать по всему коду, искать места с добавлением очков и дописывать там про файлы. Меньше ручного труда, меньше опечаток, меньше незакрытых скобок.
А что если нужно не только писать очки в файл, но и следить за рекордом? Пишем новую функцию getHighScore(), которая достаёт откуда-то рекорд по игре, и две другие — setHighScore() и celebrateHighScore() — одна будет перезаписывать рекорд, если мы его побили, а вторая — как-то поздравлять пользователя с рекордом.
// Объявляем новую функцию. Она будет называться changeScore и принимать один аргумент, который мы для этого фрагмента назовём howMuch. Дальше мы просто будем подавать в функцию число. function changeScore(howMuch) { // Прибавим к старым очкам новые playerScore = playerScore + howMuch; // Выведем новые очки на экран $(‘#scoretext’).text(playerScore) // Узнаем, какой у нас рекорд. Для этого объявим новую переменную highScore, вызовем функцию getHighScore(), запишем её результат в эту переменную var highScore = getHighScore(); // А теперь сравним, больше ли наши очки, чем рекорд по игре if (playerScore > highScore) { //Рекорд побит, значит, надо его записать setHighScore(playerScore, playerName); // Делаем тут что-то, что обычно делают, когда ты побил рекорд игры. Фейерверки? Музыка? Мигание рекордных очков на экране? Мы не знаем пока, что именно будет делать эта функция, и нам это сейчас неважно celebrateHighScore(); } }
Теперь при каждом срабатывании changeScore() будет вызывать все остальные функции. И сколько бы раз мы ни вызвали в коде changeScore(), она потянет за собой всё хозяйство автоматически.
Сила ещё в том, что при разборе этой функции нам неважно, как реализованы getHighScore(), setHighScore() и celebrateHighScore(). Они задаются где-то в другом месте кода и в данный момент нас не волнуют. Они могут брать данные с жёсткого диска, писать их в базу данных, издавать звуки и взламывать Пентагон — это будет расписано внутри самих функций в других местах текста.
Без функций трудно повесить действия на какие-либо кнопки в интерфейсе. Например, у вас на сайте есть форма, и при клике на кнопку «Отправить» вы хотите проверять, что данные в форме правильно введены. Вы спокойно описываете где-то в коде функцию validateForm() и вешаете её на нажатие кнопки. Кнопку нажали — функция вызвалась. Не нужно вписывать в кнопку полный текст программы.
А без функции пришлось бы писать огромную программу-валидатор прямо внутри кнопки. Это исполнимо, но код выглядел бы страшно громоздким. Что если у вас на странице три формы, и каждую нужно валидировать?
Хорошо написанные функции резко повышают читаемость кода. Мы можем читать чужую программу, увидеть там функцию getExamScore(username) и знать, что последняя каким-то образом выясняет результаты экзамена по такому-то юзернейму. Как она там устроена внутри, куда обращается и что использует — вам неважно. Для нас это как бы одна простая понятная команда.
Можно написать кучу вспомогательных функций, держать их в отдельном файле и подключать к проекту как библиотеку. Например, вы написали один раз все функции для обработки физики игры и потом подключаете эти функции во все свои игры. В одной — роботы, в другой — пираты, но в обеих одна и та же физика.
Функции — это бесконечная радость. На этом наш экскурс в функции закончен, переходим к чистоте.
Что такое чистые функции
Есть понятие чистых функций. Это значит, что если функции два раза дать на обработку одно и то же значение, она всегда выдаст один и тот же результат и в программе не изменит ничего, что не относится непосредственно к этой функции. То есть у чистой функции предсказуемый результат и нет побочных эффектов.
Вот примеры.
Один и тот же результат
Допустим, мы придумали функцию, которая считает площадь круга по его радиусу: getCircleArea(). Для наших целей мы берём число пи, равное 3,1415, и вписываем в функцию:
function getCircleArea(radius) { // Задаём наше местное определение числа пи var localPi = 3.1415; // Считаем площадь: пи на радиус в квадрате. Это то же самое, что пи умножить на радиус и ещё раз умножить на радиус var circleArea = localPi * radius * radius; // Говорим функции вернуть то, что мы сейчас рассчитали return circleArea; }
Теперь этой функции надо скормить число, и она выдаст площадь круга:
- getCircleArea(2) всегда выдаст результат 12,6060
- getCircleArea(4) всегда выдаст 50,2640
Разработчик может быть уверен, что эта функция всегда выдаст нужную для его задачи площадь круга и не будет зависеть от каких-либо других вещей в его программе. Эта функция с предсказуемым результатом.
Другой пример. Мы пишем программу-таймер, которая должна издать звук, например, за 10 секунд до конца отведённого ей времени. Чтобы узнать, сколько осталось секунд, нам нужна функция: она выясняет количество секунд между двумя отметками времени. Мы даём ей два времени в каком-то формате, а функция сама неким образом высчитывает, сколько между ними секунд. Как именно она это считает, сейчас неважно. Важно, что она это делает одинаково. Это тоже функция с предсказуемым результатом:
- getInterval(’09:00:00′, ’09:00:12′) всегда выдаст 12
- getInterval(’09:00:00′, ’21:00:00′) всегда выдаст 43 200
А теперь пример похожей функции: она определяет время от текущего до какого-то другого времени. При исполнении эта функция запрашивает текущее время в компьютере, сравнивает с целевым и делает нужные вычисления. При запуске одной и той же функции с разницей в несколько секунд она даст разные результаты:
- getSecondsTo(’23:59:59′) в один момент даст 43 293 секунды,
- а спустя 2 минуты эта же функция getSecondsTo(’23:59:59′) даст 43 173 секунды.
Это функция с непредсказуемым результатом. У неё есть непредсказуемая зависимость, которая может повлиять на работу программы — зависимость от текущего времени на компьютере. Что если во время исполнения у пользователя обнулились часы? Или он сменил часовой пояс? Или при запросе текущего времени происходит ошибка? Или его компьютер не поддерживает отдачу времени?
С точки зрения чистых функций, правильнее будет сначала отдельными функциями получить все внешние зависимости, проверить их и убедиться, что они подходят для нашей работы. И потом уже вызвать функцию с подсчётом интервалов. Что-то вроде такого:
- var now = getCurrentTime();
- var interval = getInterval(now, ’23:59:59′);
Тогда в функции getCurrentTime() можно будет прописать всё хозяйство, связанное с получением нужного времени и его проверкой, а в getInterval() оставить только алгоритм, который считает разницу во времени.
Побочные эффекты
Современные языки программирования позволяют функциям работать не только внутри себя, но и влиять на окружение. Например, функция может вывести что-то на экран, записать на диск, изменить какую-то глобальную переменную. Взломать Пентагон, опять же. Всё это называется побочными эффектами. Хорошие программисты смотрят на них крайне настороженно.
Примерчики!
Мы пишем таск-менеджер. В памяти программы хранятся задачи, у каждой из которых есть приоритет: высокий, средний и низкий. Все задачи свалены в кучу в памяти, а нам надо вывести только те, что с высоким приоритетом.
Можно написать функцию, которая считывает все задачи из памяти, находит нужные и возвращает. При этом на задачи в памяти это не влияет: они как были свалены в кучу, так и остались. Это функция без побочных эффектов.
- getTasksByPriority(‘high’) — вернёт новый массив с приоритетными задачами, не изменив другие массивы. В памяти был один массив, а теперь появится ещё и второй.
А можно написать функцию, которая считывает задачи, находит нужные, стирает их из исходного места и записывает в какое-то новое — например, в отдельный массив приоритетных задач. Получается, будто она физически вытянула нужные задачи из исходного массива. Побочный эффект этой функции — изменение исходного массива задач в памяти.
- pullTasksByPriority(‘high’) — физически вытащит задачи из исходного массива и переместит их в какой-то новый. В старом массиве уменьшится число задач.
- Такие изменения называют мутациями: я вызвал функцию в одном месте, а мутировало что-то в другом.
Программисты настороженно относятся к мутациям, потому что за ними сложно следить. Что если из-за какой-то ошибки функции выполнятся в неправильном порядке и уничтожат важные для программы данные? Или функция выполнится непредсказуемо много раз? Или она застрянет в цикле и из-за мутаций разорвёт память? Или мутация произойдёт не с тем куском программы, который мы изначально хотели?
Вот типичная ошибка, связанная с мутацией. Мы пишем игру, нужно поменять сумму игровых очков. За это отвечает функция changeScore(), которая записывает результат в глобальную переменную playerScore — то есть мутирует эту переменную. Мы случайно, по невнимательности, вызвали эту функцию в двух местах вместо одного, и баллы увеличиваются вдвое. Это баг.
Другая типичная ошибка. Программист написал функцию, которая удаляет из таблицы последнюю строку, потому что был почему-то уверен: строка будет пустой и никому не нужной. Случайно эта функция вызывается в бесконечном цикле и стирает все строки, от последней к первой. Данные уничтожаются. А вот если бы функция не удаляла строку из таблицы, а делала новую таблицу без последней строки, данные бы не пострадали.
Без мутирующих функций, конечно, мы не обойдёмся — нужно и выводить на экран, и писать в файл, и работать с глобальными переменными. Сложно представить программу, в которой вообще не будет мутирующих функций. Но программисты предпочитают выделять такие функции отдельно, тестировать их особо тщательно, и внимательно следить за тем, как они работают. Грубо говоря, если функция вносит изменения в большой важный файл, она должна как минимум проверить корректность входящих данных и сохранить резервную копию этого файла.
Как этим пользоваться
Когда будете писать свою следующую функцию, задайтесь вопросами:
- Нет ли тут каких-то зависимостей, которые могут повести себя непредсказуемо? Не беру ли я данные неизвестно откуда? Что если все эти данные у меня не возьмутся или окажутся не тем, что мне надо? Как защитить программу на случай, если этих данных там не окажется?
- Влияет ли эта функция на данные за её пределами?
И если логика программы позволяет, постарайтесь сделать так, чтобы функция ни от чего не зависела и ни на что за своими пределами не влияла. Тогда код будет более читаемым, а коллеги-программисты сразу увидят, что перед ними вдумчивый разработчик.