【问题标题】:Elegant way to pass multiple arguments to a function将多个参数传递给函数的优雅方式
【发布时间】:2014-09-17 02:22:57
【问题描述】:

我有一个看起来像这样的函数:

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)的向量并将它们作为参数传递给这个函数吗?

【问题讨论】:

  • 我不鼓励使用向量,结构会是更合适的解决方案。
  • 还有 boost 命名参数,如果你有很多有用的默认值
  • 将它们分组为一个或多个structs。另外,不要忘记对字符串使用const 引用类型,以防您不在函数中修改它,这样可以提高速度。
  • 如果这个函数的唯一目的是处理一个结构,为什么不把它变成那个结构的方法呢?
  • 似乎没有人提到的是它们都是按值传递的 :( 通过引用传递可以避免这么多的分配

标签: c++ function argument-passing


【解决方案1】:

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

【讨论】:

  • 有些会,所以做两三个。或者为这个方法创建一个特定的参数对象(它们是相关的,因为它们是这个方法的参数)。
  • 如果您的参数没有有意义的相关性,您的函数可能会尝试同时满足多个目的。
  • 请注意,有很多很多未实现的参数会导致您的函数是否做太多事情的问题。
  • @dynamic 可能。 structs 非常适合您真的只想要公共字段的集合,就像这里一样。对我来说,class 表明可能有私有状态正在被管理。另一方面,struct 表示平坦、简单的数据,这正是这里所需要的。
  • 是否也有可能script 应该是一个对象,你的每个参数都应该是属性,而generate 应该是类中的一个方法?
【解决方案2】:

把它们放在struct

创建一个结构

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

并把所有的参数放在那里。实际上,您也可以通过实现默认构造函数或在 C++11 中通过提供各个成员的默认初始化来为 struct 的初始化提供默认值。然后,您可以更改不应默认的值。对于在 C++ 中具有大量参数的函数调用,这种选择性地选择非默认参数是不可能的。

使界面对调用者友好

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

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

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

【讨论】:

  • 因为我有一段时间没有做过任何 C++,所以我最初将GenerateScriptParams & GenerateScriptParams::setNet 读作AND 介于两个GenerateScriptParams 实例之间——这让我很困惑。我个人更喜欢GenerateScriptParams& GenerateScriptParams::setNet,因为在考虑函数的返回类型时,与号绑定到类名。
【解决方案3】:

我个人不相信将所有参数移动到一个结构中会使代码变得更好。您只需将污垢移到地毯下即可。当您要处理结构的创建时,您会遇到同样的问题。

问题是这个结构有多少可重用性?如果您最终为一个函数调用提供了 18 个参数,那么这在您的设计中并不完全正确。经过进一步分析,您可能会发现这些参数可以分组到几个不同的类中,并且这些类可以聚合到一个对象中,作为函数的输入。您可能还希望使用类而不是 struct 来保护您的数据。

编辑

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

如果没有实现,就无法计算出测试的总数,但我们可以对数量级有所了解。让所有输入的下限都可以视为布尔值,可能组合的数量为 2^18,因此大约 262000 个测试

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

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

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

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

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

那么,什么对成本要求更高?编写数千个测试或更多类?

显然,这是一个粗略的简化,但是,它将成为问题核心的基础。 杂乱的界面不仅仅是样式问题或对开发人员的不便,它是生成高质量代码的真正障碍

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

【讨论】:

  • -1 创建“几个不同的类”来解决一个相对简单的问题是通过屋顶设计的。
  • @PaulManta 我已经详细说明了“几个不同的类”的好处
  • +1 ;这实际上是指出 OP 代码真正问题的唯一答案;在函数调用中使用太多参数使代码混乱的问题几乎不是“一个相对简单的问题”——即使是 EJ 中的 Bloch,2nd 也提到了这个项目作为使用例如的激励。构建器模式和专门的类。 ;对于“大类和胖接口是邪恶的”,我会给你另一个 +1 - 我在 OOP 编程生涯中学到了完全相同的课程(艰难的方式)。模块化取决于级联组合模型,而不是扁平模型!
【解决方案4】:

或者您可以使用fluent interface。它看起来像这样:

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

如果您有指定参数的默认值或允许有部分构造的脚本,这适用。

【讨论】:

    【解决方案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);
    

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

    【讨论】:

    • 我想我将按照 Quentin 的建议将这些参数打包到一个结构中。但是感谢您解释如何格式化这些函数。
    • 前面应该放多少个空格,例如bool net,? 4个还是8个?如果为 4,它可能与 { ... } 中的实现无法区分。
    【解决方案6】:

    这里有点晚了,但由于还没有人做过,我想指出这个问题的一个明显方面:对我来说,一个需要这么多参数的函数可能会做很多计算,所以作为第一步,考虑将其分解为更小的函数的可能性。

    这应该可以帮助您构建数据。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-11-10
      • 1970-01-01
      • 2016-04-02
      • 1970-01-01
      • 2019-01-13
      • 2015-10-12
      • 1970-01-01
      • 2015-08-19
      相关资源
      最近更新 更多