Функции в программировании обладают рядом важных характеристик, зная и понимая которые, вы сможете лучше строить работу с ними и, главное, научитесь правильно их выделять из кода.
Детерминированность
Стандартная функция 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
Как видно из вывода выше, повторный запуск создания директории выдаёт ошибку, так как она уже создана. Получается, нам нужно учитывать два состояния:
- Директории не существовало
- Директория существует
В случае детерминированной версии 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, в идеале выполняет ровно два побочных эффекта:
- Читает файл в самом начале работы программы.
- Записывает результат работы программы в новый файл.
Между этими двумя пунктами и происходит основная работа, которая содержит чистую алгоритмическую часть. Следовательно, побочные эффекты будут находиться только в верхнем слое приложения, а ядро, выполняющее основную работу, останется чистым от них.
Инкремент и декремент — единственные базовые арифметические операции в PHP, которые обладают побочными эффектами (изменяют само значение в переменной). Именно поэтому с ними сложно работать в составных выражениях. Они могут приводить к таким сложноотлавливаемым ошибкам, что во многих языках вообще отказались от их введения (в Ruby и Python их нет), а в JS стандарты кодирования предписывают их не использовать.
Чистые функции
Идеальная функция с точки зрения удобства работы с ней называется чистой (pure). Чистая функция — это детерминированная функция, которая не производит побочных эффектов. Такая функция зависит только от своих входных аргументов и всегда ведёт себя предсказуемо. Такие функции на 100% соответствуют своим математическим аналогам и могут рассматриваться как математические функции.
Чистые функции обладают рядом ключевых достоинств:
- Их крайне просто тестировать. Достаточно передать на вход функции нужные параметры и посмотреть ожидаемый выход.
- Их безопасно запускать повторно, что особенно актуально в асинхронном коде или в случае многопоточного кода.
- Их легко комбинировать, получая новое поведение без необходимости переписывать программу (подробнее далее по курсу).
В хорошо спроектированных программах побочные эффекты стараются изолировать в небольшой части приложения так, чтобы большая часть кода была чистой.
Прямо сейчас сказанное выше может звучать довольно абстрактно, что понятно. Осознание этой темы требует не только общего понимания прочитанного, но и набитых шишек, связанных со сложностью работы в мешанине побочных эффектов. Тему чистоты мы будем поднимать регулярно. Особенно сильно она прорабатывается в проектах, на живом коде.
Дополнительные материалы

Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Урок «Как эффективно учиться на Хекслете»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.