将多个参数传递给函数的优雅方式

时间:2021-05-02 23:18:21

I've got a function which looks like this:

我有一个像这样的函数

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);

In my opinion it looks ugly. What is the proper way of handling this problem? Should I create few vectors with different data types (int, string and bool) and pass them as arguments to this function?

在我看来,它看起来很丑。处理这个问题的正确方法是什么?我应该创建一些具有不同数据类型的向量(int、string和bool)并将它们作为参数传递给这个函数吗?

6 个解决方案

#1


72  

If all these parameters are meaningfully related, pack them in a structure.

如果所有这些参数都是有意义的相关,那么将它们打包到一个结构中。

#2


41  

Put them in a struct

Create a structure

创建一个结构

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

and put all the parameters in there. You can actually provide default values for the initialization of the struct as well by implementing a default constructor or, in C++11, by providing default initialization of individual members. You can then change the values that are not supposed to be defaulted. This selective picking of non-default parameters is not possible for a function call with lots of parameters in C++.

把所有的参数都放进去。通过实现默认构造函数,或者在c++ 11中,通过提供单个成员的默认初始化,您可以为结构体的初始化提供默认值。然后可以更改不应该默认的值。对于c++中有很多参数的函数调用,这种选择性地选择非默认参数是不可能的。

Making the interface nice for the caller

Yet, the usage is a little ugly, since you have to create a temporary name object, then change the values that should not be default and then pass the object to the function:

但是,这种用法有点难看,因为您必须创建一个临时名称对象,然后更改不应该默认的值,然后将对象传递给函数:

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

If you call that function in several different places, it makes sense to avoid this uglyness by providing mutating member functions that can be chained:

如果您在几个不同的地方调用该函数,那么通过提供可以链接的突变成员函数来避免这种丑陋性是有意义的:

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

Then calling code can write

然后调用代码就可以写了

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

without the above uglyness. This avoids the named object that is only used once.

没有上述uglyness。这避免了只使用一次的命名对象。

#3


17  

I personally do not believe that moving all the arguments in one struct will make the code much better. You just move dirt under the carpet. When you are going to deal with the creation of the struct you have the same problem.

我个人并不认为将所有参数移动到一个结构中会使代码更好。你只要把脏东*在地毯下面就行了。当你要处理结构的创建时你会遇到同样的问题。

The question is how much reusable this struct will be? If you end up with a 18 parameters for one function call something it is not quite right in your design. After further analysis you may discover that those parameters can be group in several different classes and those classes could be aggregated to one single object that will be the input of your function. You may want also prefer classes to struct in order to protect your data.

问题是这个结构可以重用多少?如果你最终得到一个函数的18个参数,那么你的设计就不太合适了。在进一步分析之后,您可能会发现这些参数可以在几个不同的类中进行分组,这些类可以聚合到一个对象中,该对象将作为函数的输入。为了保护您的数据,您可能还想要类而不是结构类。

EDIT

编辑

I will give you a small example to describe why several classes are better than one monolithic struct. Let's start counting the tests that you need to write to cover the function above. There are 18 parameters as input (3 boolean). So we are going to need at least 15 tests only to validate the input (assuming the values are not interconnected).

我将给出一个小例子来描述为什么几个类比一个整体结构要好。让我们开始计算需要编写的测试,以覆盖上面的函数。有18个参数作为输入(3布尔值)。因此,我们需要至少15个测试来验证输入(假设这些值没有互连)。

The overall number of tests is impossible to be calculated without the implementation, but we can have an idea of the magnitude. Let take the lower bound all the input can be treat as boolean the number of possible combination are 2^18 so around 262000 tests.

在没有实现的情况下,不可能计算出总的测试数量,但是我们可以有一个大小的概念。让把下界的所有输入可以治疗为布尔可能组合的数量是2 ^ 18所以大约262000个测试。

Now, what happen if we split the input in several objects?

现在,如果我们将输入分割成几个对象会发生什么?

First of all, the code to validate the input is moved away from the function to the body of every single object (and it can be reused).

首先,验证输入的代码将从函数转移到每个对象的主体(并且可以重用它)。

But more importantly the number of tests will collapse, let say in group of four (4,4,4 and 4 params per object) the total number of tests is only:

但更重要的是,测试的数量将会减少,假设在四组(每个对象有4、4、4和4个参数)中,测试的总数仅为:

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

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

The fifth attributes is due to the permutation of the objects with themselves.

第五个属性是由于对象本身的排列。

So, what is more cost demanding? Write thousand of tests or few more classes?

那么,什么是更高的成本要求呢?写上千个测试或者更多的类?

Obviously, this is a crude simplification, however, it will underlying the core of the problem. A clutter interface is not just matter of style or an inconvenient for the developer it is a true impediment to produce quality code.

显然,这是一个粗略的简化,但是,它将成为问题的核心。凌乱的界面不仅是风格的问题,对开发人员来说也不方便,它是产生高质量代码的真正障碍。

This is the most important lesson I ever learnt in my career as a professional developer: "Big classes and fat interfaces are evil". That's just my heuristic version of the single responsibility principle (I have notice that the SRP can be tricky to get it right, what it seems reasonable to be single responsibility it can be not quite the same after a hour coding, so I used some heuristic rule to help me to revaulate my initial choices).

这是我作为专业开发人员的职业生涯中学到的最重要的一课:“大类和大接口是邪恶的”。这只是我启发式版本的单一责任原则(我注意到SRP可以很难使它正确,它似乎合理的单一责任可以编码一个小时后,不一样了,所以我使用了一些启发式规则来帮助我revaulate初始选择)。

#4


13  

Or you could use a fluent interface. It would look like this:

或者你可以使用流畅的界面。它看起来是这样的:

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

This is applicable if you have default values for your specified parameters or it is allowed to have a partially constructed script.

如果指定参数具有默认值,或者允许使用部分构造的脚本,则可以使用这种方法。

#5


8  

Ignoring the possibility or desirability of changing the function or program in some way as to reduce the number of parameters...

忽略了以某种方式更改函数或程序以减少参数数量的可能性或可取性……

I have seen coding standards that specify how long parameter lists should be formatted, for cases where refactoring is not possible. One such example is using double indentations and one parameter per line (Not for all functions - only for those that have multiple-lines of parameters).

我看到过一些编码标准,这些标准规定了在不可能进行重构的情况下,参数列表的格式应该有多长。一个这样的例子是每行使用双缩进和一个参数(不是针对所有的函数——只针对那些具有多行参数的函数)。

E.g.

如。

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);

The point here is to create a consistent layout and look for all functions with a large number of parameters.

这里的要点是创建一个一致的布局,并查找具有大量参数的所有函数。

#6


1  

A bit late here, but since nobody has done it yet, I'd like to point out an obvious aspect of the issue: to me, a function which takes so many arguments is likely to do a lot of computation, so consider the possibility of decomposing it in smaller functions as a first step.

有点晚,但是因为没有人做过,我想指出一个明显的方面的问题:对我来说,一个函数有很多参数可能会做大量的计算,所以考虑分解的可能性较小的函数作为第一步。

This should help you structuring your data.

这将有助于您结构化数据。

#1


72  

If all these parameters are meaningfully related, pack them in a structure.

如果所有这些参数都是有意义的相关,那么将它们打包到一个结构中。

#2


41  

Put them in a struct

Create a structure

创建一个结构

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

and put all the parameters in there. You can actually provide default values for the initialization of the struct as well by implementing a default constructor or, in C++11, by providing default initialization of individual members. You can then change the values that are not supposed to be defaulted. This selective picking of non-default parameters is not possible for a function call with lots of parameters in C++.

把所有的参数都放进去。通过实现默认构造函数,或者在c++ 11中,通过提供单个成员的默认初始化,您可以为结构体的初始化提供默认值。然后可以更改不应该默认的值。对于c++中有很多参数的函数调用,这种选择性地选择非默认参数是不可能的。

Making the interface nice for the caller

Yet, the usage is a little ugly, since you have to create a temporary name object, then change the values that should not be default and then pass the object to the function:

但是,这种用法有点难看,因为您必须创建一个临时名称对象,然后更改不应该默认的值,然后将对象传递给函数:

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

If you call that function in several different places, it makes sense to avoid this uglyness by providing mutating member functions that can be chained:

如果您在几个不同的地方调用该函数,那么通过提供可以链接的突变成员函数来避免这种丑陋性是有意义的:

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

Then calling code can write

然后调用代码就可以写了

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

without the above uglyness. This avoids the named object that is only used once.

没有上述uglyness。这避免了只使用一次的命名对象。

#3


17  

I personally do not believe that moving all the arguments in one struct will make the code much better. You just move dirt under the carpet. When you are going to deal with the creation of the struct you have the same problem.

我个人并不认为将所有参数移动到一个结构中会使代码更好。你只要把脏东*在地毯下面就行了。当你要处理结构的创建时你会遇到同样的问题。

The question is how much reusable this struct will be? If you end up with a 18 parameters for one function call something it is not quite right in your design. After further analysis you may discover that those parameters can be group in several different classes and those classes could be aggregated to one single object that will be the input of your function. You may want also prefer classes to struct in order to protect your data.

问题是这个结构可以重用多少?如果你最终得到一个函数的18个参数,那么你的设计就不太合适了。在进一步分析之后,您可能会发现这些参数可以在几个不同的类中进行分组,这些类可以聚合到一个对象中,该对象将作为函数的输入。为了保护您的数据,您可能还想要类而不是结构类。

EDIT

编辑

I will give you a small example to describe why several classes are better than one monolithic struct. Let's start counting the tests that you need to write to cover the function above. There are 18 parameters as input (3 boolean). So we are going to need at least 15 tests only to validate the input (assuming the values are not interconnected).

我将给出一个小例子来描述为什么几个类比一个整体结构要好。让我们开始计算需要编写的测试,以覆盖上面的函数。有18个参数作为输入(3布尔值)。因此,我们需要至少15个测试来验证输入(假设这些值没有互连)。

The overall number of tests is impossible to be calculated without the implementation, but we can have an idea of the magnitude. Let take the lower bound all the input can be treat as boolean the number of possible combination are 2^18 so around 262000 tests.

在没有实现的情况下,不可能计算出总的测试数量,但是我们可以有一个大小的概念。让把下界的所有输入可以治疗为布尔可能组合的数量是2 ^ 18所以大约262000个测试。

Now, what happen if we split the input in several objects?

现在,如果我们将输入分割成几个对象会发生什么?

First of all, the code to validate the input is moved away from the function to the body of every single object (and it can be reused).

首先,验证输入的代码将从函数转移到每个对象的主体(并且可以重用它)。

But more importantly the number of tests will collapse, let say in group of four (4,4,4 and 4 params per object) the total number of tests is only:

但更重要的是,测试的数量将会减少,假设在四组(每个对象有4、4、4和4个参数)中,测试的总数仅为:

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

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

The fifth attributes is due to the permutation of the objects with themselves.

第五个属性是由于对象本身的排列。

So, what is more cost demanding? Write thousand of tests or few more classes?

那么,什么是更高的成本要求呢?写上千个测试或者更多的类?

Obviously, this is a crude simplification, however, it will underlying the core of the problem. A clutter interface is not just matter of style or an inconvenient for the developer it is a true impediment to produce quality code.

显然,这是一个粗略的简化,但是,它将成为问题的核心。凌乱的界面不仅是风格的问题,对开发人员来说也不方便,它是产生高质量代码的真正障碍。

This is the most important lesson I ever learnt in my career as a professional developer: "Big classes and fat interfaces are evil". That's just my heuristic version of the single responsibility principle (I have notice that the SRP can be tricky to get it right, what it seems reasonable to be single responsibility it can be not quite the same after a hour coding, so I used some heuristic rule to help me to revaulate my initial choices).

这是我作为专业开发人员的职业生涯中学到的最重要的一课:“大类和大接口是邪恶的”。这只是我启发式版本的单一责任原则(我注意到SRP可以很难使它正确,它似乎合理的单一责任可以编码一个小时后,不一样了,所以我使用了一些启发式规则来帮助我revaulate初始选择)。

#4


13  

Or you could use a fluent interface. It would look like this:

或者你可以使用流畅的界面。它看起来是这样的:

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

This is applicable if you have default values for your specified parameters or it is allowed to have a partially constructed script.

如果指定参数具有默认值,或者允许使用部分构造的脚本,则可以使用这种方法。

#5


8  

Ignoring the possibility or desirability of changing the function or program in some way as to reduce the number of parameters...

忽略了以某种方式更改函数或程序以减少参数数量的可能性或可取性……

I have seen coding standards that specify how long parameter lists should be formatted, for cases where refactoring is not possible. One such example is using double indentations and one parameter per line (Not for all functions - only for those that have multiple-lines of parameters).

我看到过一些编码标准,这些标准规定了在不可能进行重构的情况下,参数列表的格式应该有多长。一个这样的例子是每行使用双缩进和一个参数(不是针对所有的函数——只针对那些具有多行参数的函数)。

E.g.

如。

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);

The point here is to create a consistent layout and look for all functions with a large number of parameters.

这里的要点是创建一个一致的布局,并查找具有大量参数的所有函数。

#6


1  

A bit late here, but since nobody has done it yet, I'd like to point out an obvious aspect of the issue: to me, a function which takes so many arguments is likely to do a lot of computation, so consider the possibility of decomposing it in smaller functions as a first step.

有点晚,但是因为没有人做过,我想指出一个明显的方面的问题:对我来说,一个函数有很多参数可能会做大量的计算,所以考虑分解的可能性较小的函数作为第一步。

This should help you structuring your data.

这将有助于您结构化数据。