Пользовательские функции в Си
Итак, зачем нужны пользовательские функции? Пользовательские функции нужны для того, чтобы программистам было проще писать программы.
Помните, мы говорили о парадигмах программирования, а точнее о структурном программировании. Основной идеей там было то, что любую программу можно написать используя только три основных конструкции: следование, условие и цикл. Теперь к этим конструкциям мы добавим ещё одну – «подпрограммы» – и получим новую парадигму процедурное программирование» .
Отличие лишь в том, что отдельные кусочки нашей основной программы (в частности, повторяющиеся) мы будем записывать в виде отдельных функций (подпрограмм, процедур) и по мере необходимости их вызывать. По сути, программа теперь будет описывать взаимодействие различных функций.
В принципе, мы уже используем эту парадигму. Если вам пока ещё не совсем ясно, почему это проще, то просто представьте, что вместо того чтобы вызвать функцию exp(x) из заголовочного файла math.h вам каждый раз необходимо было бы описывать подробно, как вычислить значение этой функции.
Итак, в этом уроке мы подробно обсудим то, как функции устроены изнутри. А также научимся создавать свои собственные пользовательские функции.
Как устроены функции
Вспомним информацию с первого урока. Все функции, в том числе и те, которые пишет пользователь, устроены сходным образом. У них имеется две основных составных части: заголовок функции и тело функции.
int main(void)< // заголовок функции // в фигурных скобках записано тело функции >
С телом функции всё ясно: там описывается алгоритм работы функции. Давайте разберёмся с заголовком. Он состоит из трёх обязательных частей:
- тип возвращаемого значения;
- имя функции;
- аргументы функции.
Сначала записывается тип возвращаемого значения, например, int , как в функции main . Если функция не должна возвращать никакое значение в программу, то на этом месте пишется ключевое слово void . Казалось бы, что раз функция ничего не возвращает, то и не нужно ничего писать. Раньше, кстати, в языке Си так и было сделано, но потом для единообразия всё-таки добавили. Сейчас современные компиляторы будут выдавать предупреждения/ошибки, если вы не укажете тип возвращаемого значения.
В некоторых языках программирования функции, которые не возвращают никакого значения, называют процедурами (например, pascal). Более того, для создания функций и процедур предусмотрен различный синтаксис. В языке Си такой дискриминации нет.
После типа возвращаемого значения записывается имя функции. Ну а уж после имени указываются типы и количество аргументов, которые передаются в функцию.
Давайте посмотрим на заголовки уже знакомых нам функций.
// функция с именем srand, принимающая целое число, ничего не возвращает void srand(int) //функция с именем sqrt, принимающая вещественное число типа float, возвращает вещественное число типа float float sqrt(float) //функция с именем rand, которая не принимает аргументов, возвращает целое число int rand(void) //функция с именем pow, принимающая два аргумента типа double, возвращает вещественное число типа double double pow(double, double)
Как создать свою функцию
Для того чтобы создать свою функцию, необходимо её полностью описать. Тут действует общее правило: прежде чем использовать – объяви и опиши, как должно работать. Для этого вернёмся к схеме структуры программы на языке Си, которая у нас была в самом первом уроке. Отметим на ней те места, где можно описывать функции.
Рис.1 Уточнение структуры программы. Объявление функций.
Как видите, имеется аж два места, где это можно сделать.
Давайте посмотрим на пример, который иллюстрируют создание пользовательской функции вычисления максимального из двух чисел.
#include // объявляем пользовательскую функцию с именем max_num // вход: два целочисленных параметра с именами a и b // выход: максимальное из двух аргументов int max_num(int a, int b) < int max = b; if (a >b) max = a; return max; > //основная программа int main(void)
Давайте я подробно опишу, как будет работать эта программа. Выполняется тело функции main . Создются целые переменные x , y и m . В переменные x и y считываются данные с клавиатуры. Допустим мы ввели 3 5 , тогда x = 3 , y = 5 . Это вам всё и так должно быть понятно. Теперь следующая строчка
m = max_num(x,y);
Переменной m надо присвоить то, что находится справа от знака = . Там у нас указано имя функции, которую мы создали сами. Компьютер ищет объявление и описание этой функции. Оно находится выше. Согласно этому объявлению данная функция должна принять два целочисленных значения. В нашем случае это значения, записанные в переменных x и y . Т.е. числа 3 и 5 . Обратите внимание, что в функцию передаются не сами переменные x и y , а только значения (два числа), которые в них хранятся. То, что на самом деле передаётся в функцию при её вызове в программе, называется фактическими параметрами функции.
Теперь начинает выполняться функция max_num . Первым делом для каждого параметра, описанного в заголовке функции, создается отдельная временная переменная. В нашем случае создаются две целочисленных переменных с именами a и b . Этим переменным присваиваются значения фактических параметров. Сами же параметры, описанные в заголовке функции, называются формальными параметрами. Итак, формальным параметрам a и b присваиваются значения фактических параметров 3 и 5 соответственно. Теперь a = 3 , b = 5 . Дальше внутри функции мы можем работать с этими переменными так, как будто они обычные переменные.
Создаётся целочисленная переменная с именем max , ей присваивается значение b . Дальше проверяется условие a > b . Если оно истинно, то значение в переменной max следует заменить на a .
Далее следует оператор return , который возвращает в вызывающую программу (функцию main ) значение, записанное в переменной max , т.е. 5 . После чего переменные a , b и max удаляются из памяти. А мы возвращаемся к строке
m = max_num(x,y);
Функция max_num вернула значение 5 , значит теперь справа от знака = записано 5 . Это значение записывается в переменную m. Дальше на экран выводится строчка, и программа завершается.
Внимательно прочитайте последние 4 абазаца ещё раз, чтобы до конца уяснить, как работает программа.
А я пока расскажу, зачем нужен нижний блок описания функций. Представьте себе, что в вашей программе вы написали 20 небольших функций. И все они описаны перед функцией main . Не очень-то удобно добираться до основной программы так долго. Чтобы решить эту проблему, функции можно описывать в нижнем блоке.
Но просто так перенести туда полностью код функции не удастся, т.к. тогда нарушится правило: прежде чем что-то использовать, необходимо это объявить. Чтобы избежать подобной проблемы, необходимо использовать прототип функции.
Прототип функции полностью повторяет заголовок функции, после которого стоит ; . Указав прототип в верхнем блоке, в нижнем мы уже можем полностью описать функцию. Для примера выше это могло бы выглядеть так:
#include int max_num(int, int); int main(void) < int x =0, y = 0; int m = 0; scanf("%d %d", &x, &y); m = max_num(x,y); printf("max(%d,%d) = %d\n",x,y,m); return 0; >int max_num(int a, int b) < int max = b; if (a >b) max = a; return max; >
Всё очень просто. Обратите внимание, что у прототипа функции можно не указывать имена формальных параметров, достаточно просто указать их типы. В примере выше я именно так и сделал.
Сохрани в закладки или поддержи проект.
Практика
Решите предложенные задачи:

Для удобства работы сразу переходите в полноэкранный режим
Дополнительные материалы
- пока нет
Функции в Python для начинающих
В этой части мы изучим функции — составные инструкции, которые могут принимать данные ввода, выполнять указания и возвращать данные вывода. Функции позволяют определять и повторно использовать определенную функциональность в компактной форме.
Вызвать функцию — значит передать ей входные данные, необходимые для выполнения и возвращения результата. Когда вы передаете функции входные данные, это называется передача параметра функции.
Функции в Python похожи на математические функции из алгебры. Например, в алгебре функция определяется как-то так:
f(x) = x * 2
Левая часть определяет функцию f , принимающую один параметр, x . А правая часть — это определение функции, которое использует переданный параметр x , чтобы произвести вычисление и вернуть результат. В этом случае значением функции является ее параметр, умноженный на два.
Как в Python функция записывается следующим образом: имя_функции(параметры_через_запятую) . Чтобы вызвать функцию, после ее имени нужно указать круглые скобки и поместить внутрь параметры, отделив каждый из них запятой. Для создания функций в Python выберите ее имя, определите параметры, укажите, что функция должна делать и какое значение возвращать.
def имя_функции(параметры): определениие_функции
Математическая функция f(x) = x * 2 в Python будет выглядеть вот так:
def f(x): return x * 2
Ключевое слово def сообщает Python, что вы определяете функцию. После def вы указываете имя функции; оно должно отвечать тем же правилам, что и имена переменных. Согласно конвенции, в имени функции нельзя использовать заглавные буквы, а слова должны быть разделены подчеркиванием вот_так .
Как только вы присвоили своей функции имя, укажите после него круглые скобки. Внутри скобок должен содержаться один или несколько параметров.
После скобок ставится двоеточие, а новая строка начинается с отступа в четыре пробела. Любой код с отступом в четыре пробела после двоеточия является телом функции. В этом случае тело нашей функции состоит только из одной строки:
return x * 2
Ключевое слово return используется для определения значения, которое функция возвращает при вызове.
Чтобы вызвать функцию в Python, мы используем синтаксис имя_функции(параметры, через, запятую) .
Ниже описан вызов функции f из предыдущего примера с параметром 2 .
Консоль ничего не вывела. Можно сохранить вывод вашей функции в переменной и передать ее функции print .
# Продолжение# предыдущего примераdef f(x): return x * 2result = f(2)print(result) # 4
Вы можете сохранить результат, возвращаемый вашей функцией, в переменной и использовать это значение в программе позднее.
def f(x): return x + 1 z = f(4)if z == 5: print("z равно 5")else: print ("z не равно 5")
У функции может быть один параметр, несколько параметров или вообще их не быть. Чтобы определить функцию, не требующую параметров, оставьте круглые скобки пустыми.
def f(): return 1 + 1 result = f()print(result) # 2
Если хотите, чтобы функция принимала больше одного параметра, отделите каждый параметр в скобках запятой.
def f(x, y, z): return x + y + z result = f(1, 2, 3)print(result) # 6
Наконец, функция не обязана содержать инструкцию return. Если функции нечего возвращать, она возвращает значение None .
def f(): z = 1 + 1result = f()print(result) # None
Обязательные и необязательные параметры ¶
Функция может принимать параметры двух типов. Те, что встречались вам до этого, называются обязательными параметрами. Когда пользователь вызывает функцию, он должен передать в нее все обязательные параметры, иначе Python сгенерирует исключение.
В Python есть и другой вид параметров — опциональные. Опциональные параметры определяются с помощью следующего синтаксиса: имя_функции(имя_параметра = значение_параметра) . Как и обязательные, опциональные параметры нужно отделять запятыми. Ниже приведен пример функции, в коде которой используется опциональный параметр.
def f(x=2): return x**x print (f()) # 4print (f(4)) # 16
Сначала функция вызывается без передачи параметра. Так как параметр необязательный, x автоматически становится равен 2 , и функция возвращает 4 .
Затем та же функция вызывается с параметром 4 . То есть x будет равен 4 и функция вернет 16 . Вы можете определить функцию, которая принимает как обязательные, так и опциональные параметры, но обязательные нужно определять в первую очередь.
def add(x, y=10): return x + yresult = add(2)print(result)
Хотите выучить Python на практике?
Зачем нужны Lambda-функции?
Изучая Python , наткнулся на lambda -функции. Но нигде я пока не нашел примера, где они действительно нужны — только элементарные примеры вида print(lambda x, y: x**2 + y**2) , где вполне можно убрать функциональную обертку. Для чего они действительно нужны и полезны?
Отслеживать
user207618
задан 12 июн 2016 в 17:10
andrey_Kox andrey_Kox
113 1 1 золотой знак 1 1 серебряный знак 5 5 бронзовых знаков
print(lambda: . ) печатает саму функцию (объект), что практически всегда бесполезно.
12 июн 2016 в 18:33
5 ответов 5
Сортировка: Сброс на вариант по умолчанию
Они не могут быть 100% нужны, то есть без операций сложений написать программу нельзя, без лямбда-функций — можно, но с ними короче и удобней. Например, нужно напечатать словарь в порядке убывания суммы каждого значения:
bigrams = sorter = sorted(bigrams, key=lambda key: sum(bigrams[key]), reverse=True) for key in sorter: print(key, bigrams[key])
Иначе нужно написать функцию:
from functools import partial def sort_func(key, dict): return sum(dict[key]) bigrams = partial_sort = partial(sort_func, dict=bigrams) sorter = sorted(bigrams.keys(), key=partial_sort, reverse=True) for key in sorter: print(key, bigrams[key])
Результат одинаков, но с лямбдой короче, не нужно искать глазами sort_func — сразу ясно, как именно сортируется коллекция. В стандартной библиотеке полно функций, которые принимают другие функции: map , reduce , filter , sorted , any , all
Можно написать замыкание:
def addition(x): return lambda y: x + y add_to_ten = addition(10) print(add_to_ten(8)) print(add_to_ten(6))
По идее, лямбда функции абсолютно то же самое, что и обычные функции, но без имени:
def func(x): func = lambda x: x + 1 return 100 import dis print(dis.dis(func))
показывает, что действительно создается новая функция, без обмана.
Отслеживать
ответ дан 12 июн 2016 в 18:28
6,389 4 4 золотых знака 35 35 серебряных знаков 57 57 бронзовых знаков
lambda создаёт анонимные функции в Питоне. Основные отличия от def f() это отсутствие имени и ограничение на тело функции до одного выражения.
Фактически создаваемые объекты c помощью lambda выражения или def конструкции могут быть одинаковыми:
>>> type(lambda: None) >>> def f(): return None . >>> type(f)
lambda часто используется для callback в GUI и сетевых библиотеках (асинхронный код), например:
d.addBoth(lambda _: reactor.stop()) # twisted signal.signal(signal.SIGCHLD, lambda signum, frame: None) # enable SIGCHLD root.bind("", lambda _: root.destroy()) # exit on Esc root.bind('', app.prev_image) # change font that widgets with 'TButton' style use root.after(3000, lambda: ttk.Style().configure('TButton', font=MyFont)) # in 3 secs
lambda можно использовать в любом месте, где ожидается функция:
intervals.sort(key=lambda x: (x.end, (x.end - x.start))) problem.addConstraint(lambda a,b: b+1 == a, ["green", "yellow"]) def longest_alphabetical_substring(text, rank=lambda char: char): .
>>> sorted([(1,-1), (2,-2)]) [(1, -1), (2, -2)] >>> sorted([(1,-1), (2,-2)], key=lambda (a, b): (b, a)) [(2, -2), (1, -1)]
Этот Python 2 код сортирует пары сначала по b , потом по a в отличии сортировки для кортежей по умолчанию.
Часто lambda используется без нужды, например чтобы отсортировать файлы по времени последней модификации в Питоне:
#XXX DO NOT DO IT files.sort(key=lambda path: os.path.getmtime(path)) # не делайте этого
Вместо этого следует передать функцию прямо:
files.sort(key=os.path.getmtime)
Если вам приходится использовать lambda в выражениях с filter() , map() , то следует переписать их, используя генераторы списков, что может сделать код одновременно более читаемым и эффективным, например, вместо:
strings = list(map(lambda b: b.decode('utf-8').replace(os.linesep, '\n'), bytestrings))
strings = [b.decode('utf-8').replace(os.linesep, '\n') for b in bytestrings]
В некоторых случаях, код с lambda может быть менее ясен для новичков, например:
duplicate = reduce(lambda acc, (i, x): acc ^ i ^ x, enumerate(arr), 0) def lcm(*args): """Least common multiple.""" return reduce(lambda a, b: a * b // gcd(a, b), args)
(что лучше переписать, используя явный цикл). Или наоборот более ясен:
L.sort(key=lambda x: x[0]) # vs: L.sort(key=operator.itemgetter(0)) for chunk in iter(lambda: file.read(1
- Multiline Lambda in Python: Why not?
- Why are Python lambdas useful?
- Useful code which uses reduce() in python
Отслеживать
ответ дан 12 июн 2016 в 19:17
52.2k 11 11 золотых знаков 108 108 серебряных знаков 312 312 бронзовых знаков
lambda функции можно вставлять как параметры функции,например в встроенную функцию sorted в key= можно задать ламбду.
Отслеживать
6,343 5 5 золотых знаков 25 25 серебряных знаков 55 55 бронзовых знаков
ответ дан 12 мар 2018 в 19:51
Константин Константин
895 4 4 серебряных знака 15 15 бронзовых знаков
Важно разделять лямбды и замыкания.
- Замыкания - нужны, очень полезны. Применимы в функциональной парадигме программирования, в том числе в функциях обратного вызова (callback), функциях высшего порядка (HOF). Считаю замыкания полезным средством инкапсуляции (связывания) кода и данных, альтернативой инкапсуляции в ООП.
- Лямбда-выражения на Python - конструкторы простых безымянных однострочных функций. Бывают полезны (или нет). Вы можете использовать их для создания замыканий там, где не хотите использовать для создания замыкания именованную функцию. Например, в однострочниках.
Безымянные функции - короче. Именованные функции удобней для отладки и повторного использования.
Лямбды не являются необходимыми ни для функциональной парадигмы, ни для программирования в целом. Представляют собой синтаксический сахар для любителей писать скрипты коротко и быстро. Без любого синтаксического сахара всегда можно обойтись. Вопрос - нужно ли.
Простой и понятный пример на основе кода и пояснения Xyanight, и кода python-scripts.com/lambda . Этот вариант не требует установки PIL, он короче, яснее, не отвлекает внимания от сути примера, работает на Python 2 и 3.
"Допустим, вы создаете в цикле сто кнопок в вашем UI. И каждая такая кнопка вызывает одну и ту же функцию, но с разными параметрами."
import tkinter as tk # Tkinter - Python2, tkinter - Python3 class App: def __init__(self, parent): frame = tk.Frame(parent) frame.pack() for i in range(7): tk.Button( frame, text=str(i), command=lambda numButton=i: self.printNum(numButton) ).pack(side=tk.LEFT) tk.Button(frame, text="Cancel circle", fg="red", command=frame.quit).pack(side=tk.LEFT) def printNum(self, num): print("Вы нажали кнопку: %s" % num) root = tk.Tk() app = App(root) root.mainloop()
В данном коде я связываю данные (номер кнопки) и код (экземплярный метод printNum) в замыкание, с помощью lambda, для отложенного (callback) вызова этой функции с инкапсулированными внутрь замыкания данными (у каждой Button ее параметр command помнит и код печати, и номер кнопки для печати). Код с lambda короче и ясней, чем при использовании именованной функции (созданной с помощью def, а не lambda) в качестве замыкания.
Это мой первый пост на Stackoverflow. Спасибо.
Функции. Зачем они нужны и как их писать, чтобы вас уважали программисты
Сложная важная статья для тех, кто хочет стать крутым программистом.
Хороший программист старается делать свои функции чистыми. Если знать, что это такое, можно сойти за своего, а заодно написать читаемый код.
Если вы не совсем понимаете, что такое функция и зачем она нужна — добро пожаловать в наш кат:
Что такое функции и зачем они нужны
Что такое функция
Функция — это мини-программа внутри вашей основной программы, которая делает какую-то одну понятную вещь. Вы однажды описываете, что это за вещь, а потом ссылаетесь на это описание.
Например, вы пишете игру. Каждый раз, когда игрок попадает в цель, убивает врага, делает комбо, заканчивает уровень или падает в лаву, вам нужно добавить или убавить ему очков. Это делается двумя действиями: к старым очкам добавляются новые, на экран выводится новая сумма очков. Допустим, эти действия занимают 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 — то есть мутирует эту переменную. Мы случайно, по невнимательности, вызвали эту функцию в двух местах вместо одного, и баллы увеличиваются вдвое. Это баг.
Другая типичная ошибка. Программист написал функцию, которая удаляет из таблицы последнюю строку, потому что был почему-то уверен: строка будет пустой и никому не нужной. Случайно эта функция вызывается в бесконечном цикле и стирает все строки, от последней к первой. Данные уничтожаются. А вот если бы функция не удаляла строку из таблицы, а делала новую таблицу без последней строки, данные бы не пострадали.
Без мутирующих функций, конечно, мы не обойдёмся — нужно и выводить на экран, и писать в файл, и работать с глобальными переменными. Сложно представить программу, в которой вообще не будет мутирующих функций. Но программисты предпочитают выделять такие функции отдельно, тестировать их особо тщательно, и внимательно следить за тем, как они работают. Грубо говоря, если функция вносит изменения в большой важный файл, она должна как минимум проверить корректность входящих данных и сохранить резервную копию этого файла.
Как этим пользоваться
Когда будете писать свою следующую функцию, задайтесь вопросами:
- Нет ли тут каких-то зависимостей, которые могут повести себя непредсказуемо? Не беру ли я данные неизвестно откуда? Что если все эти данные у меня не возьмутся или окажутся не тем, что мне надо? Как защитить программу на случай, если этих данных там не окажется?
- Влияет ли эта функция на данные за её пределами?
И если логика программы позволяет, постарайтесь сделать так, чтобы функция ни от чего не зависела и ни на что за своими пределами не влияла. Тогда код будет более читаемым, а коллеги-программисты сразу увидят, что перед ними вдумчивый разработчик.
Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
