Передача функций в другие функции в качестве параметров, плохая практика?

40

Мы находимся в процессе изменения того, как наше приложение AS3 взаимодействует с нашим бэкэндом, и мы находимся в процессе внедрения системы REST для замены нашей старой.

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

Это вызывает у меня «плохое предчувствие», когда я чувствую, что это ужасная практика, и я могу подумать о некоторых причинах, почему, но мне нужно подтверждение, прежде чем я предложу переработать систему. Мне было интересно, есть ли у кого-нибудь опыт с этой возможной проблемой?

Эллиот Блэкберн
источник
22
Почему ты так чувствуешь? Есть ли у вас опыт работы с функциональным программированием? (Я предполагаю, нет, но вы должны взглянуть)
Фоши
8
Тогда считай это уроком! То, что преподают академические круги и что на самом деле полезно, часто удивительно мало совпадают. Существует целый мир методов программирования, которые не соответствуют такой догме.
Фоши
32
Передача функций другим функциям является настолько фундаментальным понятием, что, когда язык не поддерживает его, люди будут стараться изо всех сил создавать тривиальные «объекты», единственной целью которых является содержание рассматриваемой функции в качестве временного промежутка.
Довал
36
В свое время мы называли эти сквозные функции «обратными вызовами»
Майк
11
@BlueHat прочитайте это en.wikipedia.org/wiki/Callback_(computer_programming)
Майк

Ответы:

84

Это не проблема.

Это известная техника. Это функции высшего порядка (функции, которые принимают функции в качестве параметров).

Этот тип функций также является основным строительным блоком в функциональном программировании и широко используется в функциональных языках, таких как Haskell .

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

Одед
источник
Спасибо за это, у меня нет реального опыта работы с функциональным программированием, поэтому я посмотрю на него. Это просто не очень хорошо, потому что, насколько я знаю, когда вы объявляете что-то как частную функцию, доступ к ней должен иметь только тот класс, а публичные должны вызываться со ссылкой на объект. Я посмотрю на это должным образом, и я ожидаю, что приду оценивать это немного больше!
Эллиот Блэкберн
3
Чтение про продолжения , стиль прохождения продолжения , монады (функциональное программирование) также должно быть полезным.
Василий Старынкевич,
8
@BlueHat вы заявляете что - то , чтобы быть частным , если вы хотите контроль над тем, как оно используется, если это использование в качестве обратного вызова для конкретных функций то это хорошо
храповой урод
5
В точку. Отметив это как частное, вы не хотите, чтобы кто-то другой заходил и получал к нему доступ в любое время, когда им будет угодно. Передача приватной функции кому-то еще, потому что вы специально хотите, чтобы они вызывали ее так, как они документируют этот параметр, это нормально. Это не должно отличаться от передачи значения частного поля в качестве параметра какой-либо другой функции, которая, по-видимому, неплохо сочетается с вами :-)
Стив Джессоп
2
Стоит отметить, что если вы пишете конкретные классы с функциями в качестве параметров конструктора, вы, вероятно, делаете это неправильно, тм.
Гусдор
30

Они не просто используются для функционального программирования. Их также можно назвать обратными вызовами :

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

Подумайте об асинхронном коде на секунду. Вы передаете функцию, которая, например, отправляет данные пользователю. Только после завершения кода вы вызываете эту функцию с результатом ответа, который затем используется функцией для отправки данных обратно пользователю. Это изменение мышления.

Я написал библиотеку, которая получает данные Torrent из вашей семенной коробки. Вы используете неблокирующий цикл событий, чтобы выполнить эту библиотеку и получить данные, а затем вернуть их пользователю (скажем, в контексте веб-сокета). Представьте, что у вас есть 5 человек, связанных в этом цикле событий, и один из запросов на получение чьих-то торрент-данных. Это заблокирует весь цикл. Поэтому вам нужно думать асинхронно и использовать обратные вызовы - цикл продолжает работать, а «передача данных обратно пользователю» запускается только после того, как функция закончила выполнение, так что ждать его не приходится. Огонь и забудь.

Джеймс
источник
1
+1 За использование терминологии обратного вызова. Я встречаю этот термин гораздо чаще, чем «функции более высокого порядка».
Родни Шулер
Мне нравится этот ответ, потому что OP, казалось, описывал, в частности, обратные вызовы, а не только функции более высокого порядка: «Например, наш класс, который выполняет вызов наших серверов, принимает функцию, которую он затем вызывает и передает объект когда процесс завершится и ошибки будут обработаны и т. д. "
Ajedi32
11

Это неплохая вещь. На самом деле это очень хорошая вещь.

Передача функций в функции настолько важна для программирования, что мы изобрели лямбда-функции как сокращение. Например, можно использовать лямбда-выражения с алгоритмами C ++ для написания очень компактного, но выразительного кода, который позволяет универсальному алгоритму использовать локальные переменные и другие состояния для таких вещей, как поиск и сортировка.

Объектно-ориентированные библиотеки также могут иметь обратные вызовы, которые по сути являются интерфейсами, задающими небольшое количество функций (в идеале одну, но не всегда). Затем можно создать простой класс, который реализует этот интерфейс, и передать объект этого класса в функцию. Это краеугольный камень в программировании , управляемом событиями , когда код уровня платформы (возможно, даже в другом потоке) должен вызывать объект для изменения состояния в ответ на действие пользователя. Java- интерфейс ActionListener является хорошим примером этого.

Технически, функтор C ++ также является типом объекта обратного вызова, который использует синтаксический сахар operator()(), чтобы сделать то же самое.

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

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


источник
Кроме того, в C # делегаты и лямбды широко используются.
Пирог злой собаки
6

Как уже было сказано, это неплохая практика. Это просто способ отделить и разделить ответственность. Например, в ООП вы должны сделать что-то вроде этого:

public void doSomethingGeneric(ISpecifier specifier) {
    //do generic stuff
    specifier.doSomethingSpecific();
    //do some other generic stuff
}

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

Филипп Мерри
источник
1
Эта идиома просто заключает функцию в интерфейс, выполняя нечто очень похожее на передачу необработанной функции.
Да, я просто хотел показать, что это не редкая практика.
Филипп Мюрри
3

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

Есть несколько потенциальных недостатков простых обратных вызовов:

  • Выполнение серии вызовов может потребовать глубокого вложения обратных вызовов.
  • Для обработки ошибок может потребоваться повторение каждого вызова в последовательности вызовов.
  • Координация нескольких вызовов неудобна, как одновременное выполнение нескольких вызовов и выполнение каких-либо действий после их завершения.
  • Там нет общего способа отменить набор звонков.

С простыми веб-сервисами способ, которым вы это делаете, работает нормально, но становится неловко, если вам нужна более сложная последовательность вызовов. Хотя есть несколько альтернатив. Например, в JavaScript произошел сдвиг в сторону использования обещаний ( что такого хорошего в обещаниях javascript ).

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

ФГБ
источник
Я также добавил бы, что ненужное использование обратных вызовов может усложнить отслеживание потока выполнения для любого, кто читает код. Явный вызов легче понять, чем обратный вызов, установленный в отдаленной части кода. (Точки останова для спасения!) Тем не менее, именно так происходят события в браузере и других системах, основанных на событиях; затем нам нужно понять стратегию источника событий, чтобы знать, когда и в каком порядке будут вызываться обработчики событий.
Joeytwiddle