Когда функция слишком длинная?

35 строк, 55 строк, 100 строк, 300 строк? Когда вы должны начать разлучать его? Я спрашиваю, потому что у меня есть функция с 60 строками (включая комментарии) и думал о ее разломе.

long_function(){ ... }

в

small_function_1(){...}
small_function_2(){...}
small_function_3(){...}

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

Когда вы разделите функцию на более мелкие? Почему?

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

Спасибо всем за ответы, отредактируйте список и проголосуйте за правильный ответ, я выберу его;)

Сейчас я рефакторинг с учетом этих идей:)

Ответ 1

Там нет реальных жестких правил. Обычно мне нравятся мои методы, чтобы просто "сделать одно". Поэтому, если он захватывает данные, а затем делает что-то с этими данными, а затем записывает их на диск, я бы разложил захват и запись на отдельные методы, поэтому мой "основной" метод просто содержит "что-то".

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

Изменить: Это единственная строка кода, которую я отправил по почте на прошлой неделе (чтобы доказать, что это не так.) Я не хочу, чтобы 50-60 из этих плохих парней в моем методе: D

return level4 != null ? GetResources().Where(r => (r.Level2 == (int)level2) && (r.Level3 == (int)level3) && (r.Level4 == (int)level4)).ToList() : level3 != null ? GetResources().Where(r => (r.Level2 == (int)level2) && (r.Level3 == (int)level3)).ToList() : level2 != null ? GetResources().Where(r => (r.Level2 == (int)level2)).ToList() : GetAllResourceList();

Ответ 2

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

  • Глубоко вложенные структуры управления. for-loop 3 уровня глубоко или даже всего 2 уровня с вложенными if-утверждениями, которые имеют сложные условия.

  • Слишком много определяющих состояние параметров: по определяющему состояние параметру я имею в виду параметр функции, который гарантирует конкретный путь выполнения через функцию. Получите слишком много таких параметров, и у вас есть комбинаторный взрыв путей выполнения (это обычно происходит в тандеме с №1).

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

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

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

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

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

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

Ответ 3

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

Ответ 4

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

Что вы действительно не хотите сделать, это скопировать и вставить каждые 10 строк вашей длинной функции в короткие функции (как показывает ваш пример).

Ответ 5

Я согласен, что функция должна делать только одно, но на каком уровне это одно.

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

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

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

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

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

Ответ 6

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

Почему я могу разбить функцию:

  • Слишком долго
  • Это делает код более пригодным для обслуживания, разбивая его и используя значащие имена для новой функции
  • Функция не является связной.
  • Части функции полезны сами по себе.
  • Когда трудно найти значащее имя для функции (возможно, это слишком много)

Ответ 7

Размер приблизительно у вас размер экрана (так что возьмите большой широкоугольный шарнир и поверните его)...: -)

Шутка в сторону, одна логическая вещь для каждой функции.

И положительно, что модульное тестирование действительно намного проще делать с небольшими логическими функциями, которые делают 1 вещь. Большие функции, которые выполняют многие вещи, сложнее проверить!

/Johan

Ответ 8

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

function build_address_list_for_zip($zip) {

    $query = "SELECT * FROM ADDRESS WHERE zip = $zip";
    $results = perform_query($query);
    $addresses = array();
    while ($address = fetch_query_result($results)) {
        $addresses[] = $address;
    }

    // now create a nice looking list of
    // addresses for the user
    return $html_content;
}

гораздо приятнее:

function fetch_addresses_for_zip($zip) {
    $query = "SELECT * FROM ADDRESS WHERE zip = $zip";
    $results = perform_query($query);
    $addresses = array();
    while ($address = fetch_query_result($results)) {
        $addresses[] = $address;
    }
    return $addresses;
}

function build_address_list_for_zip($zip) {

    $addresses = fetch_addresses_for_zip($zip);

    // now create a nice looking list of
    // addresses for the user
    return $html_content;
}

Этот подход имеет два преимущества:

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

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

[С другой стороны (я отрицаю, что я сказал вам это, даже под пыткой): если вы много читаете о оптимизации PHP, вы можете подумать о том, чтобы количество функций было как можно меньше, потому что вызов функции очень, очень дороги в PHP. Я не знаю об этом, так как я никогда не делал никаких тестов. Если это так, вы, вероятно, лучше не будете отвечать ни на один из ответов на ваш вопрос, если приложение очень "чувствительно к производительности";-)]

Ответ 9

Взгляните на циклограмму McCabe, в которой он разбивает свой код на график, где "Каждый node в графе соответствует блоку кода в программе, где поток последователен, а дуги соответствуют ветвям принятых в программе".

Теперь представьте, что ваш код не имеет функций/методов; его просто одно огромное разрастание кода в виде графика.

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

Чтобы определить, какой размер ваших методов должен быть с точки зрения количества блоков на один метод, вы можете задать один вопрос: сколько методов следует минимизировать максимально возможное количество зависимостей (MPE) между всеми блоками?

Этот ответ задается уравнением. Если r - количество методов, которые минимизируют MPE системы, а n - количество блоков в системе, то уравнение равно: r = sqrt (n)

И можно показать, что это дает количество блоков в каждом методе, а также sqrt (n).

Ответ 10

Моя личная эвристика заключается в том, что она слишком длинная, если я не вижу ничего без прокрутки.

Ответ 11

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

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

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

Ответ 12

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

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

Вы уверены, что в нем нет предметов, которые были бы полезны в других местах? Какая функция это?

Ответ 13

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

Ответ 14

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

Ответ 15

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

Если ваш тест достаточно сфокусирован, он заставит вас написать небольшую сфокусированную функцию, чтобы пройти тест.

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

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

Ответ 16

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

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

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

Ответ 17

Проделали некоторые тщательные исследования по этой теме, если вы хотите получить наименьшее количество ошибок, ваш код не должен быть слишком длинным. Но это также не должно быть слишком коротким.

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

См Оптимальный размер класса для Объектно-ориентированное программное обеспечение для дальнейшего обсуждения.

Ответ 18

Я написал 500 функций линии раньше, однако это были просто большие операторы switch для декодирования и ответа на сообщения. Когда код для одного сообщения стал более сложным, чем одно, если-then-else, я извлек его.

В сущности, хотя функция была 500 строк, независимо поддерживаемые регионы усредняли 5 строк.

Ответ 19

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

Каждый цикл for, оператор if и т.д. Не рассматривается как ветвь в вызывающем методе.

Код Cobertura for Java (и я уверен, что есть другие инструменты для других языков) вычисляет число if и т.д. В функции для каждой функции и суммирует ее для "средней цикломатической сложности".

Если функция/метод имеет только три ветки, он получит три по этой метрике, что очень хорошо.

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

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

Ответ 20

Я подозреваю, что вы найдете много ответов на это.

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

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

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

Ответ 21

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

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

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

Ответ 22

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

Ответ 23

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

Ответ 24

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