Функции
Предпосылки: вывод (&&, !=, >=), Циклы (while, повторение действий).
В приложении маркетплейса правило выплаты продавцу считают в нескольких частях программы: в кнопке вывода денег, в баннере и в ночной проверке. Правило изменилось: ручная блокировка теперь тоже должна останавливать выплату. Компьютер выполнит любую проверку, которую ему дадут. Трудность в другом: одна и та же логика быстро расползается по файлу под разными локальными именами и в разном порядке условий.
Проблема здесь уже не в длине кода как таковой. Проблема в том, что человек больше не может надёжно удерживать в голове все копии правила и замечать, где они начали расходиться.
Правильная логика в чистом виде выглядит так:
can_request_payout =
profile_verified &&
bank_account_connected &&
review_state != "hold" &&
requested_amount >= 50 &&
available_balance >= requested_amountВот пример. Его не нужно разбирать как упражнение на внимательность — достаточно посмотреть, как быстро одно короткое правило превращается в тяжёлый для чтения файл, где одинаковая проверка уже живёт в UI, тексте ошибок, API и ночной обработке.
Где правило уже разъехалось
Разбирать такой файл построчно ради победы не нужно. Важно увидеть сам тип боли: даже в этом сокращённом фрагменте правило уже живёт в нескольких формах, и они начинают расходиться.
- В
seller dashboard bannerполностью потерялась ручная блокировка: там есть профиль, банк, минимальная сумма и баланс, но нетreview_state != "hold".- В
create payout APIта же проблема, только в другой форме: условие разбито на несколькоif, и новая часть правила туда просто не дошла.- В
finance sidebarправило уже дрейфует ещё сильнее: там проверка на минимальную сумму сталаrequested_amount > 50, хотя в исходном правиле было>= 50.- В
finance CSV exportручная блокировка есть, но потерялась проверка на минимальную сумму.- При этом
withdraw page button,withdraw page reason text,night payout queueиsupport toolвсё ещё держат полную версию правила. Именно поэтому такой файл неприятен: тут нет одной поломки, тут уже несколько почти одинаковых кусков, которые разъезжаются каждый по-своему.
Функция: один кусок логики с именем
Когда одна и та же логика нужна в нескольких местах, её выносят в функцию:
def can_request_payout(profile_verified, bank_account_connected, review_state, requested_amount, available_balance)
profile_verified &&
bank_account_connected &&
review_state != "hold" &&
requested_amount >= 50 &&
available_balance >= requested_amount
enddef (define — «определить») начинает определение функции. can_request_payout — имя функции. profile_verified, bank_account_connected, review_state, requested_amount, available_balance — параметры: входные данные, с которыми она работает.
Теперь вместо трёх копий формулы остаются три вызова:
button_enabled = can_request_payout(profile_verified, bank_account_connected, review_state, requested_amount, available_balance)
banner_visible = can_request_payout(profile_verified, bank_account_connected, review_state, requested_amount, available_balance)
should_queue_payout = can_request_payout(profile_verified, bank_account_connected, review_state, requested_amount, available_balance)Если правило снова изменится, править нужно одну функцию, а не каждую копию отдельно.
Это не специальный приём Ruby. Та же идея есть в разных языках: дать куску логики имя, передать входные данные и получить результат обратно.
Ruby: def greeting(name) ... end
JavaScript: function greeting(name) { ... }
Go: func greeting(name string) string { ... }Синтаксис меняется, но смысл один и тот же: функция собирает шаги в один повторно используемый блок.
Параметры и результат
Функция получает значения через параметры и возвращает результат:
def can_request_payout(profile_verified, bank_account_connected, review_state, requested_amount, available_balance)
profile_verified &&
bank_account_connected &&
review_state != "hold" &&
requested_amount >= 50 &&
available_balance >= requested_amount
end
result = can_request_payout(true, true, "clear", 120, 300)
puts result # trueПри вызове can_request_payout(true, true, "clear", 120, 300) первое true попадает в profile_verified, второе true — в bank_account_connected, строка "clear" — в review_state, 120 — в requested_amount, 300 — в available_balance. Последнее вычисленное выражение становится результатом функции.
Функция может возвращать и не число, а, например, строку:
def greeting(name)
"Hello, " + name
endВ Ruby результатом часто становится последнее вычисленное выражение. Во многих других языках результат возвращают явно через return, а иногда ещё и указывают его тип в заголовке функции. Но во всех случаях вопрос один и тот же: какие данные функция получает и что она обещает вернуть назад.
Вызовы и стек
Стек (stack — «стопка»: последний положенный элемент снимается первым) подробно устроен в заметке о памяти, но главная идея нужна уже сейчас.
Функции могут вызывать другие функции:
def enough_balance(requested_amount, available_balance)
available_balance >= requested_amount
end
def can_request_payout(profile_verified, bank_account_connected, review_state, requested_amount, available_balance)
profile_verified &&
bank_account_connected &&
review_state != "hold" &&
requested_amount >= 50 &&
enough_balance(requested_amount, available_balance)
endКогда can_request_payout вызывает enough_balance, программа временно переходит во вторую функцию, получает результат и возвращается назад. Этот порядок возврата важен: он же объясняет, где живут переменные внутри функции — разберём в памяти — и как программа возвращается из ошибки — разберём в обработке ошибок.
Область видимости
Переменные внутри функции живут отдельно от внешнего кода:
def can_request_payout(profile_verified, bank_account_connected, review_state, requested_amount, available_balance)
minimum_amount_passed = requested_amount >= 50
profile_verified &&
bank_account_connected &&
review_state != "hold" &&
minimum_amount_passed &&
available_balance >= requested_amount
end
can_request_payout(true, true, "clear", 120, 300)
puts minimum_amount_passed # ошибка: переменной здесь нетЭто защищает код от случайных пересечений. Читателю не нужно помнить все временные переменные всей программы сразу — достаточно знать параметры и результат конкретной функции.
Функция убирает дублирование логики. Но что делать, когда программа работает не с одним запросом на выплату, а с тридцатью? Каждый запрос — это набор данных, и пока единственный способ их хранить — отдельные переменные. Для хранения нескольких значений вместе нужны коллекции.
Sources
- Thomas, D. et al., 2023, Programming Ruby 3.3. Pragmatic Bookshelf.
- Abelson, H. & Sussman, G., 1996, Structure and Interpretation of Computer Programs. MIT Press.