0

Функция в контексте программирования

Переключение контекста (англ. context switch) — в многозадачных ОС и средах — процесс прекращения выполнения процессором одной задачи (процесса, потока, нити) с сохранением всей необходимой информации и состояния, необходимых для последующего продолжения с прерванного места, и восстановления и загрузки состояния задачи, к выполнению которой переходит процессор.

В процедуру переключения контекста входит т. н. планирование задачи — процесс принятия решения, какой задаче передать управление.

Описание

При переключении контекста происходит сохранение и восстановление следующей информации:

  • Регистровый контекст регистров общего назначения (в том числе флаговый регистр)
  • Контекст состояния сопроцессора с плавающей точкой / регистров MMX (x86)
  • Состояние регистров SSE, AVX (x86)
  • Состояние сегментных регистров (x86)
  • Состояние некоторых управляющих регистров (например, регистр CR3, отвечающий за страничное отображение памяти процесса) (x86)

В ядре ОС с каждым потоком связаны следующие структуры:

  • Общая информация pid, tid, uid, gid, euid, egid и т. д.
  • Состояние процесса/потока
  • Права доступа
  • Используемые потоком ресурсы и блокировки
  • Счетчики использования ресурсов (например, таймеры использованного процессорного времени)
  • Регионы памяти, выделенные процессу

Переключение контекста и производительность

Кроме того, что очень важно, при переключении контекста происходят следующие программно-незаметные аппаратные действия, влияющие на производительность:

  • Происходит очистка конвейера команд и данных процессора
  • Очищается TLB, отвечающий за страничное отображение линейных адресов на физические.

Кроме того, следует учесть следующие факты, влияющие на состояние системы:

  • Содержимое кэша (особенно это касается кэша первого уровня), накопленное и «оптимизированное» под выполнение одного потока, оказывается совершенно неприменимым к новому потоку, на который происходит переключение.
  • При переключении контекста на процесс, который до этого долгое время не использовался (см. Подкачка страниц), многие страницы могут физически отсутствовать в оперативной памяти, что порождает подгрузку вытесненных страниц из вторичной памяти.

Переключение контекста и ОС

С точки зрения прикладного уровня переключение контекста можно разделить на добровольное (voluntary) и принудительное (non-voluntary): выполняющийся процесс/поток может сам передать управление другому потоку либо ядро может насильно отобрать у него управление.

  1. Ядро ОС может отобрать управление у выполняющегося процесса/потока при истечении кванта времени, выделенного на выполнение. С точки зрения программиста это означает, что управление могло уйти от потока в «самый неподходящий» момент времени, когда структуры данных могут находиться в противоречивом состоянии из-за того, что их изменение не было завершено.
  2. Выполнение блокирующего системного вызова. Когда приложение производит ввод-вывод, ядро может решить, что можно отдать управление другому потоку/процессу в ожидании, пока запрошенный данным потоком дисковый либо сетевой ввод-вывод будет выполнен. Данный вариант является самым производительным.
  3. Синхронизирующие примитивы ядра. Мьютексы, Семафоры и т. д. Это и есть основной источник проблем с производительностью. Недостаточно продуманная работа с синхронизирующими примитивами может приводить к десяткам тысяч, а в особо запущенных случаях — и к сотням тысяч переключений контекста в секунду.
  4. Системный вызов, явно ожидающий наступления события (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 советов от МФТИ

  1. Определите, достаточно ли у вас базовых знаний для участия в соревнованиях. На раннем этапе необходимо:
    • Знать хотя бы один язык программирования. Лучше всего подойдут С, С++ или Java. В некоторых случаях можно обойтись и Python, но этот язык медленнее других. Также обязателен опыт алгоритмического программирования.
    • Обладать математической подготовкой: уметь решать нестандартные задачи и строить математические модели.
    • Знать английский достаточно, чтобы понимать лекции и задачи.

  1. Организуйте индивидуальные тренировки. Даже если вы будете выступать на командных соревнованиях, уделите время самостоятельной прокачке навыков — как практических, так и теоретических.
  2. Изучите теорию. Почитайте справочник «Олимпиадное программирование», разберите материалы на бесплатном портале E–maxx — там собраны алгоритмы, электронные пособия и новости спортивного программирования.
  3. Решайте задачи — как можно больше и чаще. Ищите задания с прошлых чемпионатов и олимпиад. Их публикуют на Timus Online Judge, Codeforces, TopCoder, Spoj, CodeChef, Codingame и C Puzzles (для языка C). На русском языке доступен Сервис дистанционной подготовки к информатике.
  4. Для начала решайте задачи без учёта времени. Потом уже ставьте таймер. Не останавливайтесь на самых лёгких вариантах — постепенно двигайтесь от простого к более сложному.
  5. Организуйте командные тренировки. Это поможет наладить отношения в коллективе и определить сильные и слабые стороны каждого. Для этой цели подойдут как локальные тренировки, так и сборы по спортивному программированию. В России регулярно проводятся кэмпы для подготовки к крупным соревнованиям. География обширная: Москва, Петрозаводск, Ижевск, Иркутск, Уфа, Владивосток. Есть и международная программа Moscow Workshops, которая зародилась на кампусе Физтеха, а сейчас проводит сборы по всему миру — в Пекине, Сингапуре, Максате, Риге и других городах. В рамках проекта есть и лагерь для учеников 9–11 классов — Moscow Workshops Juniors. Он подойдет для уже прокачанных школьников. Участники каждый день тренируются, посещают лекции, но также занимаются спортом и участвуют в интеллектуальных играх, чтобы поддерживать баланс.
  6. Не забывайте про техническую подготовку компьютера. ICPC предоставляет устройства с установленным программным обеспечением. На региональных чемпионатах могут потребоваться свои ноутбуки — заранее уточните, какие требования к настройкам предъявляют организаторы.
  7. Не пренебрегайте пробным туром перед соревнованием. Это поможет протестировать систему, проверить базовую функциональность и оценить дополнительные возможности.
  8. Помните о гибких навыках. Вы можете идеально знать язык программирования и великолепно решать задачи, но при этом не уметь планировать время или управлять процессами. Эти навыки нужно прокачивать точно так же, как навыки работы с Java. Изучите принципы тайм-менеджмента и командообразования. Тренируйте креативность, когнитивную гибкость и критическое мышление. Учитесь вести переговоры и решать конфликты, а также грамотно распределять ресурсы.
  9. Не геройствуйте — отсутствие сна и отказ от еды ещё никому не помогал победить.

Хоро­ший про­грам­мист ста­ра­ет­ся делать свои функ­ции чисты­ми. Если знать, что это такое, мож­но сой­ти за сво­е­го, а заод­но напи­сать чита­е­мый код.

Если вы не совсем пони­ма­е­те, что такое функ­ция и зачем она нуж­на — доб­ро пожа­ло­вать в наш кат:

Что такое функции и зачем они нужны

Что такое функция

Функ­ция — это мини-программа внут­ри вашей основ­ной про­грам­мы, кото­рая дела­ет какую-то одну понят­ную вещь. Вы одна­жды опи­сы­ва­е­те, что это за вещь, а потом ссы­ла­е­тесь на это опи­са­ние.

Напри­мер, вы пише­те игру. Каж­дый раз, когда игрок попа­да­ет в цель, уби­ва­ет вра­га, дела­ет ком­бо, закан­чи­ва­ет уро­вень или пада­ет в лаву, вам нуж­но доба­вить или уба­вить ему очков. Это дела­ет­ся дву­мя дей­стви­я­ми: к ста­рым очкам добав­ля­ют­ся новые, на экран выво­дит­ся новая сум­ма очков. Допу­стим, эти дей­ствия зани­ма­ют 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 — то есть мути­ру­ет эту пере­мен­ную. Мы слу­чай­но, по невни­ма­тель­но­сти, вызва­ли эту функ­цию в двух местах вме­сто одно­го, и бал­лы уве­ли­чи­ва­ют­ся вдвое. Это баг.

Дру­гая типич­ная ошиб­ка. Про­грам­мист напи­сал функ­цию, кото­рая уда­ля­ет из таб­ли­цы послед­нюю стро­ку, пото­му что был почему-то уве­рен: стро­ка будет пустой и нико­му не нуж­ной. Слу­чай­но эта функ­ция вызы­ва­ет­ся в бес­ко­неч­ном цик­ле и сти­ра­ет все стро­ки, от послед­ней к пер­вой. Дан­ные уни­что­жа­ют­ся. А вот если бы функ­ция не уда­ля­ла стро­ку из таб­ли­цы, а дела­ла новую таб­ли­цу без послед­ней стро­ки, дан­ные бы не постра­да­ли.

Без мути­ру­ю­щих функ­ций, конеч­но, мы не обой­дём­ся — нуж­но и выво­дить на экран, и писать в файл, и рабо­тать с гло­баль­ны­ми пере­мен­ны­ми. Слож­но пред­ста­вить про­грам­му, в кото­рой вооб­ще не будет мути­ру­ю­щих функ­ций. Но про­грам­ми­сты пред­по­чи­та­ют выде­лять такие функ­ции отдель­но, тести­ро­вать их осо­бо тща­тель­но, и вни­ма­тель­но сле­дить за тем, как они рабо­та­ют. Гру­бо гово­ря, если функ­ция вно­сит изме­не­ния в боль­шой важ­ный файл, она долж­на как мини­мум про­ве­рить кор­рект­ность вхо­дя­щих дан­ных и сохра­нить резерв­ную копию это­го фай­ла.

Как этим пользоваться

Когда буде­те писать свою сле­ду­ю­щую функ­цию, задай­тесь вопро­са­ми:

  1. Нет ли тут каких-то зави­си­мо­стей, кото­рые могут пове­сти себя непред­ска­зу­е­мо? Не беру ли я дан­ные неиз­вест­но отку­да? Что если все эти дан­ные у меня не возь­мут­ся или ока­жут­ся не тем, что мне надо? Как защи­тить про­грам­му на слу­чай, если этих дан­ных там не ока­жет­ся?
  2. Вли­я­ет ли эта функ­ция на дан­ные за её пре­де­ла­ми?

И если логи­ка про­грам­мы поз­во­ля­ет, поста­рай­тесь сде­лать так, что­бы функ­ция ни от чего не зави­се­ла и ни на что за сво­и­ми пре­де­ла­ми не вли­я­ла. Тогда код будет более чита­е­мым, а коллеги-программисты сра­зу уви­дят, что перед ними вдум­чи­вый раз­ра­бот­чик.

admin

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *