Допустимы ли длинные функции, если они имеют внутреннюю структуру?


23

При работе со сложными алгоритмами в языках с поддержкой вложенных функций (таких как Python и D) я часто пишу огромные функции (потому что алгоритм сложный), но смягчаю это, используя вложенные функции для структурирования сложного кода. Огромные (более 100 строк) функции по-прежнему считаются злыми, даже если они хорошо структурированы внутри благодаря использованию вложенных функций?

Редактировать: Для тех из вас, кто не знаком с Python или D, вложенные функции в этих языках также разрешают доступ к внешней области функций. В D этот доступ допускает мутацию переменных во внешней области видимости. В Python разрешено только чтение. В D вы можете явно отключить доступ к внешней области во вложенной функции, объявив ее static.


5
Я регулярно пишу более 400 линейных функций / методов. Должен где-то поместить это заявление о переключении дел в 500 экземпляров :)
Каллум Роджерс

7
@Callum Rogers: В Python вместо 500 операторов переключения регистра вы бы использовали словарь поиска / отображение. Я полагаю, что они превосходят то, что имеют примерно 500-ти или около того заявления о замене регистра. Вам все еще нужно 500 строк определений словаря (альтернативно, в Python вы можете использовать отражение для их динамического перечисления), но определение словаря - это данные, а не код; и гораздо меньше проблем с большим определением данных, чем с большим определением функции. Кроме того, данные более надежны, чем код.
Ли Райан

Каждый раз, когда я вижу функции с большим количеством LOC, я смущаюсь, заставляю себя задуматься, какова цель модульности. Проще следовать логике и отлаживать с меньшими функциями
Aditya P

Древний и знакомый язык Pascal также разрешает доступ к внешней области, как и расширение вложенных функций в GNU C. (Однако эти языки допускают только нисходящие функции, то есть аргументы, которые являются функциями, которые содержат область действия, могут быть только передается и не возвращается, что потребует полной поддержки лексического замыкания).
Каз

Хорошим примером функций, которые имеют тенденцию быть длинными, являются комбинаторы, которые вы пишете при выполнении реактивного программирования. Они легко достигают тридцати строк, но удвоятся в размере, если разделятся, потому что вы потеряете замыкания.
Крейг Гидни,

Ответы:


21

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

Это мешает читабельности и тестированию.


9
Я всегда ненавидел это правило, потому что функция, которая делает «одну вещь» при просмотре на высоком уровне абстракции, может делать «много вещей» при просмотре на более низком уровне. При неправильном применении правило «одно» может привести к чрезмерно детализированным API.
2010 года

1
@dsimcha: Какое правило не может быть неправильно использовано и привести к проблемам? Когда кто-то применяет «правила» / банальности выше точки, где они полезны, просто выведите другое: все обобщения неверны.

2
@ Роджер: законная жалоба, за исключением того, что ИМХО правило часто неправильно применяется на практике, чтобы оправдать чрезмерно мелкозернистые, чрезмерно развитые API. Это связано с конкретными затратами в том, что API становится все труднее и более подробным для использования в простых, общих случаях использования.
dsimcha

2
«Да, правило неправильно применяется, но правило применяется слишком часто!»? : P

1
Моя функция делает одну вещь, и она делает эту вещь очень хорошо: а именно, занимают 27 экранов. :)
Каз

12

Некоторые утверждают, что короткие функции могут быть более подвержены ошибкам, чем длинные функции .

Кард и Гласс (1990) указывают, что сложность дизайна действительно включает в себя два аспекта: сложность внутри каждого компонента и сложность взаимоотношений между компонентами.

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

Я думаю, что главный вывод состоит в том, что когда вы разделяете блок кода, вы обмениваете один вид сложности на другой. Где-то посередине, вероятно, есть сладкое пятно.


Вы читали «Чистый код»? Это обсуждает это подробно. amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/…
Мартин Уикман

9

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

Я знаю, что как только я нажимаю Page Up / Down или перехожу к другому разделу кода, я могу вспомнить только 7 +/- 2 вещи с предыдущей страницы. И, к сожалению, некоторые из этих мест будут использоваться при чтении нового кода.

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


3
Вам просто нужно распечатать его на матричном принтере - видите, 10 метров кода за раз :)

Или получите монитор с более высоким разрешением
frogstarr78

И используйте его в вертикальной ориентации.
Кальмариус

4

Зачем использовать вложенные функции, а не обычные внешние функции?

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

DoThing(int x){
    x += ;1
    int y = FirstThing(x);
    x = SecondThing(x, y);
    while(spoo > fleem){
        x = ThirdThing(x+y);
    }
}

FirstThing(int x){...}
SecondThing(int x, int y){...}
ThirdThing(int z){...}

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

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

3

В данный момент у меня нет книги передо мной (чтобы процитировать), но согласно Code Complete «сладость» для длины функции составляла около 25-50 строк кода согласно его исследованию.

Есть моменты, когда нормально иметь длинные функции:

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

Время, когда нельзя иметь длинные функции:

  • У вас есть функция с глубоко вложенными условными выражениями. Сделайте одолжение читателям своего кода, улучшите читабельность, разбив функцию. Функция предоставляет своему читателю указание, что «Это блок кода, который делает одну вещь». Также спросите себя, указывает ли длина функции на то, что она выполняет слишком много задач и должна быть передана другому классу.

Суть в том, что ремонтопригодность должна быть одним из самых высоких приоритетов в вашем списке. Если другой разработчик не может взглянуть на ваш код и получить «суть» того, что код делает менее чем за 5 секунд, ваш код не предоставляет достаточно «метаданных», чтобы сказать, что он делает. Другие разработчики должны быть в состоянии сказать, что делает ваш класс, просто взглянув на браузер объектов в выбранной вами IDE вместо того, чтобы читать более 100 строк кода.

Меньшие функции имеют следующие преимущества:

  • Переносимость: намного проще перемещать функциональность (либо внутри класса по рефакторингу на другой)
  • Отладка: когда вы смотрите на трассировку стека, гораздо быстрее определить ошибку, если вы смотрите на функцию с 25 строками кода, а не с 100.
  • Удобочитаемость - имя функции говорит о том, что делает весь блок кода. Разработчик в вашей команде может не захотеть читать этот блок, если он не работает с ним. Кроме того, в большинстве современных IDE другой разработчик может лучше понять, что делает ваш класс, читая имена функций в браузере объектов.
  • Навигация. Большинство IDE позволяют выполнять поиск по названию функций. Кроме того, большинство современных IDE имеют возможность просматривать источник функции в другом окне, что дает другим разработчикам возможность просматривать вашу длинную функцию на 2 экранах (если это несколько мониторов) вместо того, чтобы заставлять их прокручиваться.

Список можно продолжить .....


2

Ответ - это зависит, однако вы, вероятно, должны превратить это в класс.


Если вы не пишете в OOPL, в этом случае, возможно, нет :)
Фрэнк Шиарар

Dsimcha упомянул, что они используют D и Python.
frogstarr78

Занятия слишком часты для людей, которые никогда не слышали о таких вещах, как лексические замыкания.
Каз

2

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

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

Я считаю, что функция должна делать свое дело. Делать другие вещи - это то, что делают другие функции. Так что, если у вас есть функция из 200 строк, которая делает свое дело, и все течет, это нормально.


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

1

Это приемлемо? Это действительно вопрос, на который только вы можете ответить. Достигает ли функция того, что ей нужно? Это ремонтопригодно? Это «приемлемо» для других членов вашей команды? Если так, то это то, что действительно имеет значение.

Редактировать: я не видел вещь о вложенных функциях. Лично я бы их не использовал. Я бы использовал обычные функции вместо этого.


1

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

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

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

В некоторых языках эту проблему легко предотвратить: локальный фрагмент кода можно обернуть в свой собственный блок, например, с помощью "{...}", внутри которого любые вновь введенные переменные видны только этому блоку. В некоторых языках, таких как Python, эта функция отсутствует, и в этом случае локальные функции могут быть полезны для реализации областей меньшего масштаба.


1

Нет, многостраничные функции нежелательны и не должны проходить проверку кода. Я тоже писал длинные функции, но после прочтения Рефакторинга Мартина Фаулера я остановился. Длинные функции сложно написать правильно, трудно понять и сложно протестировать. Я никогда не видел функцию даже из 50 строк, которую было бы проще понять и протестировать, если бы она была разбита на набор более мелких функций. В многостраничной функции почти наверняка есть целые классы, которые должны быть выделены. Трудно быть более конкретным. Возможно, вам следует опубликовать одну из ваших длинных функций в Code Review, и кто-то (может быть, я) может показать вам, как ее улучшить.


0

Когда я программирую на python, мне нравится отступать после написания функции и спрашивать себя, соответствует ли она «Zen of Python» (введите «import this» в вашем интерпретаторе python):

Красиво лучше, чем безобразно.
Явное лучше, чем неявное.
Простое лучше, чем сложное.
Сложный лучше, чем сложный.
Квартира лучше, чем вложенная.
Разреженный лучше, чем плотный.
Читаемость имеет значение.
Особые случаи не достаточно особенные, чтобы нарушать правила.
Хотя практичность превосходит чистоту.
Ошибки никогда не должны проходить бесшумно.
Если явно не молчать.
Перед лицом двусмысленности откажитесь от соблазна гадать.
Должен быть один - и желательно только один - очевидный способ сделать это.
Хотя этот путь поначалу может быть неочевидным, если вы не голландец.
Сейчас лучше, чем никогда.
Хотя никогда не бывает лучше, чем правильнов настоящее время.
Если реализацию сложно объяснить, это плохая идея.
Если реализацию легко объяснить, это может быть хорошей идеей.
Пространства имен - одна из отличных идей - давайте сделаем больше!


1
Если явное лучше, чем неявное, мы должны кодировать на машинном языке. Даже не на ассемблере; он делает неприятные неявные вещи, такие как автоматическое заполнение интервалов задержки ветвления инструкциями, вычисление относительных смещений ветвления и разрешение символических ссылок между глобальными переменными. Чувак, эти тупые пользователи Python и их маленькая религия ...
Kaz

0

Поместите их в отдельный модуль.

Предполагая, что ваше решение не более раздуто, чем необходимость, у вас не слишком много вариантов. Вы уже разделили функции на разные подфункции, поэтому вопрос в том, где вы должны их поместить:

  1. Поместите их в модуль только с одной «публичной» функцией.
  2. Поместите их в класс только с одной «публичной» (статической) функцией.
  3. Вложите их в функцию (как вы описали).

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.