Аргументы функции PHP - использовать массив или нет?

Мне нравится создавать свои PHP-функции, используя пары ключ-значение (массивы) в качестве аргументов, а не отдельные параметры.

Например, я предпочитаю:

function useless_func($params) {
    if (!isset($params['text'])) { $params['text'] = "default text"; }     
    if (!isset($params['text2'])) { $params['text2'] = "default text2"; }   
    if (!isset($params['text3'])) { $params['text3'] = "default text3"; }   
    echo $params['text'].$params['text2'].$params['text3'];
    return;
}

И мне не нравится

function useless_func($text = "default text", $text2 = "default text2", $text3 = "default text3") {
        echo $text.$text2.$text3;
    return;
}

Впервые я увидел, как так обстоят дела в кодовой базе Wordpress.

Причина, по которой я предпочитаю массивы:

  • Аргументы функции могут быть предоставлены в любом порядке
  • Легче читать код / ​​больше самодокументирования (на мой взгляд)
  • Менее подвержен ошибкам, потому что при вызове функции я должен исследовать правильные ключи массива

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

Я ищу некоторые общие советы и рекомендации от экспертов, которые могли бы дать понимание: какой лучший или более правильный способ сделать это?

9 ответов

Решение

Ну, это немного полезно. Но для некоторых аргументов, которые всегда проходят, лучше использовать классическую передачу, например function some($a1, $a2), Я делаю так в моем коде:

function getSome(SomeClass $object, array $options = array())
{
    // $object is required to be an instance of SomeClass, and there's no need to get element by key, then check if it's an object and it's an instance of SomeClass

    // Set defaults for all passed options
    $options = array_merge(array(
        'property1' => 'default1',
        'property2' => 'default2',
        ... => ...
    ), $options); 
}

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

Не делай этого!

Передавать все в массиве - плохая идея в большинстве случаев.

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

Похоже на то, что нужно вводить в функцию то, что ей нужно.

Аргументы функции могут быть предоставлены в любом порядке

У меня нет таких предпочтений. Я не понимаю, что нужно.

Легче читать код / ​​больше самодокументирования (на мой взгляд)

Большинство IDE предоставят вам различные аргументы, необходимые для функции. Если кто-то видит объявление функции вроде foo(Someclass $class, array $params, $id) это очень ясно, что нужно функции. Я не согласен с тем, что один аргумент param легче читать или самодокументировать.

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

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


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

Я предполагаю, что вы спрашиваете, хорошо ли писать все функции, чтобы они принимали только один аргумент, и чтобы этот аргумент был массивом?

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

Я бы порекомендовал, чтобы параметры массива были зарезервированы либо для элементов, для которых вы не знаете, сколько их будет (например, для серии элементов данных), либо для групп связанных опций / настроек (что может быть тем, что происходит в Wordpress. пример, который вы упоминаете?).

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

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

Это граничит с программированием Cargo Cult. Вы говорите, что это более читабельно и самодокументировано. Я бы спросил как? Чтобы узнать, как использовать вашу функцию / метод, я должен прочитать сам код. Я никак не могу знать, как использовать это из самой подписи. Если вы используете какой-либо полуприличный IDE или редактор, который поддерживает намеки на сигнатуру метода, это будет настоящая PITA. Кроме того, вы не сможете использовать синтаксис подсказок типа PHP.

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

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

function useless_func(array $params = array())
{
    $params += array(
        'text' => 'default text',
        'text2' => 'default text2',
        'text3' => 'default text3',
    );
}

Смотрите также: Функция Передача массива в определенный ключ

Несколько вещей, которые вы не получите, используя массивы в качестве аргументов функции:

  1. проверка типов (применима только к объектам и массивам, но может быть полезной и в некоторых случаях ожидаемой).
  2. интеллектуальные (эр) текстовые редакторы имеют функцию понимания кода, которая показывает аргументы, которые понимает функция; использование массивов лишает эту функцию, хотя вы можете добавить возможные ключи в функциональный докблок.
  3. из-за #2 это на самом деле становится более подверженным ошибкам, потому что вы можете неправильно набрать ключ массива.

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

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

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

// Class will tokenize a string based on params
public static function tokenize(array $params)
{
    // Validate required elements
    if (!array_key_exists('value', $params)) {
        throw new Exception(sprintf('Invalid $value: %s', serialize($params)));
    }        

    // Localize optional elements
    $value            = $params['value'];
    $separator        = (array_key_exists('separator', $params)) ? $params['separator'] : '-';
    $urlEncode        = (array_key_exists('urlEncode', $params)) ? $params['urlEncode'] : false;
    $allowedChars     = (array_key_exists('allowedChars', $params)) ? $params['allowedChars'] : array();
    $charsToRemove    = (array_key_exists('charsToRemove', $params)) ? $params['charsToRemove'] : array();

....

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

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

function ($a1, $a2, ... $a10){

        if($a1 == "Y"){$clause_1 = " something = ".$a1." AND ";}
        ...
        if($a10 == "Y"){$clause_10 = " something_else = ".$a10." AND ";}

        $sql = "
        SELECT * FROM some_table 
        WHERE
        ".$clause_1." 
        ....
        ".$clause_10." 
        some_column = 'N'
        ";

        return $sql;
    }

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

@Mike, вы также можете "извлечь ()" ваш аргумент $params в локальные переменные, например:

// Class will tokenize a string based on params
public static function tokenize(array $params)
{
    extract($params);
    // Validate required elements
    if (!isset($value)) {
        throw new Exception(sprintf('Invalid $value: %s', serialize($params)));
    }

    // Localize optional elements
    $value         = isset($value) ? $value : '';
    $separator     = isset($separator) ? $separator] : '-';
    $urlEncode     = isset($urlEncode) ? $urlEncode : false;
    $allowedChars  = isset($allowedChars) ? $allowedChars : array();
    $charsToRemove = isset($charsToRemove) ? $charsToRemove : array();

....

Та же реализация, но короче.

Другие вопросы по тегам