有多少参数太多了?

时间:2008-10-06 16:14:49

标签: parameters language-agnostic

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

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

  

谨慎的程序员避免捆绑数据,这在逻辑上是必要的。

因此,如果您的例程有太多参数或者您使用结构来伪装一个大参数列表,那么您可能做错了什么。也就是说,你没有保持松耦合。

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

34 个答案:

答案 0 :(得分:162)

尽管第一修正案保证言论自由,但什么时候被认为是一种可以受到监管的东西?据波特斯图尔特大法官说,“当我看到它时,我就知道了。”这同样适用于此。

我讨厌像这样制定严格而快速的规则,因为答案的变化不仅取决于项目的规模和范围,而且我认为它甚至会改变到模块级别。根据你的方法正在做什么,或者类应该代表什么,很可能2个参数太多,并且是耦合过多的症状。

我建议首先提出这个问题,并尽可能多地解决你的问题,你真的知道这一切。这里最好的解决方案不是依靠一个快速而快速的数字,而是在同行中寻找设计评论和代码评审,以确定你的内聚力和紧耦合低的领域。

永远不要害怕向同事展示你的工作。如果您害怕,那可能是代码出现问题的更大迹象,并且您已经知道

答案 1 :(得分: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 * 帮助你。

* 其他编码助理可供使用!

答案 2 :(得分:106)

Clean Code中,Robert C. Martin在这个主题上专门写了四页。这是要点:

  

a的理想参数数量   函数为零(niladic)。接下来   一个(monadic),紧接着两个   (二元)。三个论点(三元组)   应该尽可能避免。更多   超过三(polyadic)需要非常   特殊理由 - 然后   不管怎么说都不应该使用。

答案 3 :(得分:79)

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

请不要那样做!

(一般)。

答案 4 :(得分:38)

如果你开始不得不在心理上计算签名中的参数并将它们与调用相匹配,那么现在是重构的时候了!

答案 5 :(得分:31)

非常感谢您的所有答案:

  • 找到那些同样认为(就像我一样)5个参数对代码的理智有限的人来说有点令人惊讶。

  • 一般来说,人们倾向于同意3到4之间的限制是很好的经验法则。这是合理的,因为人们通常计算超过4件事情的时间不好。

  • 作为Milan points,平均而言,人们可以一次保持或多或少的7件事。但我认为你不能忘记,当你设计/维护/研究一个例程时,你必须记住更多的事情,而不仅仅是参数。

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

  • Nick 对此有some interesting thoughts。如果您不想阅读他的评论,我总结一下:简而言之,取决于

      

    我讨厌像这样制定严格而快速的规则,因为答案的变化不仅取决于项目的规模和范围,而且我认为它甚至会改变到模块级别。根据你的方法正在做什么,或者类应该代表什么,很可能2个参数太多,并且是耦合过多的症状。

    这里的道德是不要害怕向同行展示你的代码,与他们讨论并尝试“找出你的内聚力和紧密耦合的区域”

  • 最后,我认为 wnoise 非常赞同Nick,并总结了他对编程艺术this poetical vision(见下文评论)的讽刺性贡献:

      

    编程不是工程。代码组织是一门艺术,因为它取决于人为因素,而人为因素在很大程度上依赖于任何硬性规则的背景。

答案 6 :(得分:16)

这个答案假设是OO语言。如果你没有使用它 - 跳过这个答案(换句话说,这不是一个与语言无关的答案。

如果传递超过3个左右的参数(特别是内在类型/对象),则不是“太多”,而是您可能错过了创建新对象的机会。

查找传递给多个方法的参数组 - 即使传递给两个方法的组几乎可以保证你在那里有一个新对象。

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

答案 7 :(得分:13)

似乎还有其他考虑因素而不仅仅是数字,以下是我想到的一些因素:

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

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

答案 8 :(得分:12)

Alan Perlis的着名编程之一(在ACM SIGPLAN Notices 17(9),1982年9月中叙述)指出“如果你有一个包含10个参数的程序,你可能会错过一些。”

答案 9 :(得分:11)

Steve McConnell在代码完成中表示,你应该

  

限制例程的数量   参数大约七

答案 10 :(得分:9)

我普遍同意5,但是,如果我需要更多的情况并且这是解决问题的最明确的方法,那么我会使用更多。

答案 11 :(得分:9)

对我来说,当列表在我的IDE上跨过一行时,它的一个参数太多了。我希望在一行中看到所有参数而不会破坏目光接触。但这只是我个人的偏好。

答案 12 :(得分:8)

短期记忆中的七件事情?

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

答案 13 :(得分:7)

Worst 5 Code Snippets中,检查第二个,“这是一个构造函数”。它有超过37⋅4≈150个参数:

  

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

     

constructor with over 150 parameters

答案 14 :(得分:6)

超过必要的一个。我并不是说要搞笑,但有一些功能必然需要很多选择。例如:

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

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

答案 15 :(得分:5)

根据 Perl最佳实践,3表示没问题,4表示太多。这只是一个指导方针,但在我们的商店里,这是我们努力坚持的。

答案 16 :(得分:5)

97 sounds just about right.

任何更少,你失去灵活性。

答案 17 :(得分:5)

你应该考虑的一个相关问题是cohesive例程是怎样的。大量的参数可能是一种气味,告诉你例程本身正在试图做太多,因此它的凝聚力是可疑的。我同意很难和快速数量的参数可能是不可能的,但我猜想高内聚程序会意味着参数数量很少。

答案 18 :(得分:5)

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

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

答案 19 :(得分:4)

作为一般经验法则,我停在三个参数上。更多,是时候传递参数数组或配置对象,这也允许在不更改API的情况下添加将来的参数。

答案 20 :(得分:4)

参数列表的长度限制只是一个限制。限制意味着应用暴力。这听起来很有趣,但即使在编程时你也可能非暴力。只需让代码规定规则即可。很明显,如果你有很多参数,函数/类方法的主体将足够大,可以使用它们。大代码片段通常可以重构并拆分成更小的块。因此,您可以获得许多参数作为免费奖励的解决方案,因为它们被分成较小的重构代码。

答案 21 :(得分:4)

我从性能角度指出的一点是,根据您将参数传递给方法的方式,按值传递大量参数会降低程序速度,因为每个参数都必须复制然后放在堆栈上。

使用单个类来包含所有参数会更好地工作,因为通过引用传递的单个参数将更优雅,更清晰,更快!

答案 22 :(得分:3)

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

  1. 你的方法做得太多了,你需要重构。
  2. 您可能需要考虑使用集合或某些数据结构。
  3. 重新思考你的课堂设计,也许有些事情不需要传递。
  4. 从易于使用或易于阅读代码的角度来看,我认为当你需要对自己的方法签名进行“自动换行”时,应该让你停下来思考,除非你感到无助和做出签名的所有努力较小导致无结果。过去和现在的一些非常好的图书馆使用超过4-5个婴儿车。

答案 23 :(得分:3)

我的经验法则是,我需要能够记住足够长的参数来查看呼叫并告诉它它的作用。因此,如果我不能查看该方法,然后转到方法调用并记住哪个参数做了什么,那就太多了。

对我而言,相当于约5,但我并不那么聪明。您的里程可能会有所不同。

您可以创建一个具有属性的对象来保存参数,如果超出您设置的限制,则传入该参数。请参阅Martin Fowler的Refactoring一书以及关于使方法调用更简单的章节。

答案 24 :(得分:1)

如果我在一个例程中有7-10个参数,我会将它们捆绑到一个新类中,如果该类只是一堆带有getter和setter的字段 - 新类必须除了shuffle值之外的东西。否则我宁愿忍受长参数列表。

答案 25 :(得分:1)

众所周知,平均而言,人们可以同时保留7 +/- 2件事。我喜欢用参数原理。假设程序员都是高于平均水平的聪明人,我会说10+都太多了。

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

答案 26 :(得分:1)

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

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

答案 27 :(得分:1)

我的回答是关于函数调用的频率。

如果它是一个只被调用过一次的初始化函数,那就让它需要10个或更多的参与者。

如果每帧调用一次,那么我倾向于创建一个结构,只是传递一个指针,因为它往往更快(假设你不是每次都重建结构)。

答案 28 :(得分:1)

根据亚马逊成名的杰夫贝索斯的说法,只有two pizzas可以喂养:

答案 29 :(得分:1)

我同意罗伯特·马丁在清洁代码中的引用(as cited above):参数越少越好。超过5-7个参数和方法调用变得非常难以理解。如果某些参数是可选的(因此采用空值),或者如果所有参数具有相同的类型(使得更难以确定哪个参数是哪个),事情会变得特别糟糕。如果您可以将参数捆绑到客户和帐户等有凝聚力的域对象中,那么您的代码将更加愉快。

有一个边缘情况:如果你有一个方法调用,它采用可变数量的参数形成一个逻辑集,那么拥有更多参数的认知开销就会减少。例如,您可能需要一种方法,根据重试之间的毫秒数指定多个HTTP请求重试次数。在1s,2s和3s间隔的三次重试可以指定为:

retries(1000, 2000, 3000)

在这种有限的情况下,为呼叫添加更多参数并不会增加心理超负荷。

另一个考虑因素是您的语言是否支持命名参数列表,并允许您省略可选参数。较大的命名参数列表比较大的未命名参数列表更容易理解。

但我仍然偏向于更少而不是更多的参数。

答案 30 :(得分:1)

我同意3是可以的,4是太多的指南。有超过3个参数,你不可避免地要做一个以上的任务。应该将多个任务分成多个单独的方法。

但是,如果我看一下我所研究的最新项目,那么例外情况会比比皆是,大多数情况下很难达到3个参数。

答案 31 :(得分:0)

IMO,长参数列表的理由是数据或上下文本质上是动态的,想想printf();使用varargs的一个很好的例子。处理此类情况的更好方法是通过传递流或xml结构,这再次最小化参数的数量。

一台机器肯定不会介意大量的参数,但开发人员也会考虑维护开销,单元测试用例和验证检查的数量。设计人员也讨厌冗长的args列表,更多的参数意味着更改接口定义,无论何时进行更改。关于耦合/内聚的问题来自上述方面。

答案 32 :(得分:0)

我认为实际数字实际上取决于对函数上下文的逻辑意义。我同意大约4-5个参数开始变得拥挤。

在设置标志的情况下,处理这种情况的一个好方法是枚举值并将它们组合在一起。

答案 33 :(得分:0)

我会说,只要你有超过2-4的重载,如果你需要的话你可以升高。