将多个参数传递给函数的优雅方式
Posted
技术标签:
【中文标题】将多个参数传递给函数的优雅方式【英文标题】: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 命名参数,如果你有很多有用的默认值 将它们分组为一个或多个struct
s。另外,不要忘记对字符串使用const
引用类型,以防您不在函数中修改它,这样可以提高速度。
如果这个函数的唯一目的是处理一个结构,为什么不把它变成那个结构的方法呢?
似乎没有人提到的是它们都是按值传递的 :( 通过引用传递可以避免这么多的分配
【参考方案1】:
如果所有这些参数都有意义相关,请将它们打包到一个结构中。
【讨论】:
有些会,所以做两三个。或者为这个方法创建一个特定的参数对象(它们是相关的,因为它们是这个方法的参数)。 如果您的参数没有有意义的相关性,您的函数可能会尝试同时满足多个目的。 请注意,有很多很多未实现的参数会导致您的函数是否做太多事情的问题。 @dynamic 可能。struct
s 非常适合您真的只想要公共字段的集合,就像这里一样。对我来说,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】:
这里有点晚了,但由于还没有人做过,我想指出这个问题的一个明显方面:对我来说,一个需要这么多参数的函数可能会做很多计算,所以作为第一步,考虑将其分解为更小的函数的可能性。
这应该可以帮助您构建数据。
【讨论】:
以上是关于将多个参数传递给函数的优雅方式的主要内容,如果未能解决你的问题,请参考以下文章