Подтвердить что ты не робот

Элегантный способ передачи нескольких аргументов функции

У меня есть функция, которая выглядит так:

bool generate_script (bool net, bool tv, bool phone,
                        std::string clientsID,
                        std::string password,
                        int index, std::string number, 
                        std::string Iport, std::string sernoID,
                        std::string VoiP_number, std::string  VoiP_pass,
                        std::string target, int slot, int port, 
                        int onu, int extra, std::string IP, std::string MAC);

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

4b9b3361

Ответ 1

Если все эти параметры осмысленно связаны, упакуйте их в структуру.

Ответ 2

Поместите их в struct

Создайте структуру

struct GenerateScriptParams { /* ... */ };

и поместите все параметры там. Фактически вы можете предоставить значения по умолчанию для инициализации struct, а также путем реализации конструктора по умолчанию или, в С++ 11, путем предоставления инициализации по умолчанию для отдельных членов. Затем вы можете изменить значения, которые не должны использоваться по умолчанию. Этот выборочный выбор нестандартных параметров невозможен для вызова функции с большим количеством параметров в С++.

Сделать интерфейс приятным для вызывающего

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

GenerateScriptParams gsp;
gsp.net = true;
gsp.phone = false;
gps.extra = 10;
generate_script( gsp );

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

GenerateScriptParams & GenerateScriptParams::setNet  ( bool val );
GenerateScriptParams & GenerateScriptParams::setTV   ( bool val );
GenerateScriptParams & GenerateScriptParams::setPhone( bool val );
// ... //

Затем вызывающий код может написать

generate_script( GenerateScriptParams()
    .setNet(true),
    .setPhone(false),
    .setExtra(10) );

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

Ответ 3

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

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

ИЗМЕНИТЬ

Я дам вам небольшой пример, чтобы описать, почему несколько классов лучше одной монолитной структуры. Позвольте начать отсчет тестов, которые вам нужно написать, чтобы покрыть вышеприведенную функцию. В качестве входных данных имеется 18 параметров (3 булево). Поэтому нам потребуется не менее 15 тестов только для проверки ввода (при условии, что значения не связаны между собой).

Общее количество тестов невозможно вычислить без реализации, но мы можем иметь представление о величине. Пусть взять нижнюю границу, все входные данные можно рассматривать как логическое число возможных комбинаций 2 ^ 18, поэтому вокруг 262000 тестов.

Теперь, что произойдет, если мы разделим входные данные на несколько объектов?

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

Но что более важно, количество тестов будет рушиться, скажем, в группе из четырех (4,4,4 и 4 параметра на объект) общее число тестов:

2 ^ 4 + 2 ^ 4 + 2 ^ 4 + 2 ^ 4 + 2 ^ 4 = 80

Пятые атрибуты связаны с перестановкой объектов с самим собой.

Итак, что более дорого стоит? Напишите тысячи тестов или несколько классов?

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

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

Ответ 4

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

script my_script(mandatory, parameters);
my_script.net(true).tv(false).phone(true);

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

Ответ 5

Игнорирование возможности или желательности изменения функции или программы в некотором роде для уменьшения количества параметров...

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

например.

bool generate_script (
        bool net,
        bool tv,
        bool phone,
        std::string clientsID,
        std::string password,
        int index,
        std::string number,
        std::string Iport,
        std::string sernoID,
        std::string VoiP_number,
        std::string  VoiP_pass,
        std::string target,
        int slot,
        int port,
        int onu,
        int extra,
        std::string IP,
        std::string MAC);

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

Ответ 6

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

Это поможет вам структурировать ваши данные.