Сколько параметров слишком много? [закрыто]


228

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

Конечно, вы можете использовать структурную переменную в качестве обходного пути: поместить все эти переменные в одну структуру и передать ее в рутину. Фактически, использование структур для упрощения списков параметров является одним из методов, описанных Стивом Макконнеллом в Code Complete . Но как он говорит:

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

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

У меня вопрос, когда я могу считать список параметров слишком большим? Я думаю, что более 5 параметров, это слишком много. Что вы думаете?


1
Просто ссылаясь здесь на этот вопрос, который является практическим примером того, сколько параметров слишком много ...
Гидеон

5
В JavaScript 65537 параметров слишком много: jsfiddle.net/vhmgLkdm
Аадит М Шах

просто посмотрите на http-компоненты apache, из этого почти невозможно построить что-то динамическое
Эндрю Скотт Эванс,

Ответы:


162

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

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

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

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


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

1
@ Michaelangel007 относительно ответа, который вы комментируете, вы не должны этого говорить, потому что он говорит, что правила не существует. Кроме того, количество параметров зависит от читабельности, а не от производительности.
clemp6r

1
@ clemp6r Неверно - это и то и другое . Ребятам из компиляторов приходится иметь дело с «разливом регистров» постоянно. Только авторитетный ответ , чтобы проверить сборку , что генерирует компилятор. Количество регистров не меняется магически на одной платформе. en.wikipedia.org/wiki/Register_allocation
Michaelangel007,

124

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

HWND CreateWindowEx
(
  DWORD dwExStyle,
  LPCTSTR lpClassName,
  LPCTSTR lpWindowName,
  DWORD dwStyle,
  int x,
  int y,
  int nWidth,
  int nHeight,
  HWND hWndParent,
  HMENU hMenu,
  HINSTANCE hInstance,
  LPVOID lpParam
);

Это 12 параметров (9, если вы связываете x, y, w и h в виде прямоугольника), а также параметры, полученные из имени класса. Как бы вы сократили это? Хотели бы вы сократить число до сути?

Не позволяйте количеству параметров беспокоить вас, просто убедитесь, что он логичен и хорошо документирован, и пусть intellisense * поможет вам

* Другие помощники кодирования доступны!


37
Я голосую за это. Я поражен всеми другими ответами, говоря "3" и "4"! Правильный ответ: необходимый минимум, которого иногда может быть немало.
Тони Эндрюс

16
Если бы эта функция была разработана сегодня, она бы выглядела немного иначе. x, y, nWidth и nHeight могут быть связаны в объекте Rectangle. style и xStyle могут быть объединены в набор перечислений или строк. Теперь у вас есть только 8 параметров.
Finnw

16
@finnw Если бы эта функция была разработана сегодня? Он уже был переработан. Форма f = новая форма (); имеет 0 параметров.
Ник

36
Это С и Винапи. Я не могу думать о худшем примере.
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳

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

106

В « Чистом коде» Роберт С. Мартин посвятил этой теме четыре страницы. Вот суть:

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


2
Я сомневаюсь, что это включает в себя «это», потому что это контекст исполнения. В функциональном программировании контекст является глобальным, и в действительности он является объектом, к которому вы применяете метод. Кроме того, если бы вы включили «this» в список параметров, было бы невозможно иметь 0 params (идеально).
Том

1
Нет, это не включает «это».
Патрик МакЭлхани

20
Кроме того, Мартин должен поговорить со стандартным комитетом C ++. Половина <алгоритма> принимает более 3 параметров, потому что только один диапазон итераторов равен 2. Это просто природа программирования с итераторами (то есть общее программирование с коллекциями STL).
Стив Джессоп

3
@SteveJessop: ошибка <алгоритма> в том, что он всегда работает на срезе. Если бы алгоритм и коллекцию нужно было перепроектировать, я бы сделал так, чтобы алгоритмы всегда брали целую коллекцию, и у вас был бы способ разрезать представление коллекции, чтобы позволить алгоритмам работать с частями; в качестве альтернативы я бы также определил переопределение сокращений, чтобы упростить работу над общим делом.
Ложь Райан

3
@LieRyan: тогда как если бы я занимался редизайном, <algorithm>я бы воздействовал на объект диапазона. Коллекции были бы диапазонами, но не все диапазоны были бы коллекциями. И действительно, Boost уже сделал это. Во всяком случае, моя точка зрения в том , что в широком масштабе широко используется библиотека игнорирует этот совет, так что худшее , что это гарантированно произойдет с вами , если вы тоже, что многие из ваших миллионов пользователей будет возиться с небольшими упрощениями на ваш интерфейс ;-)
Steve Джессоп

79

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

Пожалуйста, не делай этого!

(Обычно.)


Некоторый код, над которым я работал, использовал учеников для достижения того же. В то время я был программистом C и спросил, почему так много глобальных переменных, почему вход / выход не может быть явно параметром?
user3528438

38

Если вы начинаете мысленно отсчитывать параметры в подписи и сопоставлять их с вызовом, то пришло время провести рефакторинг!


Это очень хороший ответ. Если параметры организованы логически (x, y, w, h), легко запомнить все из них в правильном порядке. Намного сложнее вспомнить, куда поместить указатель FILE в putc (который имеет только два параметра), тем более что fprintf противоположен.
user877329

Самый хороший ответ. Очень хорошо сказано
Анвар

31

Большое спасибо за все ваши ответы:

  • Было немного удивительно найти людей, которые также думают (как и я), что 5 параметров - это хороший предел разумности кода.

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

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

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

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

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

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

  • Наконец, я думаю, что wnoise очень согласен с Ником и завершает его сатирический вклад этим поэтическим видением (см. Комментарии ниже) искусства программирования:

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


16

Этот ответ предполагает язык ОО. Если вы не используете один - пропустите этот ответ (другими словами, это не совсем языковой ответ).

Если вы передаете более трех или около того параметров (особенно внутренних типов / объектов), дело не в том, что это «Слишком много», а в том, что вы можете упустить шанс создать новый объект.

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

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


Пожалуйста, назовите язык, который не использует ни процедуры, ни параметры. Я не могу думать об одном, даже сборка может иметь подпрограммы (метки).
Аурон

Сборка x86 определенно имеет подпрограммы (вызов / возврат). Другие могут потребовать комбо cmp / jmp.
Брайан Кноблаух

Извините, "ret", а не "return". Вздыхает. Это был уже долгий день.
Брайан Кноблаух

Извините за неоднозначную формулировку Auron, я думаю, что я это исправил. Это был мой ответ, который зависел от языка, а не вопрос.
Билл К

1
Не все языки допускают прохождение структур. Кроме того, передача структуры означает, что принимающий метод должен иметь код для обработки структуры и, следовательно, может потребоваться больше параметров. Кроме того, в не-оо языке, как правило, вам нужен дополнительный параметр - все языки ОО имеют параметр «скрытый» «this», который в противном случае должен быть передан (или, в более ужасающем языке / дизайне, может быть глобально). доступно)
Билл К

13

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

  1. логическое отношение к основной цели функции в сравнении с одноразовыми настройками

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


12

Одна из известных программных эпиграмм Алана Перлиса (описанная в заметках ACM SIGPLAN 17 (9), сентябрь 1982 г.) гласит: «Если у вас есть процедура с 10 параметрами, вы, вероятно, пропустили некоторые».


11

Согласно Стиву Макконнеллу в Code Complete , вы должны

Ограничьте количество параметров процедуры до семи


3
/: Число, указывающее, что фактоид составлен: xkcd.com/899
user877329

9

Для меня, когда список пересекает одну строку в моей IDE, тогда это один параметр слишком много. Я хочу видеть все параметры в одной строке, не нарушая зрительный контакт. Но это только мое личное предпочтение.


Это хорошо, пока вы с некоторыми разработчиками не попробуете foo (int a, float b, string c, double d). Лучше постараться избежать работы с ними, я думаю. : D
Ронтолог

4
Если бы кто-то еще давал классы смехотворно длинными именами, я бы не позволил этому повлиять на то, как я определяю или называю мои процедуры.
Finnw

9
Могу ли я познакомить вас с «возвратом каретки»?

3
Когда список пересекает одну строку в вашей IDE, ваш монитор слишком мал, чтобы использовать его с этим кодом, и вам следует явно купить монитор с более высоким разрешением по горизонтали. Разрыв строки или уменьшение количества параметров - это просто обходные пути, которые не решают проблему с корнем, которая заключается в том, что ваш монитор слишком мал для этой кодовой базы!
Кайзерлуди

9

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


8

Семь вещей в краткосрочной памяти?

  1. Наименование функции
  2. Возвращаемое значение функции
  3. Назначение функции
  4. Параметр 1
  5. Параметр 2
  6. Параметр 3
  7. Параметр 4

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

7
8. порядок параметров
Ева

7

В худших 5 фрагментах кода , проверьте второй, «Это конструктор». Он имеет более 37 ⋅ 4 ≈ 150 параметров:

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

конструктор с более чем 150 параметрами


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

6

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

void *
mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t offset);

Есть 6 аргументов, и каждый из них необходим. Кроме того, между ними нет общей связи, которая бы оправдывала их объединение. Может быть, вы могли бы определить «struct mmapargs», но это было бы хуже.


Ну, protи flagsможно было бы свернуть вместе, если бы дизайнер чувствовал, что 5 - это волшебное число, которое намного лучше, чем 6. Немного похоже на способ, который openсочетает режим чтения / записи со всеми другими разными флагами. И, возможно, вы могли бы избавиться от offsetэтого, указав, что отображаемый раздел начинается с текущей позиции поиска filedes. Я не знаю, есть ли ситуации, когда вы можете mmapсоздать регион, на который вы не способны lseek, но если нет, то в этом нет особой необходимости.
Стив Джессоп

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

1
@Steve: установка позиции поиска отдельным вызовом заранее привела бы к условию беспричинной гонки. Для некоторых API (OpenGL) существует так много параметров, которые влияют на вызов, что вам действительно нужно использовать состояние, но обычно каждый вызов должен быть как можно более автономным. Действие на расстоянии - это путь к темной стороне.
Бен Фойгт

5

Согласно Perl Best Practices , 3 - это хорошо, 4 - это слишком много. Это просто руководство, но в нашем магазине мы стараемся придерживаться этого.


5

Я бы нарисовал предел для публичных функций по 5 параметрам сам.

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


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

5

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



4

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


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

однако, если вам нужен еще один параметр для настройки граничного случая, он не должен распространяться на несвязанные компоненты с помощью API
Eran Galperin

4

Ограничение длины в списке параметров - это еще одно ограничение. А ограничение означает применение насилия. Звучит смешно, но вы можете быть ненасильственным даже при программировании. Просто позвольте коду диктовать правила. Очевидно, что если у вас много параметров, тело метода функции / класса будет достаточно большим, чтобы использовать их. И большие фрагменты кода обычно могут быть реорганизованы и разбиты на более мелкие куски. Таким образом, вы получаете решение против использования многих параметров в качестве бесплатного бонуса, поскольку они разделены между меньшими реорганизованными частями кода.


4

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

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


Я даю этому ответу +1, потому что он единственный, который обсуждает что-либо, кроме чистоты кода или произвольных ограничений, которые оба субъективны. Некоторые люди могут не задумываться о том, что вы делаете со стеком, когда находитесь в цикле и толкаете десятки аргументов в стек и из него. Если это в цикле, вам следует рассмотреть возможность использования количества аргументов, которые передаются REGISTERS в ABI, для которого вы компилируете. Например, в MS x64 ABI максимальное количество аргументов, передаваемых через регистры, равно 4. ABI «System V» (используется не-Windows OS) использует больше регистров, поэтому используется 4 переносимых аргумента
Lakey

3

По моему мнению, могут быть случаи, когда вы превысите 4 или какое-то фиксированное число. Вещи, чтобы посмотреть

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

С точки зрения простоты использования или простоты чтения кода, я думаю, что когда вам нужно как бы «обернуть слово» в сигнатуру вашего метода, это заставит вас задуматься, если вы не чувствуете себя беспомощным, и все усилия по уменьшению подписи ведут к безрезультатно. Некоторые очень хорошие библиотеки в прошлом и настоящем используют более 4-5 колясок.


3

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

Для меня это примерно 5, но я не такой яркий. Ваш пробег может варьироваться.

Вы можете создать объект со свойствами для хранения параметров и передать его, если вы превысите установленный предел. См. Книгу Рефакторинга Мартина Фаулера и главу о том, как упростить вызовы методов.


1

Это сильно зависит от среды, в которой вы работаете. Возьмите, например, javascript. В javascript лучший способ передачи параметров - использование объектов с парами ключ / значение, что на практике означает, что у вас есть только один параметр. В других системах сладкое пятно будет на уровне трех или четырех.

В конце концов, все сводится к личному вкусу.


1

Я согласен с 3, все в порядке, 4 слишком много в качестве ориентира. Имея более 3 параметров, вы неизбежно выполняете более одной задачи. Более одной задачи должны быть разделены на отдельные методы.

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


1

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


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

1

Это известный факт, что в среднем люди могут держать в голове 7 +/- 2 вещи одновременно. Мне нравится использовать этот принцип с параметрами. Если предположить, что все программисты умнее людей среднего уровня, я бы сказал, что все 10+ - это слишком много.

Кстати, если параметры в любом случае похожи, я бы поместил их в вектор или список, а не в структуру или класс.


1

Я бы основал свой ответ на том, как часто вызывается функция.

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

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


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