PHP: Функции

Чистые функции

Функции в программировании обладают рядом важных характеристик, зная и понимая которые, вы сможете лучше строить работу с ними и, главное, научитесь правильно их выделять из кода.

Детерминированность

Стандартная функция rand, вызванная без аргументов, возвращает некоторое случайное число.

<?php

rand(); // 151273074
rand(); // 1129177627

Функция нужная и полезная, но неудобная в отладке и тестировании. Связано это с тем, что для одних и тех же входных аргументов (отсутствие аргументов также попадает под это понятие), она может возвращать разные значения. Функции с таким поведением называются недетерминированными. К ним, в том числе, относятся функции, оперирующие системным временем.

<?php

// возвращает текущий год
date('Y'); // 2019

Хотя прямо сейчас повторный запуск вернёт точно такое же значение, через год оно уже будет другим (2020). То есть недетерминированной функция считается в том случае, когда она ведёт себя так хотя бы единожды.

Детерминированные функции, напротив, ведут себя предсказуемо. Для одних и тех же входных данных они всегда выдают один и тот же результат. Именно такими являются функции в математике. Для одного и того же x результат работы функции y = f(x) будет один и тот же. Интересно то, что, например, функция print_r детерминированная. Дело в том, что она всегда возвращает одно и то же значение для любых входных данных. Это значение true, а не то, что печатается на экран, как можно было бы подумать. Печать на экран - побочный эффект, о нем мы поговорим чуть позже.

<?php

var_dump(print_r('lala'));
bool(true)

Понятие "Детерминированность" не ограничивается программированием или математикой. Сквозь него можно рассматривать практически любой процесс. Например, подбрасывание монетки — недетерминированный процесс, его результат всегда случаен.

Почему это понятие так важно? Детерминированность даёт нам предсказуемость, а это, прямым образом, влияет на количество состояний, которые надо обрабатывать и предусматривать. В конечном итоге, код становится проще, а логика прямолинейнее. Пример: создание чего-либо на сайтах в интернете, обычно, недетерминированный процесс. То есть, если быстро нажать два раза кнопку отправки формы, то во многих ситуациях можно получить два раза оставленный комментарий, два раза отправленные деньги. Разработчикам сайтов приходится защищаться, ставить защиту от двойных нажатий или производить проверку на дубликаты уже внутри приложения.

Другой пример - создание директории. В командной строке эта операция выполняется с помощью программы mkdir.

$ mkdir test
$ mkdir test
mkdir: test: File exists

Как видно из вывода выше, повторный запуск создания директории выдаёт ошибку, так как она уже создана. Получается, нам нужно учитывать два состояния:

  1. Директории не существовало
  2. Директория существует

В случае детерминированной версии mkdir об этом можно было бы не думать.

Понятие детерминированности играет огромную роль в администрировании, в задачах связанных с программной настройкой серверов (configuration management), выкладкой ПО и обновлениями. Ключевые слова: docker, immutable infrastructure, ansible.

Функция становится недетерминированной и в том случае, если она обращается не только к своим аргументам, но и некоторым внешним данным, например глобальным переменным, переменным окружения и так далее. Так происходит потому, что внешние данные могут измениться, и функция начнёт выдавать другой результат даже если в неё передаются одни и те же аргументы.

<?php

function getCurrentShell()
{
    // Функция getenv обращается к указанной переменной окружения
    return getenv('SHELL'); // /bin/bash
}

В общем случае нельзя сказать, что отсутствие детерминированности — абсолютное зло. Более того, многих полезных программ попросту бы не существовало без возможности писать недетерминированный код. Общая рекомендация при работе с детерминированностью звучит следующим образом. Если есть возможность написать функцию так, что она будет детерминированной, то так и делайте. Не используйте глобальных переменных, создавайте функции зависящие только от своих собственных аргументов.

Побочные эффекты (side effects)

Вторая ключевая характеристика функций - наличие побочных эффектов. Побочным эффектом называют любые действия изменяющие среду выполнения. К ним относятся любые файловые операции, такие как запись в файл, отправка или приём данных по сети, даже вывод в консоль или чтение файла. Кроме того, побочными эффектами считаются обращения к глобальным переменным (как на чтение, так и запись) и изменение входных аргументов в случае, когда они передаются по ссылке. Вызов функции с побочными эффектами также считается побочным эффектом.

<?php

function sayHiTo($name)
{
    print_r("Hi, {$name}");
}

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

<?php

function sum($num1, $num2)
{
    return $num1 + $num2;
}

Без побочных эффектов невозможно написать ни одной полезной программы. Какие бы важные вычисления она ни делала, их результат должен быть как-то продемонстрирован. В самом простом случае его нужно вывести на экран, что автоматически приводит нас к побочным эффектам.

<?php

print_r(2 ** 5);

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

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

Например, программа, которая конвертирует файл из текстового формата в PDF, в идеале выполняет ровно два побочных эффекта:

  1. Читает файл в самом начале работы программы.
  2. Записывает результат работы программы в новый файл.

Между этими двумя пунктами и происходит основная работа, которая содержит чистую алгоритмическую часть. Следовательно, побочные эффекты будут находиться только в верхнем слое приложения, а ядро, выполняющее основную работу, останется чистым от них.

Инкремент и декремент — единственные базовые арифметические операции в PHP, которые обладают побочными эффектами (изменяют само значение в переменной). Именно поэтому с ними сложно работать в составных выражениях. Они могут приводить к таким сложноотлавливаемым ошибкам, что во многих языках вообще отказались от их введения (в Ruby и Python их нет), а в JS стандарты кодирования предписывают их не использовать.

Чистые функции

Pure Functions

Идеальная функция с точки зрения удобства работы с ней называется чистой (pure). Чистая функция — это детерминированная функция, которая не производит побочных эффектов. Такая функция зависит только от своих входных аргументов и всегда ведёт себя предсказуемо. Такие функции на 100% соответствуют своим математическим аналогам и могут рассматриваться как математические функции.

Чистые функции обладают рядом ключевых достоинств:

  • Их крайне просто тестировать. Достаточно передать на вход функции нужные параметры и посмотреть ожидаемый выход.
  • Их безопасно запускать повторно, что особенно актуально в асинхронном коде или в случае многопоточного кода.
  • Их легко комбинировать, получая новое поведение без необходимости переписывать программу (подробнее далее по курсу).

В хорошо спроектированных программах побочные эффекты стараются изолировать в небольшой части приложения так, чтобы большая часть кода была чистой.

Прямо сейчас сказанное выше может звучать довольно абстрактно, что понятно. Осознание этой темы требует не только общего понимания прочитанного, но и набитых шишек, связанных со сложностью работы в мешанине побочных эффектов. Тему чистоты мы будем поднимать регулярно. Особенно сильно она прорабатывается в проектах, на живом коде.


Дополнительные материалы

  1. Побочные эффекты
  2. Детерминированная функция

<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты.

Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Для полного доступа к курсу нужна профессиональная подписка

Профессиональная подписка откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
115
курсов
892
упражнения
2241
час теории
3196
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 115 курсов, 2000+ часов теории
  • 800 практических заданий в браузере
  • 250 000 студентов

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг».

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг».