228

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

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

细心的程序员避免捆绑数据超出逻辑上的必要。

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

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

4

34 回答 34

162

尽管第一修正案保证言论自由,什么时候被认为如此淫秽以至于可以监管的东西?根据波特斯图尔特法官的说法,“我一看到就知道了。” 这里也一样。

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

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

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

于 2008-10-06T16:25:52.597 回答
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 个),还有从类名派生的参数。你会如何减少这个?你想把这个数字减少到更重要的程度吗?

不要让参数的数量困扰您,只需确保它是合乎逻辑且有据可查的,并让智能感知*帮助您。

*其他编码助手可用!

于 2008-10-06T16:37:25.257 回答
106

Clean Code中,Robert C. Martin 用了四页纸来讨论这个主题。这是要点:

函数的理想参数数量为零(niladic)。接下来是一个(单子),紧随其后的是两个(二元)。应尽可能避免使用三个参数(三元)。超过三个(多元)需要非常特殊的理由——然后无论如何都不应该使用。

于 2008-10-06T16:25:53.453 回答
79

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

请不要那样做!

(通常。)

于 2008-10-06T16:28:52.417 回答
38

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

于 2008-10-06T16:19:35.667 回答
31

非常感谢您的所有回答:

  • 发现有人也认为(像我一样)5 个参数是代码健全性的一个很好的限制,这有点令人惊讶。

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

  • 正如米兰指出的那样,平均而言,人们一次可以在脑海中保留或多或少的 7 件事。但我认为你不能忘记,当你在设计/维护/研究一个例程时,你必须记住的不仅仅是参数。

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

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

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

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

  • 最后,我认为wnoise非常同意尼克的观点,并以这种对编程艺术的诗意愿景(见下文评论)结束了他的讽刺贡献:

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

于 2008-10-06T21:14:36.770 回答
16

此答案假定为 OO 语言。如果您不使用一个 - 跳过这个答案(换句话说,这不是一个与语言无关的答案。

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

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

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

于 2008-10-06T16:27:43.950 回答
13

似乎除了数量之外还有其他考虑因素,这里有一些我想到的:

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

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

于 2008-10-06T16:22:34.723 回答
12

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

于 2008-10-06T17:59:40.923 回答
11

根据 Steve McConnell 在Code Complete中的说法,你应该

将例程的参数数量限制为大约七个

于 2008-11-25T23:48:44.693 回答
9

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

于 2008-10-06T16:17:46.870 回答
9

我一般同意 5,但是,如果在某种情况下我需要更多并且这是解决问题的最清晰的方法,那么我会使用更多。

于 2008-10-06T16:18:13.483 回答
8

短期记忆中的七件事?

  1. 函数名称
  2. 函数的返回值
  3. 函数的目的
  4. 参数 1
  5. 参数 2
  6. 参数 3
  7. 参数 4
于 2010-12-18T03:57:59.627 回答
7

Worst 5 Code Snippets 中,检查第二个“Is this a constructor”。它有超过 37 ⋅ 4 ≈ 150 个参数:

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

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

于 2010-03-02T08:07:55.030 回答
6

比必要的多一个。我不是说油嘴滑舌,但有些功能必然需要很多选项。例如:

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

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

于 2008-10-06T16:35:44.337 回答
5

根据Perl Best Practices,3 还可以,4 太多了。这只是一个指导方针,但在我们的商店中,这是我们努力坚持的。

于 2008-10-06T16:18:42.080 回答
5

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

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

于 2008-10-06T16:24:29.027 回答
5

您应该考虑的一个相关问题是例程的凝聚力。大量参数可能是一种气味,它告诉您例程本身正在尝试做太多事情,因此它的凝聚力值得怀疑。我同意硬而快速的参数数量可能是不可能的,但我猜想高内聚例程意味着参数数量少。

于 2008-10-28T13:42:38.057 回答
5

97听起来差不多。

再少一点,你就会失去灵活性。

于 2011-06-01T23:35:16.660 回答
4

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

于 2008-10-06T16:24:28.743 回答
4

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

于 2008-10-06T16:34:53.357 回答
4

从性能的角度来看,我要指出的一件事是,根据您将参数传递给方法的方式,按值传递大量参数会减慢程序速度,因为必须复制每个参数然后将其放入堆栈中。

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

于 2010-10-26T07:46:48.543 回答
3

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

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

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

于 2008-11-25T23:41:07.923 回答
3

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

对我来说,这相当于大约 5,但我没那么聪明。你的旅费可能会改变。

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

于 2009-10-24T13:08:39.860 回答
1

这在很大程度上取决于您工作的环境。以 javascript 为例。在 javascript 中,传递参数的最佳方式是使用带有键/值对的对象,这实际上意味着您只有一个参数。在其他系统中,最佳位置将是三到四。

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

于 2008-10-06T16:22:38.843 回答
1

我同意 3 是可以的,4 作为指导方针太多了。使用超过 3 个参数,您不可避免地要做不止一项任务。一个以上的任务应该被分成不同的方法。

但是,如果我查看我从事的最新项目,就会发现异常情况很多,而且大多数情况下很难减少到 3 个参数。

于 2008-10-06T16:25:02.003 回答
1

如果我在一个例程中有 7-10 个参数,我会考虑将它们捆绑到一个新类中,但如果该类只是一堆带有 getter 和 setter 的字段 - 新类必须一些事情,而不是在和出去。否则我宁愿忍受长长的参数列表。

于 2008-10-06T17:02:08.637 回答
1

众所周知,平均而言,人们一次可以在脑海中保留 7 +/- 2 件事情。我喜欢将这个原则与参数一起使用。假设程序员都是高于平均水平的聪明人,我会说所有 10+ 的人都太多了。

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

于 2008-10-06T17:38:58.343 回答
1

我会根据调用函数的频率来回答。

如果它是一个只被调用一次的初始化函数,那么让它需要 10 个参数或更多,谁在乎呢。

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

于 2008-10-07T00:28:28.080 回答
1

根据亚马逊名人杰夫贝索斯的说法,最多只能吃两个比萨饼

于 2008-10-07T00:45:09.093 回答
1

我同意 Robert Martin 在 Clean Code 中的引用(如上所述):参数越少越好。超过 5-7 个参数和方法调用变得非常难以理解。如果某些参数是可选的(因此采用空值),或者如果所有参数都具有相同的类型(使得找出哪个参数是哪个参数变得更加困难),情况就会变得特别糟糕。如果您可以将参数捆绑到像客户和帐户这样的内聚域对象中,那么您的代码将更加令人愉快。

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

retries(1000, 2000, 3000)

在这种有限的情况下,向调用添加更多参数并不会增加太多的心理负担。

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

但是我仍然会在更少而不是更多参数方面犯错。

于 2009-11-17T06:50:13.823 回答
0

我想说,只要你有 2-4 的重载,如果你需要它,你就可以升得更高。

于 2008-10-06T16:24:56.670 回答
0

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

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

于 2008-10-06T16:25:32.207 回答
0

IMO,长参数列表的理由是数据或上下文本质上是动态的,想想 printf(); 使用可变参数的一个很好的例子。处理这种情况的更好方法是传递流或 xml 结构,这再次最大限度地减少了参数的数量。

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

于 2008-10-06T16:49:21.330 回答