Какова идеальная длина метода для вас? [закрыто]

122

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

В « Чистом коде: справочник по мастерству гибкого программного обеспечения» Роберт Мартин говорит:

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

и он приводит пример из кода Java, который он видит из Кента Бека:

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

Это звучит великолепно, но с другой стороны, в Code Complete Стив Макконнелл говорит что-то совсем другое:

Рутине нужно дать возможность органически расти до 100-200 строк, десятилетия доказательств говорят, что процедуры такой длины не более подвержены ошибкам, чем более короткие процедуры.

И он дает ссылку на исследование, в котором говорится, что рутины длиной 65 строк или более дешевые в разработке.

Так что, хотя существуют различные мнения по этому вопросу, есть ли функциональная передовая практика для вас?

iPhoneDeveloper
источник
17
Функции должны быть просты для понимания. Длина должна следовать из этого, в зависимости от обстоятельств.
Хенк Холтерман
56
Я думаю, что реальный лимит составляет 53 строки. При среднем размере строки 32,4 символа. Серьезно, нет однозначного ответа. Метод из 100 строк может быть очень понятным и понятным, а метод из 4 строк может оказаться кошмаром для понимания. Однако, как правило, длинные методы, как правило, имеют слишком много обязанностей, и их труднее понять и поддерживать, чем более мелкие. Я бы подумал с точки зрения ответственности и попытался бы иметь одну ответственность за метод.
23
В программировании есть термин «функциональная согласованность». Длина функции должна изменяться при условии, что ее реализация по-прежнему представляет собой единое логическое звено в вашем приложении. Произвольное разделение функций для уменьшения их размера с большей вероятностью приведет к расширению кода и ухудшит удобство обслуживания.
9
И, если вы хотите ограничить сложность своих функций, вы должны измерить их цикломатическую сложность , а не их длину. switchЗаявление с 100 caseусловиями становится более сопровождаемым , чем 10 уровней ifзаявлений , вложенных в друг друга.
10
Подход Боба Мартина - с 2008 года, Стив Мак Коннелл - с 1993 года. У них разные взгляды на то, что такое «хороший код», и ИМХО Боб Мартин пытается достичь гораздо более высокого уровня качества кода.
Док Браун

Ответы:

115

Функции обычно должны быть короткими, от 5 до 15 строк - мое личное «правило» при кодировании на Java или C #. Это хороший размер по нескольким причинам:

  • Он легко помещается на экране без прокрутки
  • Речь идет о концептуальном размере, который вы можете держать в голове
  • Это достаточно значимо, чтобы требовать функцию самостоятельно (как отдельный, значимый кусок логики)
  • Функция меньше 5 строк - это намек на то, что вы, возможно, слишком сильно разбиваете код (что затрудняет чтение / понимание, если вам нужно перемещаться между функциями). Либо это, либо вы забываете свои особые случаи / обработку ошибок!

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

  • Однострочная функция доступа, которая выполняет приведение типа, в некоторых ситуациях вполне приемлема.
  • Есть несколько очень коротких, но полезных функций (например, подкачка, как упомянуто пользователем неизвестно), которые явно требуют менее 5 строк. Ничего страшного, несколько трехстрочных функций не наносят вреда вашей кодовой базе.
  • 100-строчная функция, представляющая собой один большой оператор switch, может быть приемлемой, если предельно ясно, что делается. Этот код может быть концептуально очень простым, даже если для описания различных случаев требуется много строк. Иногда предлагается, чтобы это было реорганизовано в отдельные классы и реализовано с использованием наследования / полиморфизма, но ИМХО это слишком далеко заходит ООП - я бы предпочел иметь только один большой 40-позиционный оператор переключения, а не 40 новых классов, с которыми нужно иметь дело, кроме того, на 40-позиционный переключатель, чтобы создать их.
  • Сложная функция может иметь много переменных состояния, которые могут стать очень грязными, если будут передаваться между различными функциями в качестве параметров. В этом случае вы могли бы обоснованно аргументировать, что код проще и легче следовать, если вы храните все в одной большой функции (хотя, как справедливо отмечает Марк, это также может быть кандидатом на превращение в класс для инкапсуляции логики). и государство).
  • Иногда меньшие или большие функции имеют преимущества в производительности (возможно, из-за встраивания или по причинам JIT, как упоминает Фрэнк). Это сильно зависит от реализации, но это может иметь значение - убедитесь, что вы отметили!

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

mikera
источник
9
Для коротких методов есть и техническая причина: попадания в JIT-кеш. Многие меньшие, многократно используемые методы, скорее всего, были вызваны раньше. Да, и дополнительное преимущество Diag, StackTraces больше сосредоточено на логике, которая стала популярной.
Люк Пуплетт
20
«Используйте здравый смысл» - самый важный совет
Симон
Следует помнить, что эта эвристика приведет к жалобам на «код равиоли» из-за глубины стека в точках останова при отладке.
Фрэнк Хайлеман,
5
@FrankHileman Я буду принимать равиоли за спагетти в любой день при написании кода
1
@ Снеговик: эти выборы не являются взаимоисключающими ... короткие глубины стека без спагетти является идеальным. Глубокая стопка, со спагетти на стороне?
Фрэнк Хилман
30

Хотя я согласен с комментариями других, когда они сказали, что нет строгого правила относительно правильного номера LOC, я держу пари, если мы оглянемся назад на проекты, которые мы рассматривали в прошлом, и определим каждую функцию выше, скажем, 150 строк кода, я Я предполагаю, что мы придем к общему мнению, что 9 из 10 этих функций нарушают SRP (и, скорее всего, также OCP), имеют слишком много локальных переменных, слишком большой поток управления и, как правило, их трудно читать и поддерживать.

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

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

DXM
источник
3
Это кажется разумным - LOC ничего не значит в отношении сложности или качества кода, но он может быть хорошим маркером вероятности того, что все должно быть реорганизовано.
Кори
4
«прийти к общему мнению, что 9 из 10 из этих функций нарушают SRP» - я не согласен, я уверен, что 10 из 10 из этих функций нарушат его ;-)
Док Браун
1
+1 для поднятия флага во время проверки кода: другими словами, не жесткое и быстрое правило, но давайте обсудим этот код как группу.
22

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

Общее правило о том, насколько большим должен быть метод, иногда не очень хорошо. Мне нравится правило Роберта К. Мартина (дядя Боб): «Методы должны быть маленькими, меньше маленьких». Я стараюсь использовать это правило все время. Я пытаюсь сделать мои методы простыми и маленькими, разъясняя, что мой метод делает только одно и больше ничего.

Smokefoot
источник
4
Я не понимаю, почему функцию из 6000 строк сложнее отладить, чем более короткую ... Если только у нее нет других проблем (например, повторение)
Calmarius
17
Это не имеет ничего общего с отладкой ... речь идет о сложности и ремонтопригодности. Как бы вы расширили или изменили метод 6000 строк, сделанный другим?
Smokefoot
10
@Calmarius, разница обычно в том, что 6000 строковых функций, как правило, содержат локальные переменные, которые были объявлены очень далеко (визуально), что затрудняет программисту создание ментального контекста, необходимого для высокого доверия к коду. Можете ли вы быть уверены в том, как переменная инициализируется и создается в любой заданной точке? Вы уверены, что ничто не помешает вашей переменной после того, как вы установили ее в строке 3879? С другой стороны, с 15 строковыми методами вы можете быть уверены.
Даниэль Б
8
@Calmarius согласился, но оба эти утверждения являются аргументом против 6000 функций LOC.
Даниэль Б,
2
Локальная переменная в 600-строчном методе по сути является глобальной переменной.
MK01
10

Дело не в количестве строк, а в SRP. Согласно этому принципу, ваш метод должен делать одно и только одно.

Если ваш метод делает это И это И это ИЛИ это => это, вероятно, делает много. Попробуйте взглянуть на этот метод и проанализировать: «здесь я получаю эти данные, сортирую их и получаю нужные мне элементы» и «здесь я обрабатываю эти элементы» и «здесь я, наконец, объединяю их, чтобы получить результат». Эти «блоки» должны быть реорганизованы для других методов.

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

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

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

Алексей
источник
2
Большие операторы switch, форматирование вывода, определения хэшей / словарей, которые должны быть жестко закодированы, а не гибки в некоторых базах данных, это часто происходит, и это прекрасно. Пока логика разделена, значит, у вас все хорошо. Большой метод может побудить вас подумать «должен ли я разделить это». Ответ вполне может быть «нет, это нормально, как есть» (или да, это абсолютный беспорядок)
Мартейн
Что значит "SRP"?
августа
3
SRP выступает за принцип единой ответственности и гласит, что каждый класс (или метод) должен иметь только одну ответственность. Это связано со сплоченностью и сцеплением. Если вы будете следовать SRP, ваши классы (или методы) будут иметь хорошую сплоченность, но связь может быть увеличена, потому что в итоге вы получите больше классов (или методов).
Кристиан Дуске
+1 за СРП. Написав связные функции, можно легко объединить их в функциональном стиле для достижения более сложных результатов. В конце концов, лучше, чтобы функция была составлена ​​из трех других функций, которые были склеены вместе, чем иметь одну функцию, выполняющую три отдельных, даже как-то связанных, вещи.
Марио Т. Ланца
Да, но какова единственная ответственность. Это просто концепция, созданная в вашей голове. Если вам нужно 400 строк для одной ответственности, ваша концепция единой ответственности, вероятно,
сильно
9

Серьезно, это зависит от того , действительно ли нет точного ответа на этот вопрос, потому что язык, с которым вы работаете, имеет значение, пять-пятнадцатая строки, упомянутые в этом ответе, могут работать для C # или Java, но в других языках это не дает с вами много работать. Аналогично, в зависимости от домена, в котором вы работаете, вы можете обнаружить, что задаете значения параметров кода в большой структуре данных. С некоторыми структурами данных у вас могут быть десятки элементов, которые вам нужно установить, стоит ли разбивать их на отдельные функции только потому, что ваша функция выполняется долго?

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

rjzii
источник
8

Я думаю, что одна проблема здесь в том, что длина функции ничего не говорит о ее сложности. LOC (Lines of Code) - плохой инструмент для измерения чего-либо.

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

например, обработчик для входящих данных может иметь большой оператор switch и затем простой код для каждого случая. У меня есть такой код - управление входящими данными из ленты. 70 (!) Численно закодированных обработчиков. Теперь можно сказать «использовать константы» - да, за исключением того, что API не предоставляет их, и мне нравится оставаться здесь рядом с «источником». Методы? Конечно, к сожалению, все они имеют дело с данными из тех же двух огромных структур. Нет смысла разделять их, кроме, возможно, наличия большего количества методов (удобочитаемость). Код по сути не сложен - один переключатель, в зависимости от поля. Затем в каждом случае есть блок, который анализирует x элементов данных и публикует их. Нет обслуживания кошмар. Существует одно повторяющееся условие «если», которое определяет, есть ли у поля данные (pField = pFields [x], если pField-> IsSet () {blabla}) - то же самое в значительной степени для каждого поля.

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

Так что, извините, LOC не очень хорошее измерение для начала. Если что-то, то должны быть использованы точки сложности / решения.

TomTom
источник
1
LOC - это прекрасный инструмент для использования в одной области, где они обеспечивают соответствующую меру - очень большие проекты, где они могут использоваться, чтобы помочь оценить, сколько времени может потребоваться для завершения подобного проекта. Кроме того, люди склонны беспокоиться о них слишком сильно.
rjzii
Правильно. Это не похоже на то, как LOC иногда не зависит от того, насколько выразительно я пишу код, от форматирования требований и т. Д. LOC совершенно не подходит и что-то такое, что MBA не использует. Только. Вы можете внести себя в список людей, не понимающих, почему LOC является плохим измерением, но ясно, что это не заставит вас выглядеть как кого-то, кто будет слушать.
TomTom
Пожалуйста, ознакомьтесь с тем, что я сказал еще раз, я отметил, что LOC - хороший инструмент только для одной области измерения и использования (то есть чрезвычайно большие проекты, где они могут использоваться для оценки). Все, что меньше крупномасштабного, и они используют большинство, если не всю ценность чего-либо, кроме быстрых звуковых фрагментов на собрании, чтобы сделать людей счастливыми. Они вроде как пытаются использовать световые годы, чтобы измерить, насколько справедливо кафе в офисе, конечно, вы можете это сделать, но измерение бесполезно. Но когда вам нужно обсудить расстояния между звездами, они прекрасно работают.
rjzii
2
+1 функция должна иметь весь код, необходимый для ее выполнения. Он должен делать одно и только одно - но если для этого требуется 1000 строк кода, пусть будет так.
Джеймс Андерсон
Я написал обработчики для входящих данных сокетов, и да, они могут потребовать тысячи или более LOC. Тем не менее, я могу пересчитать по пальцам одной руки, сколько раз я , необходимых , чтобы сделать это и не может подсчитать количество раз это было не соответствующий способ кодирования.
7

Я просто добавлю еще одну цитату.

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

- Гарольд Абельсон

Очень маловероятно, что функции, возрастающие до 100-200, следуют этому правилу

Пит
источник
1
За исключением случаев, когда они содержат переключатель.
Кальмарий
1
или создать объект на основе результатов запроса к базе данных, который возвращает десятки полей на строку в наборе результатов ...
jwenting
Результаты базы данных, безусловно, являются приемлемым исключением - плюс они, как правило, являются «глупыми» утверждениями, которые заполняют некоторый экземпляр класса (или что-то еще), а не логикой, чем нужно следовать.
MetalMikester
6

Я был в этой безумной ракетке, так или иначе, с 1970 года.

За все это время, за двумя исключениями, которые я получу через мгновение, я НИКОГДА не видел хорошо разработанную «подпрограмму» (метод, процедуру, функцию, подпрограмму и т. Д.), Которой НУЖНО быть более одной напечатанной страницы ( около 60 строк) длиной. Подавляющее большинство из них были довольно короткими, порядка 10-20 строк.

Я, однако, видел МНОГО кода «потока сознания», написанного людьми, которые, очевидно, никогда не слышали о модульности.

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

Другой была рутина фотонной торпеды из игры STARTRK Матушека-Рейнольдса-МакГирти-Коэна, написанная на CDC 6600 FORTRAN IV. Он должен был проанализировать командную строку, затем смоделировать полет каждой торпеды, с возмущениями, проверить взаимодействие между торпедой и каждой вещью, на которую он может поразить, и, между прочим, имитировать рекурсию, чтобы сделать соединение с 8 путями на цепях новые от торпедирования звезды, которая была рядом с другими звездами.

Джон Р. Штром
источник
2
+1 за вибрацию "Сойди с моего газона" я получаю из этого ответа. Кроме того, из личного опыта до ООП языки были широко распространены.
Это не столько "сорваться с моего газона", сколько наблюдение, что я видел много дерьмового кода за эти годы, и кажется, что он становится ХУДОЖЕСТВЕННЫМ.
Джон Р. Штром
У моего босса есть привычка писать методы длиной в несколько сотен строк, часто с несколькими уровнями вложенных if. Он также использует частичные классы (.NET), чтобы «разбить» класс на несколько файлов, чтобы он мог утверждать, что он держит их короткими. Это только две вещи, с которыми мне приходится иметь дело. Занимаюсь этим около 25 лет, и я могу подтвердить, что дела ухудшаются. А теперь время для меня, чтобы вернуться в этот беспорядок.
MetalMikester
5

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

Сергей Шевчик
источник
2

Не существует абсолютных правил относительно длины метода, но были полезны следующие правила:

  1. Основная цель функции - найти возвращаемое значение. Нет другой причины его существования. Как только эта причина заполнена, никакой другой код не должен быть вставлен в нее. Это обязательно сохраняет функции маленькими. Вызов других функций должен выполняться только в том случае, если это облегчает поиск возвращаемого значения.
  2. С другой стороны, интерфейсы должны быть маленькими. Это означает, что у вас либо большое количество классов, либо у вас большие функции - одна из двух произойдет, как только вы начнете иметь достаточно кода, чтобы сделать что-то существенное. Большие программы могут иметь и то и другое.
ТР1
источник
1
Как насчет побочных эффектов - записи в файл, сброса статуса и т. Д.?
Vorac
2

Авторы подразумевают одно и то же под «функцией» и «рутиной»? Обычно, когда я говорю «функция», я имею в виду подпрограмму / операцию, которая возвращает значение и «процедуру» для той, которая не имеет (и чей вызов становится единым оператором). Это не распространенное различие в SE в реальном мире, но я видел это в текстах пользователей.

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

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

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

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

Трикси Вольф
источник
1

ИМХО, вам не нужно использовать полосу прокрутки, чтобы прочитать свою функцию. Как только вам нужно переместить полосу прокрутки, потребуется еще немного времени, чтобы понять, как работает функция.

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

Вкратце, это зависит от настроек ваших коллег.

Жером Пуйлер
источник
2
Разве это не было 24 строки в то время? Я имею в виду терминалы 3270 или 9750, где 25-й был статусной линией. И эмуляция терминала последовала за этим.
ot--
в некоторых системах / редакторах изначально было 40 или 50 строк. В наши дни 150 строк - это не редкость, а 200+ выполнимо, так что это не очень хороший показатель.
Я использую свой экран в портретной ориентации, я вижу сразу 200 строк кода.
Кальмарий
и если я не использую разрывы строк, чтобы разбить мои строки, я могу кодировать метод из 5000 строк в одну строку ...
jwenting
1

Я думаю, что ответ TomTom был близок к тому, как я к этому отношусь.

Я все больше и больше сталкиваюсь с цикломатической сложностью, а не с линиями.

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

Иногда я помещаю однострочные if в случаи переключения, потому что по какой-то причине это, как правило, случаи, когда разделение его скорее мешает, чем помогает.

Обратите внимание, что я не считаю охранную логику против этого предела.

Лорен Печтель
источник
Было показано, что цикломатическая сложность в больших количествах реального производственного кода ОЧЕНЬ сильно коррелирует с необработанным SLOC, что делает вычисление цикломатической сложности полной тратой времени, энергии и тактовых циклов.
Джон Р. Штром
@ JohnR.Strohm Я говорю о методе, а не о целом. Конечно, в целом это сильно коррелирует - вопрос в том, как разделить этот код на методы. 10 методов по 100 строк или 100 методов по 10 строк по-прежнему будут иметь одинаковую общую SLOC и сложность, но с первым будет намного сложнее работать.
Лорен Печтель
Я тоже. Исследование корреляции смотрело на много кода и много процедур. (Это был один из крупных публичных репозиториев.)
Джон Р. Штром
-3

В ООП все вещи возражают и есть такие особенности:

  1. Полиморфизм
  2. абстракция
  3. наследование

Когда вы соблюдаете эти правила, тогда ваши методы обычно невелики, но для маленьких или очень маленьких (например, 2-3 строчных) правил не существует. Преимущество маленького метода (маленькая единица, например, метод или функция):

  1. лучше читаемый
  2. поддерживать лучше
  3. исправлена ​​ошибка лучше
  4. меняется лучше
Сэм
источник