сколько параметров может принимать функция
Существуют ли рекомендации относительно того, сколько параметров должна принимать функция?
Я заметил, что несколько функций, с которыми я работаю, имеют 6 или более параметров, тогда как в большинстве библиотек я редко встречаю функцию, которая занимает больше 3.
Часто многие дополнительные параметры являются бинарными параметрами для изменения поведения функции. Я думаю, что некоторые из этих функций с расширенными параметрами, вероятно, должны быть реорганизованы. Есть ли правило, для какого числа слишком много?
10 ответов
Я никогда не видел руководства, но по моему опыту функция, которая принимает более трех или четырех параметров, указывает на одну из двух проблем:
Есть несколько хороших успехов:
В соответствии с «Чистым Кодексом: Руководство по гибкому программному мастерству» нуль идеален, один или два являются приемлемыми, три в особых случаях и четыре или более, никогда!
В этой книге есть глава, в которой рассказывается только о функциях, где обсуждаются большие параметры, поэтому я думаю, что эта книга может быть хорошим ориентиром в отношении того, сколько параметров вам нужно.
По моему личному мнению, один параметр лучше, чем никто, потому что я думаю, что более ясно, что происходит.
В качестве примера, на мой взгляд, второй выбор лучше, потому что более понятно, что обрабатывает этот метод:
О множестве параметров это может быть признаком того, что некоторые переменные могут быть сгруппированы в один объект или, в этом случае, много логических элементов могут представлять, что функция /метод делает больше, что одна вещь, и в этот случай лучше рефакторинг каждого из этих действий в другой функции.
Например, скажем, у вас есть класс менеджера, который просит класс 3-го класса выполнить задания.
Если вы правильно моделируете,
Если у вас нет правильной модели, метод будет таким:
Правильная модель всегда уменьшает параметры функции между вызовами метода, поскольку правильные функции делегируются их собственным классам (одна ответственность), и у них достаточно данных для выполнения своей работы.
Всякий раз, когда я вижу увеличение числа параметров, я проверяю свою модель, чтобы проверить, правильно ли я создал модель приложения.
Однако есть некоторые исключения: когда мне нужно создать объект переноса или объекты конфигурации, я буду использовать шаблон построителя для создания небольших построенных объектов перед созданием большого объекта конфигурации.
Если вы программируете на достаточно низком уровне (C, C ++, сборка), большое количество параметров может быть весьма вредным для производительности на некоторых архитектурах, особенно если функция называется большим количеством раз.
Для функций, которые вызываются очень часто, даже тот факт, что программа должна настраивать аргументы перед каждым вызовом, может повлиять на производительность ( r0 to r3 ) может быть перезаписана по вызываемой функции и должен быть заменен перед следующим вызовом), поэтому в этом отношении наилучшие нулевые аргументы.
Update:
KjMag поднимает интересную тему вложения. Вложение может каким-то образом смягчить это, поскольку это позволит компилятору выполнять те же оптимизации, которые вы могли бы сделать, если пишете в чистой сборке. Другими словами, компилятор может видеть, какие параметры и переменные используются вызываемой функцией, и может оптимизировать использование регистров, чтобы минимизировать чтение /запись стека.
Есть некоторые проблемы с inlining, хотя.
Нет, нет стандартного руководства
Но есть некоторые методы, которые могут сделать функцию с большим количеством параметров более терпимой.
Вы можете использовать параметр list-if-args (args *) или параметр dictionary-of-args (kwargs ** )
Или вы можете использовать синтаксис определения литерала объекта
Например, вот вызов JavaScript jQuery для запуска запроса AJAX GET:
Если вы посмотрите на класс ajax jQuery, вы можете установить lot (приблизительно 30) больше свойств; в основном потому, что связи ajax очень сложны. К счастью, синтаксис объектного литерала облегчает жизнь.
C # intellisense обеспечивает активную документацию параметров, поэтому нередко можно увидеть очень сложные схемы перегруженных методов.
Динамически типизированные языки, такие как python /javascript, не имеют такой возможности, поэтому гораздо чаще встречаются определения ключевых слов и описания литерала объекта.
Я предпочитаю определения литерала объекта ( даже в C # ) для управление сложными методами, поскольку вы можете явно видеть, какие свойства задаются при создании объекта. Вам придется немного поработать над аргументами по умолчанию, но в конечном итоге ваш код будет намного читабельнее. С определениями литералов объектов вы можете нарушить свою зависимость от документации, чтобы понять, что делает ваш код на первый взгляд.
IMHO, перегруженные методы сильно переоцениваются.
Примечание. Если я помню, что управление правами на чтение в режиме readonly должно работать для конструкторов литералов объектов в C #. Они по существу работают так же, как и задание свойств в конструкторе.
Если вы никогда не писали какой-либо нетривиальный код на языке динамически типизированного (python) и /или функционального /прототипа javaScript, я настоятельно рекомендую попробовать его. Это может быть просветительский опыт.
Вначале может быть страшно нарушить вашу зависимость от параметров для конечного результата, для всего подхода к инициализации функции /метода, но вы научитесь делать это гораздо больше с вашим кодом, не добавляя лишней сложности.
Update:
Я, вероятно, должен был предоставить примеры для демонстрации использования на статически типизированном языке, но сейчас я не думаю о статически типизированном контексте. В основном, я делал слишком много работы в динамически типизированном контексте, чтобы внезапно переключиться обратно.
Когда список параметров увеличивается до более пяти, рассмотрите определение структуры или объекта контекста.
Это в основном просто структура, которая содержит все необязательные параметры с некоторыми разумными настройками по умолчанию.
Но опять же, это правило, а не правило. У меня часто есть функции, которые принимают более двух параметров из-за необычных обстоятельств или простоты использования.
Очень похоже на то, как говорит Эван Плэйс, я большой поклонник просто передавать ассоциативные массивы (или сопоставимую структуру данных вашего языка) по возможности.
Таким образом, вместо (например) это:
WordPress делает много всего этого, и я думаю, что он работает хорошо. (Хотя мой примерный код выше мнимый и сам по себе не является примером из WordPress.)
Этот метод позволяет вам легко передавать много данных в ваши функции, но освобождает вас от необходимости запоминать порядок, в котором каждый должен быть передан.
Вы также оцените эту технику, когда приходит время на рефакторинг, вместо того, чтобы потенциально изменить порядок аргументов функции (например, когда вы понимаете, что вам нужно передать еще один аргумент), вам не нужно менять список параметров ваших функций.
Предыдущий answer упомянул надежного автора, который заявил, что чем меньше параметров ваши функции, тем лучше вы делаете. Ответ не объяснил, почему, но книги объясняют это, и вот две из самых убедительных причин, почему вам нужно принять эту философию и с которой я лично соглашаюсь:
Параметры относятся к уровню абстракции, который отличается от уровня функции. Это означает, что читателю вашего кода нужно будет подумать о характере и назначении параметров ваших функций: это мышление является «более низким уровнем», чем имя и назначение их соответствующих функций.
В идеале нуль. Один или два в порядке, три в некоторых случаях.
Четыре или более, как правило, плохой практики.
Как и отдельные принципы ответственности, которые другие отметили, вы также можете подумать об этом с точки зрения тестирования и отладки.
Если есть один параметр, зная его значения, тестирование и поиск ошибок с ними «относительно просто, поскольку есть только один фактор. По мере увеличения факторов общая сложность быстро возрастает. Для абстрактного примера:
Параметры и аргументы функции
В программировании функции могут не только возвращать данные, но также принимать их, что реализуется с помощью так называемых параметров, которые указываются в скобках в заголовке функции. Количество параметров может быть любым.
Параметры представляют собой локальные переменные, которым присваиваются значения в момент вызова функции. Конкретные значения, которые передаются в функцию при ее вызове, будем называть аргументами. Следует иметь в виду, что встречается иная терминология. Например, формальные параметры и фактические параметры. В Python же обычно все называют аргументами.
Рассмотрим схему и поясняющий ее пример:
Когда интерпретатор переходит к функции, чтобы начать ее исполнение, он присваивает переменным-параметрам переданные в функцию значения-аргументы. В примере переменной a будет присвоено 100, b будет присвоено 12.
Существуют изменяемые типы данных. Для Питона, это, например, списки и словари. В этом случае данные передаются по ссылке. В функцию передается ссылка на них, а не сами данные. И эта ссылка связывается с локальной переменной. Изменения таких данных через локальную переменную обнаруживаются при обращении к ним через глобальную. Это есть следствие того, что несколько переменных ссылаются на одни и те же данные, на одну и ту же область памяти.
Необходимость передачи по ссылке связана в первую очередь с экономией памяти. Сложные типы данных, по сути представляющие собой структуры данных, обычно копировать не целесообразно. Однако, если надо, всегда можно сделать это принудительно.
Произвольное количество аргументов
Обратим внимание еще на один момент. Количество аргументов и параметров совпадает. Нельзя передать три аргумента, если функция принимает только два. Нельзя передать один аргумент, если функция требует два обязательных. В рассмотренном примере они обязательные.
Однако в Python у функций бывают параметры, которым уже присвоено значение по-умолчанию. В таком случае, при вызове можно не передавать соответствующие этим параметрам аргументы. Хотя можно и передать. Тогда значение по умолчанию заменится на переданное.
Согласно правилам синтаксиса Python при определении функции параметры, которым присваивается значение по-умолчанию должны следовать (находиться сзади) за параметрами, не имеющими значений по умолчанию.
А вот при вызове функции, можно явно указывать, какое значение соответствует какому параметру. В этом случае их порядок не играет роли:
В данном случае оба вызова – это вызовы с одними и теми же аргументами-значениями. Просто в первом случае сопоставление параметрам-переменным идет в порядке следования. Во-втором случае – по ключам, которыми выступают имена параметров.
В Python определения и вызовы функций имеют и другие нюансы, рассмотрение которых мы пока опустим, так как они требуют более глубоких знаний, чем у нас есть на данный момент. Скажем лишь, что функции может быть определена так, что в нее можно передать хоть ни одного аргумента, хоть множество:
Опять же, судя по скобкам, здесь возникает упомянутый в прошлом уроке кортеж.
Практическая работа
Напишите программу, в которой определены следующие четыре функции:
Функция getInput не имеет параметров, запрашивает ввод с клавиатуры и возвращает в основную программу полученную строку.
Функция strToInt имеет один параметр. В теле преобразовывает переданное значение к целочисленному типу. Возвращает полученное число.
Функция printInt имеет один параметр. Она выводит переданное значение на экран и ничего не возвращает.
Примеры решения и дополнительные уроки в android-приложении и pdf-версии курса
Функциональное мышление. Часть 4
После небольшого экскурса в базовые типы, мы можем снова вернуться к функциям. В частности, к ранее упомянутой загадке: если математическая функция может принимать только один параметр, то как в F# может существовать функция, принимающая большее число параметров? Подробнее под катом!
Ответ довольно прост: функция с несколькими параметрами переписывается как серия новых функций, каждая из которых принимает только один параметр. Эту операцию компилятор выполняет автоматически, и называется она «каррирование» (currying), в честь Хаскела Карри, математика, который существенно повлиял на разработку функционального программирования.
Чтобы увидеть, как каррирование работает на практике, воспользуемся простейшим примером кода, печатающим два числа:
На самом деле, компилятор переписывает его приблизительно в такой форме:
Рассмотрим этот процесс подробнее:
Переписывая функции таким образом, компилятор гарантирует, что каждая функция принимает только один параметр, как и требовалось. Таким образом, используя » printTwoParameters «, можно подумать, что это функция с двумя параметрами, но на самом деле используется функция только с одним параметром. В этом можно убедиться, передав ей лишь один аргумент вместо двух:
Если вычислить ее с одним аргументом, мы не получим ошибку — будет возвращена функция.
Итак, вот что на самом деле происходит, когда printTwoParameters вызывается с двумя аргументами:
Вот пример пошаговой и нормальной версий:
Опять же, «функция с двумя параметрами» на самом деле является функцией с одним параметром, которая возвращает промежуточную функцию.
Но подождите, а что с оператором » + «? Это ведь бинарная операция, которая должна принимать два параметра? Нет, она тоже каррируется, как и другие функции. Это функция с именем » + «, которая принимает один параметр и возвращает новую промежуточную функцию, в точности как addTwoParameters выше.
Сигнатуры каррированных функций
Теперь, когда мы знаем, как работают каррированные функции, интересно узнать на что будут похожи их сигнатуры.
Если вычислить явно каррированную реализацию, можно увидеть скобки в сигнатуре, но если вычислить обыкновенную, неявно каррированную реализацию, скобок не будет:
Скобки необязательны. Но их можно представить в уме, чтобы упростить восприятие сигнатур функций.
А в чём разница между функцией, которая возвращает промежуточную функцию, и обычной функцией с двумя параметрами?
Вот функция с одним параметром, возвращающая другую функцию:
А вот функция с двумя параметрами, которая возвращает простое значение:
Их сигнатуры немного отличаются, но в практическом смысле между ними нет особой разницы, за исключением того факта, что вторая функция автоматически каррирована.
Функции с более чем двумя параметрами
Как работает каррирование для функций с количеством параметров, большим двух? Точно так же: для каждого параметра, кроме последнего, функция возвращает промежуточную функцию, замыкающую предыдущий параметр.
Рассмотрим этот нелегкий пример. У меня явно объявлены типы параметров, но функция ничего не делает.
Сигнатура всей функции:
и сигнатуры промежуточных функций:
Сигнатура функции может сообщить о том, сколько параметров принимает функция: достаточно подсчитать число стрелок вне скобок. Если функция принимает или возвращает другую функцию, будут еще стрелки, но они будут в скобках и их можно будет проигнорировать. Вот некоторые примеры:
Трудности с множественными параметрами
Пока вы не поймёте логику, которая стоит за каррированием, она будет приводить к некоторым неожиданным результатам. Помните, что вы не получите ошибку, если запустите функцию с меньшим количеством аргументов, чем ожидается. Вместо этого вы получите частично примененную функцию. Если затем вы воспользуетесь частично примененной функцией в контексте, где ожидается значение, можно получить малопонятную ошибку от компилятора.
Рассмотрим безобидную с первого взгляда функцию:
Как думаете, что произойдет, если вызвать ее так, как показано ниже? Выведется ли «hello» на консоль? Попробуйте догадаться до выполнения. Подсказка: посмотрите на сигнатуру функции.
Вопреки ожиданиям вызова не будет. Исходная функция ожидает unit как аргумент, который не был передан. Поэтому была получена частично примененная функция (в данном случае без аргументов).
А что насчет этого случая? Будет ли он скомпилирован?
Если нет понимания каррирования, данное сообщение может быть очень загадочным. Дело в том, что все выражения, которые вычисляются отдельно (т.е. не используются как возвращаемое значение или привязка к чему-либо посредством «let»), должны вычисляться в unit значение. В данном случае, оно не вычисляется в unit значение, но вместо этого возвращает функцию. Это длинный способ сказать о том, что printfn не хватает аргумента.
Слишком много параметров
Можно получить столь же загадочные сообщения, если передать функции слишком много параметров. Несколько примеров передачи слишком большого числа параметров в printf :
Если разбить общий вызов на серию явных промежуточных функций, как делали ранее, можно увидеть, что именно происходит не так.
Дополнительные ресурсы
Для F# существует множество самоучителей, включая материалы для тех, кто пришел с опытом C# или Java. Следующие ссылки могут быть полезными по мере того, как вы будете глубже изучать F#:
Также описаны еще несколько способов, как начать изучение F#.
И наконец, сообщество F# очень дружелюбно к начинающим. Есть очень активный чат в Slack, поддерживаемый F# Software Foundation, с комнатами для начинающих, к которым вы можете свободно присоединиться. Мы настоятельно рекомендуем вам это сделать!
Не забудьте посетить сайт русскоязычного сообщества F#! Если у вас возникнут вопросы по изучению языка, мы будем рады обсудить их в чатах:
Об авторах перевода
Автор перевода @kleidemos
Перевод и редакторские правки сделаны усилиями русскоязычного сообщества F#-разработчиков. Мы также благодарим @schvepsss и @shwars за подготовку данной статьи к публикации.
Функции с переменным числом параметров
Функции с переменным числом параметров
К ак уже обсуждалось ранее, по умолчанию параметры передаются функции через стек. Поэтому, технически, нет ограничения на количество передаваемых параметров – “запихать” можно сколько угодно. Проблема в том, как потом функция будет разбирать переданные параметры. Функции с переменным числом параметров объявляются как обычные функции, но вместо недостающих аргументов ставится многоточие. Пусть мы хотим сделать функцию, которая складывает переданные ей числа, чисел может быть произвольное количество. Необходимо каким-то образом передать функции число параметров. Во-первых, можно явно передать число параметров обязательным аргументом. Во-вторых, последний аргумент может иметь некоторое «терминальное» значение, наткнувшись на которое функция закончит выполнение.
Общий принцип работы следующий: внутри функции берём указатель на аргумент, далее двигаемся к следующему аргументу, увеличивая значение указателя.
OLD SCHOOL
Д елаем всё вручную. Функция, которая складывает переданные ей аргументы
Первый параметр – число аргументов. Это обязательный параметр. Второй аргумент – это первое переданное число, это тоже обязательный параметр. Получаем указатель на первое число
Далее считываем все числа и складываем их. В этой функции мы также при сложении проверяем на переполнение типа unsigned.
Можно сделать первый аргумент необязательным и «перешагнуть» аргумент unsigned char num, но тогда возникнет большая проблема: аргументы располагаются друг за другом, но не факт, что непрерывно. Например, в нашем случае первый аргумент будет сдвинут не на один байт, а на 4 относительно num. Это сделано для повышения производительности. На другой платформе или с другим компилятором, или с другими настройками компилятора могут быть другие результаты.
Поэтому лучше число параметров, если это аргумент, сделать типом int или unsigned int.
Можно сделать по-другому: в качестве «терминального» элемента передавать ноль и считать, что если мы встретили ноль, то больше аргументов нет. Пример
Но теперь уже передавать нули в качестве аргументов нельзя. Здесь также есть один обязательный аргумент – первое переданное число. Если его не передавать, то мы не сможем найти адрес, по которому размещаются переменные в стеке. Некоторые компиляторы (Borland Turbo C) позволяют получить указатель на …, но такое поведение не является стандартным и его нужно избегать.
VA_ARG
М ожно воспользоваться макросом va_arg библиотеки stdarg.h. Он делает практически то же самое, что и мы: получает указатель на первый аргумент а затем двигается по стеку. Пример, та же функция, только с va_arg
Первый аргумент – число параметров – также лучше делать типа int, иначе получим проблему со сдвигом, кратным 4.
Название | Описание |
---|---|
va_list | Тип, который используется для извлечения дополнительных параметров функции с переменным числом параметров |
void va_start(va_list ap, paramN) | Макрос инициализирует ap для извлечения дополнительных аргументов, которые идут после переменной paramN. Параметр не должен быть объявлена как register, не может иметь типа массива или указателя на функцию. |
void va_end(va_list ap) | Макрос необходим для нормального завершения работы функции, работает в паре с макросом va_start. |
void va_copy(va_list dest, va_list src) | Макрос копирует src в dest. Поддерживается начиная со стандарта C++11 |
Неправильное использование
Если передано больше аргументов, то функция выведет только те, которые ожидала встретить
Так как очистку стека производит вызывающая функция, то стек не будет повреждён. Получается, что если изменить схему вызова и сделать так, чтобы вызываемый объект сам чистил стек после себя, то в случае неправильного количества аргументов стек будет повреждён. То есть, буде функция объявлена как __stdcall, в целях безопасности она не может иметь переменного числа аргументов.
Однако, если добавить спецификатор __stdcall к нашей функции summ она будет компилироваться. Это связано с тем, что компилятор автоматически заменит __stdcall на __cdecl.
Программа завершится с ошибкой вроде The value of ESP was not properly saved across a function call.
Число параметров, переданных функции?
Я хочу знать, сколько параметров может быть передано функции, я имею в виду, что такое хорошая практика программирования, касающаяся передачи параметров для работы?
ОТВЕТЫ
Ответ 1
Чем меньше, тем лучше, но только если это все еще имеет смысл. Я никогда не слышал о стандартном числе параметров, которые нужно передать, но я слышал о том, как улучшить их.
Например, не делайте этого:
но, надеюсь, это само собой разумеется. Но также я бы рекомендовал не создавать странный класс, просто чтобы сократить количество параметров.
Ответ 2
. количество объектов, которое может удерживать средний человек в рабочей памяти, составляет 7 ± 2; это часто называют Законом Миллера.
Здесь выдержка из кода Complete 2nd Edition:
Ограничьте количество параметров подпрограмм примерно до семи
Ответ 3
6 слишком много для меня и 7 подавляющее!
Ответ 4
Если у вас есть много вещей, которые вы хотели бы передать функции, вы можете захотеть взглянуть на некоторые другие способы передачи этих данных в отличие от простой передачи параметров. Например, в некоторых случаях может быть лучше создать XML файл, а затем передать значения, связанные с получением данных вокруг этого XML файла. Если вы используете веб-приложение, это может просто передавать данные через сеансы или отправлять сообщения, а не получать или выполнять вызовы, которые упростят вашу жизнь.
Также вы можете захотеть сохранить часть этой информации в качестве переменных-членов.
Я бы порекомендовал не более 4. Вы не хотите, чтобы ваши строки длились длиннее 30 символов, если вы не генерируете массивную строку, но даже тогда она становится действительно нечитаемой и грубой (хотя это особенно необходимо для javascript).
Ответ 5
Хорошая практика программирования для написания программ, чтобы их было легко читать. Лично я стараюсь не писать функции, которые имеют больше параметров, чем могут отображаться в одной строке на экране. Обычно это не более пяти или шести параметров.
Ответ 6
Ответ 7
некоторые компиляторы ARM передают три или менее параметра с использованием регистров и не более трех сгруппированы. Вызов с использованием сложного типа медленнее, чем вызов с использованием регистров, поэтому в этом случае вы должны использовать три или менее параметра для скорости.
Ответ 8
Если вы не знаете, сколько параметров вы собираетесь передать функции, используйте параметр для отправки аргументов переменной методу.
Ответ 9
В зависимости от архитектуры более 1-3 вызовет передачу в стеке. Это медленнее, чем передача через регистры. С точки зрения производительности лучше всего передать либо указатель на класс-оболочку, либо указатель на структуру. Это гарантирует, что передается только одно значение и сохраняет некоторые записи/чтения в память.