多少个参数太多?[关闭]


228

例程可以有参数,这不是新闻。您可以根据需要定义任意数量的参数,但是参数太多会使您的例程难以理解和维护。

当然,您可以使用结构化变量作为解决方法:将所有这些变量放在单个结构中,然后将其传递给例程。实际上,使用结构简化参数列表是Steve McConnell在Code Complete中描述的技术之一。但正如他所说:

谨慎的程序员避免捆绑数据超出逻辑上的必要性。

因此,如果您的例程中有太多参数,或者您使用结构来掩盖大参数列表,则可能是您做错了什么。也就是说,您不会使耦合松动。

我的问题是,何时可以考虑参数列表太大?我认为超过5个参数太多了。你怎么看?


1
只是在这里引用这个问题,这是多少个参数太多的一个实际例子 ……
Gideon

5
在JavaScript 65537中,参数太多了:jsfiddle.net/vhmgLkdm
Aadit M Shah,

仅查看apache http组件,几乎不可能从中构建出一些动态的东西
Andrew Scott Evans

Answers:


162

尽管第一条修正案保证了言论自由,但什么时候被认为如此淫秽而又可被监管?根据波特·斯图尔特大法官的说法,“当我看到它时便知道。” 这里也一样。

我讨厌制定如此严格的规则,因为答案不仅会根据项目的大小和范围而变化,而且我认为它甚至会在模块级别变化。根据您的方法正在执行的操作或类应表示的内容,两个参数很可能太多,并且是过多耦合的征兆。

我建议首先提出问题,并尽可能多地限定您的问题,以使您真正了解所有这一切。最好的解决方案不是依靠硬性数字,而是寻求同行之间的设计审查和代码审查,以识别内聚力和紧密耦合程度低的区域。

永远不要害怕向同事展示您的工作。如果您害怕这样做,那可能是更大的信号,表明您的代码有问题,并且您已经知道了


一个很好的经验法则是CPU寄存器的数量,因为这样一来,编译器将被迫在堆栈上分配它们。
Michaelangel007

1
@ Michaelangel007关于您要评论的答案,您不应这样说,因为它表明没有规则。此外,参数的数量是可读性的问题,而不是性能的问题。
clemp6r

1
@ clemp6r错误- 两者都是。编译人员必须一直处理“寄存器溢出”。的唯一权威的答案是检查你的编译器生成的组装。在同一平台上,寄存器的数量不会发生神奇的变化。zh.wikipedia.org/wiki/Register_allocation
Michaelangel007

124

如果某些参数是冗余的,则函数只能具有太多参数。如果使用了所有参数,则该函数必须具有正确数量的参数。采取以下常用功能:

HWND CreateWindowEx
(
  DWORD dwExStyle,
  LPCTSTR lpClassName,
  LPCTSTR lpWindowName,
  DWORD dwStyle,
  int x,
  int y,
  int nWidth,
  int nHeight,
  HWND hWndParent,
  HMENU hMenu,
  HINSTANCE hInstance,
  LPVOID lpParam
);

这是12个参数(如果将x,y,w和h捆绑为矩形,则为9),并且还有从类名派生的参数。您将如何减少呢?您是否想进一步减少数量?

不要让参数的数量困扰您,只需确保它是合乎逻辑的并且有据可查,并让intellisense *可以为您提供帮助。

*可以使用其他编码助手!


37
我对此表示赞成。我对所有其他回答“ 3”和“ 4”感到惊讶!正确的答案是:最低要求,有时可能很多。
托尼·安德鲁斯

16
如果今天设计该功能,则外观可能会有所不同。x,y,nWidth和nHeight可以捆绑在Rectangle对象中。style和xStyle可以合并为一组枚举或字符串。现在您只有8个参数。
finnw

16
@finnw如果该功能是今天设计的?它已经被重新设计。表格f = new Form(); 有0个参数。
尼克

36
这是C winapi。我想不出更糟糕的例子。
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳ 2011年

17
不,不。这是程序风格。Windows是对象,因此现在已过时。今天,这将通过聚合类而不是一堆参数来解决。好的设计的直觉规则是,如果您不能用一个简单的句子来描述功能(包括参数),那么它的设计就很差。我相信情况就是如此。
JanTuroň2012年

106

罗伯特·C·马丁(Robert C. Martin)在《清洁规范》中用了四页的篇幅论述了这一主题。这是要点:

函数的理想参数个数为零(尼拉度)。接下来是一个(单声道),紧接着是两个(双声道)。在可能的情况下,应避免使用三个参数(三重性)。超过三个(polyadic)需要非常特殊的理由-因此无论如何都不应使用。


2
我怀疑它是否包含“ this”,因为这是执行的上下文。在函数式编程中,上下文是全局的,在oop中,上下文是您要在其上应用方法的对象。另外,如果您在参数列表中包含“ this”,则不可能有0个参数(理想)。
汤姆”,

1
不,它不包含“ this”。
Patrick McElhaney,

20
此外,Martin应该与C ++标准委员会保持联系。<algorithm>的一半采用三个以上的参数,因为只有一个迭代器范围已经是2。这只是使用迭代器进行编程的本质(即使用STL集合进行通用编程)。
史蒂夫·杰索普

3
@SteveJessop:<algorithm>的缺点是它总是在切片上工作。如果要重新设计算法和集合,我将使算法始终占据整个集合,并且您将获得一种切片集合视图的方法,以允许算法在零件上工作。或者,我还将定义一个速记覆盖,以使其易于处理常见情况。
Lie Ryan

3
@LieRyan:如果我<algorithm>要重新设计,我会让它作用于范围对象。集合将是范围,但并非所有范围都将是集合。实际上,提振已经做到了。不管怎样,我的观点是,大量广泛使用的库忽略了这个建议,所以这是最糟糕的保证,如果你做太多发生在你身上,是很多的你的百万用户将有轻微简化你的界面;-)鼓捣
史蒂夫Jessop 2013年

79

我过去使用的一些代码使用全局变量只是为了避免传递过多的参数。

请不要那样做!

(通常)


我正在使用过的类成员执行一些代码来实现相同的目的。那时我是一名C程序员,我问,为什么会有这么多的全局变量,为什么输入/输出不能明确地成为参数?
user3528438

38

如果您开始必须认真考虑签名中的参数并将其与调用匹配,那么该重构一下了!


这是一个很好的答案。如果参数是按逻辑组织的(x,y,w,h),则很容易以正确的顺序记住所有参数。很难确定将FILE指针放在putc(只有两个参数)中的位置,尤其是因为fprintf相反。
user877329'May

最好的答案。说得很好
Anwar

31

非常感谢您的所有回答:

  • 令人惊讶的是,找到像我一样也认为5个参数是代码健全性的一个很好的限制的人。

  • 通常,人们倾向于认为3到4之间的限制是很好的经验法则。这是合理的,因为人们通常很难计算4件以上的事情。

  • 正如米兰所指出的,平均每个人一次最多可以保留7件事。但是我认为您不能忘记,在设计/维护/研究例程时,您不仅要记住参数,还必须记住更多的事情。

  • 有人认为例程应包含所需的尽可能多的参数。我同意,但仅适用于一些特定情况(调用OS API,优化很重要的例程等)。我建议通过在可能的时候在这些调用之上添加一个抽象层来隐藏这些例程的复杂性。

  • 尼克对此有一些有趣的想法。如果您不想阅读他的评论,我为您总结:简而言之,这取决于

    我讨厌制定如此严格的规则,因为答案不仅会根据项目的大小和范围而变化,而且我认为它甚至会在模块级别变化。根据您的方法正在执行的操作或类应表示的内容,两个参数很可能太多,并且是过多耦合的征兆。

    这里的道义是不要害怕向同行展示您的代码,与他们讨论并尝试“识别出内聚力低且耦合紧密的区域”

  • 最后,我认为无奈与尼克非常吻合,并以这种对编程艺术的诗意的见解(见下面的评论)总结了他的讽刺性贡献:

    编程不是工程。代码的组织是一门艺术,因为它取决于人为因素,对于任何硬性规则,人为因素都过于依赖于上下文。


16

该答案假定使用OO语言。如果您不使用它,请跳过此答案(换句话说,这与语言无关)。

如果您传递的参数超过3个左右(尤其是固有类型/对象),则不是因为它“太多”,而是您可能错过了创建新对象的机会。

寻找可以传递给多个方法的参数组-即使是传递给两个方法的组也几乎可以保证您应该在那里有一个新对象。

然后,您将功能重构到新对象中,您将不敢相信它对您的代码和对OO编程的理解有多大帮助。


请命名一种不使用例程或参数的语言。我想不到一个,即使汇编也可以被认为具有例程(标签)。
奥龙

x86程序集肯定具有例程(调用/返回)。其他人可能需要cmp / jmp组合。
Brian Knoblauch

对不起,“退回”,而不是“退回”。感叹 已经漫长的一天了。
Brian Knoblauch

抱歉,对Auron的模棱两可的措词,我相信已解决。是我的回答是针对特定语言的,而不是问题。
Bill K

1
并非所有语言都允许传递结构。同样,传递结构意味着接收方法必须具有处理该结构的代码,因此可能需要更多参数。另外,在非oo语言中,通常需要一个额外的参数-所有OO语言都具有“ this”的“ Hidden”参数,否则应将其传递(或者,以更恐怖的语言/设计,可能是全局的)无障碍)
Bill K,

13

似乎除了数字之外还有其他考虑因素,以下是一些要考虑的问题:

  1. 与功能主要目的的逻辑关系与一次性设置的关系

  2. 如果它们只是环境标志,则捆绑非常方便


12

艾伦·帕利斯(Alan Perlis)著名的编程摘要之一(在1982年9月的ACM SIGPLAN Notices 17(9)中有所叙述)指出:“如果您的程序具有10个参数,则可能会错过一些参数。”



9

对我来说,当列表在我的IDE上越过一行时,则它是一个太多的参数。我想在一行中查看所有参数而又不中断目光接触。但这只是我的个人喜好。


直到您和一些开发人员一起尝试foo(int a,float b,string c,double d),这是很好的。我猜最好避免与他们合作。:D
放射科医生

4
如果其他人给了类可笑的长名称,我不会让那影响我定义或调用例程的方式。
finnw

9
我可以向您介绍“回车”吗?

3
当列表在您的IDE上越过一行时,则您的显示器太小而无法与该代码一起使用,因此您应该购买水平分辨率更高的显示器。换行或减少参数数量只是无法解决根本问题的解决方法,根本问题是您的监视器对于该代码库而言太小了!
Kaiserludi

9

我通常同意5,但是,如果存在需要更多的东西,并且这是解决问题的最明确的方法,那么我会使用更多的东西。


8

短期记忆中的七件事?

  1. 功能名称
  2. 函数的返回值
  3. 功能目的
  4. 参数1
  5. 参数2
  6. 参数3
  7. 参数4

好。这是一个经验法则。在对函数的主体进行编码时,您不必在意它的名称,返回值和它的用途是紧密相关的。
奥龙2010年

7
8.参数顺序
Eva 2013年

7

最差的5个代码段中,检查第二个代码段,“这是一个构造函数”。它具有超过37⋅4≈150个参数:

在这里,一个程序员写了这个构造函数[...]有些人可能会认为它是一个很大的构造函数,但是他使用了eclipse自动代码生成工具[。] NOO,在这个构造函数中,我发现了一个小错误,这使我结论是该构造函数是手工编写的。(顺便说一下,这只是构造函数的顶部,不完整)。

具有超过150个参数的构造函数


这太可悲了……不了解“记录”或“结构”或“值对象”,它们将多个值捆绑在一起并赋予它们一个通用名称,以便您可以以可读的方式分层表示很多它们,就像走来走去一样穿了多年的鞋子,因为没有人告诉过你你甚至可以这样做
yeoman

6

比必要多一。我并不是说要成为glib,但是有些功能必然需要很多选择。例如:

void *
mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t offset);

有6个参数,每个参数都是必不可少的。此外,它们之间没有共同的联系来证明捆绑它们是合理的。也许您可以定义“ struct mmapargs”,但这会更糟。


好了,protflags可能已被卷起来,如果有人认为由设计师是5不知何故神奇的数字,是远好于6有点类似的方式,open结合了所有其他杂项标志的读/写模式。也许您可以offset通过指定映射部分从的当前搜索位置开始摆脱filedes。我不知道在任何情况下都可以访问您无法访问mmap的区域lseek,但是如果不是这样,则并非绝对必要。
Steve Jessop

...因此,我认为mmap这很好地说明了这一事实,即有些设计师和用户更喜欢一长串参数,而另一些设计师和用户则希望通过一些步骤来准备较少的参数,然后再进行调用。
史蒂夫·杰索普

1
@Steve:事先通过一个单独的调用来设置搜索位置会引入免费的比赛条件。对于某些API(OpenGL),影响调用的参数太多,您实际上必须使用状态,但是通常,每个调用应尽可能独立。远距离的行动是通往黑暗面的道路。
Ben Voigt

5

根据Perl Best Practices,3可以,4太多。这只是一个准则,但是在我们的商店中,这就是我们要坚持的原则。


5

我自己会在5个参数处绘制公共函数的限制。

恕我直言,长参数列表仅在专用/本地帮助函数中才可接受,这些函数只能从代码中的几个特定位置调用。在这种情况下,您可能需要传递很多状态信息,但是可读性并不是一个大问题,因为只有您(或维护您的代码并应了解模块基础知识的人)才需要关心调用该函数。


这就是为什么很多人此时会做出不是实际参数而是状态携带的一切,只是对象以字段(成员变量)形式存在的状态的原因。该对象将表示为一个类。它将被实例化一次或每次使用。有时,这样的对象只有一个包private或public方法,然后将工作委托给私有方法,每个方法都很少参数。现在很少有参数可用,因为配置和状态现在有适当的位置了:)
yeoman

5

您应该考虑的一个相关问题是例程的凝聚力。大量的参数可能是一种气味,它告诉您例程本身正在尝试执行过多操作,因此怀疑其内聚性。我同意,大量参数是不可能的,但我猜想,高内聚例程将意味着参数数量较少。



4

作为一般经验法则,我停止了三个参数。再也不需要传递参数数组或配置对象的时间了,这还允许在不更改API的情况下添加将来的参数。


如果API发生了变化,那么API实际上应该发生变化,不仅是在隐隐性变化(可能仍会发生不兼容)方面发生变化,而且不太明显。
08年

但是,如果您需要一个以上的参数来配置边缘情况,则不应使用API​​将其传播到不相关的组件
Eran Galperin

4

对参数列表的长度限制只是又一个限制。限制意味着施加暴力。听起来很有趣,但是即使编程也可以保持不暴力。只要让代码决定规则即可。显然,如果您有许多参数,则函数/类方法的主体将足够大以可以使用它们。大代码片段通常可以重构并分成较小的块。因此,您可以获得针对许多免费赠品的解决方案,因为它们被分配在较小的重构代码段中。


4

从性能的角度来看,我要指出的一件事是,取决于将参数传递给方法的方式,按值传递大量参数会使程序变慢,因为必须复制每个参数然后将它们放在堆栈上。

使用单个类来包含所有参数会更好,因为通过引用传递的单个参数会更优雅,更简洁,更快捷!


我给这个答案+1,因为它是唯一讨论除代码清洁度或任意限制(这都是主观的)之外的任何内容的+1。当您处于循环状态并在堆栈上上下推数十个参数时,有些人可能不会考虑您对堆栈所做的事情。如果处于循环中,则应考虑使用要编译的ABI中REGISTERS传递的参数数量。例如,在MS x64 ABI中,通过寄存器传递的最大args为4。“ System V” ABI(非Windows OS使用)使用更多寄存器,因此使用4个args可移植性
很强

3

根据我的说法,在某些情况下您可能会超过4个或某个固定数字。要注意的事情可能是

  1. 您的方法做得太多,您需要重构。
  2. 您可能要考虑使用集合或某些数据结构。
  3. 重新考虑您的班级设计,也许有些事情不需要传递。

从易于使用或易于阅读的角度来看,我认为当您需要对方法签名进行“自动换行”时,应该使您停下来思考,除非您感到无助,并且为使签名变小而付出的所有努力都会导致没有结果。过去和现在一些非常好的库使用4-5多个婴儿车。


3

我的经验法则是,我需要能够记住足够长的参数,以便查看呼叫并确定其作用。因此,如果我看不到该方法,然后转到方法的调用并记住哪个参数执行了该操作,则太多了。

对我来说,大约等于5,但我并不聪明。你的旅费可能会改变。

您可以创建一个带有属性的对象,以保存参数,并在超过设置的任何限制时将其传递给对象。请参阅Martin Fowler的《Refactoring》一书和有关使方法调用更简单的章节。


1

这在很大程度上取决于您所使用的环境。例如javascript。在javascript中,传递参数的最佳方法是使用具有键/值对的对象,这实际上意味着您只有一个参数。在其他系统中,最佳点是三点或四点。

最后,一切都归结为个人品味。


1

我同意3可以,4太多可以作为指导。使用3个以上的参数,您不可避免地要完成一项以上的任务。然后,一项以上的任务应分为单独的方法。

但是,如果我查看我正在从事的最新项目,则异常将比比皆是,并且大多数情况下很难归结为3个参数。


1

如果我在一个例程中有7-10个参数,我会考虑将它们捆绑到一个新类中,但是如果该类将只剩下一堆带有getter和setter的字段,则不会 -新类除了在和出来。否则,我宁愿忍受冗长的参数列表。


1
如果将它用在多个地方,则将其与仅数据类捆绑在一起,但是即使那样,我通常也会创建两个构造函数。
Leahn Novash

1

一个众所周知的事实是,人们平均一次可以保留7 +/- 2件事。我喜欢将该原理与参数一起使用。假设程序员都是聪明人,那么10岁以上的人太多了。

顺便说一句,如果参数在任何方面都相似,我会将它们放在向量或列表中,而不是结构或类中。


1

我的回答将基于函数被调用的频率。

如果它是只被调用一次的init函数,那么让它接受10个或更多的parm,谁在乎。

如果每帧调用多次,则我倾向于制作一个结构并仅将指针传递给它,因为这往往会更快(假设您并非每次都重建该结构)。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.