Функции: именованная коробка с одним выходом
Аргументы, возвращаемое значение, область видимости и побочные эффекты — и почему тестировать можно только коробку размером с предложение.
Ты дважды написал одни и те же три строки в двух местах, поменяв одно число. Потом исправил ошибку в одной копии и забыл про вторую. Функция существует, чтобы это прекратить. Это именованная коробка: кладёшь внутрь значения, наружу выходит одно значение, а содержимое ты пишешь один раз.
Представь машину с ячейками. Входы называются параметрами, когда ты описываешь коробку, и аргументами, когда ты их подставляешь. Выход — это то, что возвращает return. Через стенку не проходит ничего другого: коробка не видит остальную программу, кроме того, что ты ей сам передал.
inputs box output
price, count --> totalPrice --> a number
price = 2.50, count = 4 --> 10.00Два входа, один выход. Стенка посередине и есть суть всей идеи.Одна и та же коробка на трёх языках: цена умножить на количество.
def total_price(price, count):
return price * count
total = total_price(2.50, 4)Python: def, двоеточие и тело с отступом.function totalPrice(price, count) {
return price * count;
}
const total = totalPrice(2.50, 4);JavaScript: function, фигурные скобки и return с точкой с запятой.func totalPrice(price float64, count int) float64 {
return price * float64(count)
}
total := totalPrice(2.50, 4)Go: func, тип каждого входа и тип выхода после закрывающей скобки.Аргументы сопоставляются с параметрами по позиции, а не по смыслу. Язык понятия не имеет, что первый параметр назывался price; он знает только, что первый аргумент попадает в первую ячейку.
total_price(2.50, 4) arguments in the call
2.50 -> price parameters in the definition
4 -> count
total_price(4, 2.50) same shape, different meaningТе же два числа, но переставленные. Обе строки выполнятся. Правильна только одна.Маленькие функции легко тестировать, и это практическая причина писать именно их. В dev.testing ты видел, что тест — это утверждение о том, что делает код. Функция с двумя входами и одним выходом делает такое утверждение совсем простым, потому что готовить больше нечего: ни базы данных, ни браузера, ни порядка событий.
def discount(total):
if total >= 20:
return total * 0.9
return total
assert discount(20) == 18.0
assert discount(10) == 10.0Две строки описывают всю функцию. Ни подготовки, ни состояния, ни сюрпризов.Так что правило тут не про красоту. Функцию на сорок строк, которая читает три глобальные переменные, нельзя протестировать, не построив вокруг неё половину мира, поэтому её никто не тестирует, поэтому она ломается. Коробку размером с предложение можно проверить одной строкой и починить за минуту.
Область видимости отвечает на вопрос, что может видеть имя. Когда ты вызываешь функцию, у неё появляется своя маленькая комната. Имена, созданные внутри этой комнаты, живут только пока идёт вызов, и исчезают, когда return отдаёт ответ. Функция может читать имена снаружи, а снаружи внутрь заглянуть не может.
const rate = 0.21;
function vat(net) {
const inner = net * rate;
return inner;
}
vat(100)
innerrate видно внутри функции. inner снаружи не видно.И наконец честное слово о побочных эффектах. Чистая функция смотрит только на свои аргументы и выдаёт только своё возвращаемое значение. Вызови её с теми же аргументами тысячу раз, и тысячу раз получишь тот же ответ. Нечистая функция ещё и меняет что-то вне себя: печатает, пишет файл, отправляет запрос, увеличивает счётчик.
pure: def vat(net): return net * 0.21
vat(100) always 21.0
impure: def vat(net):
print("calculating")
total = net * 0.21
save_to_file(total)
return totalПервую можно предсказать. Вторую можно только запустить и надеяться.Практика
Функция должна вернуть цену с налогом. Она печатает верное число, но вызывающий код не получает обратно ничего. В чём ошибка?
Подсказки
- Коробка возвращает значение; напечатать значение и вернуть значение — это два разных действия.
- Спроси, что получает вызывающий код. Если функция показывает ответ на экране и ничего не отдаёт, вызывающий не получает ничего.
Сопоставь каждую функцию с тем, что она делает с миром вокруг себя.
Подсказки
- Чистая функция — это коробка, которую можно предсказать по одним лишь её входам.
- Побочные эффекты не зло: программа, которая ничего не пишет и не печатает, не делает ничего полезного. Смысл в том, чтобы их замечать.
Расставь моменты одного вызова функции по порядку, от самого вызова до строки после него.
- 1Вызов начинается, и аргументы попадают в ячейки параметров
- 2Тело выполняется и читает свои собственные имена
- 3return отдаёт вызывающему коду одно значение назад
- 4Вызывающий код вычисляет значения аргументов
- 5Функция останавливается, и вызывающий код продолжает со следующей строки
Подсказки
- Вызов не может начать работу, пока у аргументов нет значения.
- Имена внутри коробки создаются, когда начинается вызов, и исчезают, когда return отдаёт ответ.