Создание вложенных функций по чисто эстетическим причинам?

16

Мне всегда было интересно, что другие программисты думают об идее создания чисто эстетических функций.

Скажем , у меня есть функция , которая обрабатывает кусок данных: Function ProcessBigData. Скажем , мне нужно несколько шагов процесса, действительны только для этих данных: Step1, Step2, Step3.

Обычный подход, который я вижу больше всего в исходном коде, - это писать комментарии примерно так:

Function ProcessBigData:
    # Does Step1
    Step1..
    Step1..

    #Does Step2
    Step2..
    Step2..

Что я обычно делаю, но всегда чувствовал себя неправильно из-за отсутствия такого стиля кодирования со стороны коллег:

Function ProcessBigData:
    Function Step1:
        Step1..
        Step1..

    Function Step2:
        Step2..
        Step2..

    Step1() -> Step2()

Я в основном обеспокоен, есть ли недостатки такого стиля в Javascript и Python

Есть ли альтернативы, которых я не вижу?

Slytael
источник
3
Я ничего не могу сказать о Python, но для Javascript есть затраты производительности для вложенных функций: большинство движков JavaScript используют структуру, похожую на связанный список, чтобы представить область переменных. Таким образом, добавление дополнительного уровня функций заставляет механизм при поиске переменных искать более длинную / большую структуру данных. С другой стороны, корень всего зла, конечно же, преждевременная оптимизация. :)
Марко

Ответы:

4

Это не так странно, как вы думаете. Например, в Standard ML принято ограничивать область действия вспомогательных функций. Конечно, SML имеет синтаксис для облегчения этого:

local
    fun recursion_helper (iteration_variable, accumulator) =
        ... (* implementation goes here *)
in
    fun recursive_function (arg) = recursion_helper(arg, 0);
end

Я хотел бы рассмотреть этот хороший стиль, учитывая, что 1) маленькие функции облегчают рассуждения о программе, и 2) он сигнализирует читателю, что эти функции не используются вне этой области.

Я предполагаю, что возможно создание некоторых внутренних функций при каждом вызове внешней функции (я не знаю, оптимизируют ли это JS или Python), но вы знаете, что они говорят о преждевременной оптимизации.

Doval
источник
11

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

Подзадача - это конкретная единица работы, которая может быть выполнена: она несет определенную ответственность, а также определенные входные данные и выходные данные (подумайте о «S» в SOLID ). Подзадача не нуждается в повторном использовании: некоторые люди склонны думать: «Мне никогда не придется вызывать это из чего-либо еще, так зачем писать это как функцию?» но это заблуждение

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

Читаемость.

Более 200 строк процедурного кода (тела функции) трудно читать. Функции 2-20 строк легко читаются. Код для людей.

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

Предел переменной области

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

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

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

Модульные тесты

Каждая подзадача, реализованная функцией (или даже классом), является отдельным, тестируемым фрагментом кода. Преимущества модульного тестирования и TDD хорошо документированы в других местах.

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

Работа в команде / Дизайн сверху вниз

Подзадачи могут быть написаны разными людьми, независимо, при необходимости.

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

Повторное использование кода

Хорошо, так что, несмотря на то, что я сказал ранее, иногда на самом деле в конечном итоге возникает причина для повторного использования подзадачи для чего-то другого. Я вовсе не сторонник "архитектурного астронавта", а просто потому, что при написании слабосвязанного кода вы можете в конечном итоге получить выгоду от повторного использования.

Часто такое повторное использование означает некоторый рефакторинг, что вполне ожидаемо, но рефакторинг входных параметров для небольшой автономной функции НАМНОГО проще, чем извлечение ее из 200+ строковых функций через месяцы после ее написания, что на самом деле является моей точкой зрения здесь.

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

gregmac
источник
2
Вот несколько действительно важных моментов для использования функций в целом, но я не понял вашего ответа, если вы считаете, что NESTED-функции - хорошая идея. Или вы выводите функции из вышестоящей области?
Слайтаэль
Извините, хороший момент, я попал в другие преимущества, которые я забыл рассмотреть в этой части. :) Отредактировано.
gregmac