How many parameters are too many?
例程可以有参数,这不是新闻。 您可以根据需要定义任意数量的参数,但是过多的参数会使您的日常工作难以理解和维护。
当然,您可以使用结构化变量作为解决方法:将所有这些变量放在单个结构中并将其传递给例程。 实际上,使用结构来简化参数列表是Steve McConnell在Code Complete中描述的技术之一。 但正如他所说:
Careful programmers avoid bundling data any more than is logically necessary.
因此,如果您的例程有太多参数或使用结构来伪装一个大参数列表,那么您可能做错了。 也就是说,你没有保持松耦合。
我的问题是,我什么时候可以考虑参数列表太大? 我认为超过5个参数,太多了。 你怎么看?
尽管第一修正案保证言论自由,但什么时候被认为是一种可以受到监管的东西?据波特斯图尔特大法官说,"当我看到它时,我就知道了。"这同样适用于此。
我讨厌制定像这样的硬性规则,因为答案的变化不仅取决于项目的规模和范围,而且我认为它甚至会改变到模块级别。根据您的方法正在做什么,或者该类应该表示什么,很可能2个参数太多并且是过多耦合的症状。
我建议通过首先提出问题,并像你一样对你的问题进行鉴定,你真的知道所有这一切。这里最好的解决方案不是依靠一个快速而快速的数字,而是在同行中寻找设计评论和代码评审,以确定您具有低内聚和紧耦合的区域。
永远不要害怕向同事展示你的工作。如果你害怕,这可能是你的代码出现问题的更大迹象,而且你已经知道了。
如果某些参数是冗余的,则函数只能有太多参数。如果使用了所有参数,则该函数必须具有正确数量的参数。拿这个经常使用的功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 *帮助你。
*其他编码助手可用!
在清洁法典中,罗伯特C.马丁为这个主题专门写了四页。这是要点:
The ideal number of arguments for a
function is zero (niladic). Next comes
one (monadic), followed closely by two
(dyadic). Three arguments (triadic)
should be avoided where possible. More
than three (polyadic) requires very
special justification -- and then
shouldn't be used anyway.
我过去使用的一些代码使用全局变量只是为了避免传递太多参数。
请不要那样做!
(平时)。
如果你开始不得不在心理上计算签名中的参数并将它们与调用匹配,那么现在是重构的时候了!
非常感谢您的所有答案:
-
找到那些也认为(就像我一样)5个参数对代码的健全性有很好限制的人来说有点令人惊讶。
-
一般来说,人们倾向于同意3到4之间的限制是很好的经验法则。这是合理的,因为人们通常计算超过4件事情的时间不好。
-
正如米兰指出的那样,平均而言,人们可以一次保持或多或少的7件事情。但我认为你不能忘记,当你设计/维护/研究一个例程时,你必须记住更多的东西,而不仅仅是参数。
-
有些人认为例程应该有尽可能多的参数。我同意,但仅限于几个特定情况(调用OS API,优化很重要的例程等)。我建议尽可能在这些调用之上添加一层抽象来隐藏这些例程的复杂性。
-
尼克对此有一些有趣的想法。如果您不想阅读他的评论,我总结一下:简而言之,这取决于:
I hate making hard and fast rules like this because the answer changes not only depending on the size and scope of your project, but I think it changes even down to the module level. Depending on what your method is doing, or what the class is supposed to represent, it's quite possible that 2 arguments is too many and is a symptom of too much coupling.
这里的道德是不要害怕向同行展示你的代码,与他们讨论并试图"找出你内聚力低,紧密耦合的区域"。
-
最后,我认为wnoise非常赞同Nick,并总结了他对编程艺术的这种诗意愿景(见下文评论)的讽刺性贡献:
Programming is not engineering. Organization of code is an art because it depends on human factors, which depend too much on context for any hard rule.
这个答案假设一个OO语言。如果你没有使用它 - 跳过这个答案(换句话说,这不是一个与语言无关的答案。
如果你传递的参数超过3个(特别是内在类型/对象),那不是"太多",而是你可能错过了创建新对象的机会。
查找传递给多个方法的参数组 - 即使传递给两个方法的组几乎保证您应该在那里有一个新对象。
然后,您将功能重构到新对象中,您不会相信它对您的代码和对OO编程的理解有多大帮助。
似乎有其他考虑而不仅仅是数字,这里有一些想到的:
与功能与一次性设置的主要目的的逻辑关系
如果它们只是环境标志,捆绑可以非常方便
Alan Perlis着名的编程语言之一(在ACM SIGPLAN Notices 17(9),1982年9月中叙述)指出"如果你有一个包含10个参数的程序,你可能会错过一些。"
根据Code Complete中的Steve McConnell,您应该这样做
Limit the number of a routine's
parameters to about seven
我普遍同意5,但是,如果我需要更多的情况,这是解决问题的最明确的方法,那么我会使用更多。
对我来说,当列表在我的IDE上跨过一行时,它的一个参数太多了。我希望在一行中看到所有参数而不会破坏目光接触。但这只是我个人的偏好。
短期记忆中的七件事?
在Worst 5 Code Snippets中,检查第二个,"这是一个构造函数"。它有超过37? 4≈150参数:
Here a programmer wrote this constructor [... S]ome of you may think yes its a big constructor but he used eclipse automatic code generation tools[.] NOO, in this constructor there was a tiny bug that I discovered, which made me conclude that this constructor was written by hand. (by the way this is only the top part of the constructor, its not complete).
一个不必要的。我并不是说要搞笑,但有一些功能必然需要很多选择。例如:
1 2 | void * mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t offset); |
共有6个论点,每个论点都是必不可少的。此外,它们之间没有共同的联系来证明捆绑它们的合理性。也许你可以定义"struct mmapargs",但这会更糟。
根据Perl Best Practices,3可以,4太多了。这只是一个指导方针,但在我们的商店里,这是我们努力坚持的。
97听起来恰到好处。
任何更少,你失去灵活性。
我自己在5个参数中绘制了公共函数的限制。
恕我直言,长参数列表仅在私有/本地助手函数中可接受,这些函数仅用于从代码中的几个特定位置调用。在这些情况下,您可能需要传递大量的状态信息,但可读性并不是一个大问题,因为只有您(或将维护您的代码并且应该了解模块基础知识的人)必须关心调用那个函数。
您应该考虑的一个相关问题是例行公事的凝聚力。大量的参数可能是一种气味,告诉你例程本身正在试图做太多,因此它的凝聚力是可疑的。我同意很难和快速数量的参数可能是不可能的,但我猜想高内聚程序会意味着参数数量很少。
作为一般的经验法则,我停在三个参数。更多,是时候传递参数数组或配置对象,这也允许在不更改API的情况下添加将来的参数。
参数列表的长度限制只是一个限制。限制意味着应用暴力。这听起来很有趣,但即使在编程时你也可能非暴力。只需让代码规定规则即可。很明显,如果你有很多参数,函数/类方法的主体将足够大,可以使用它们。大代码片段通常可以重构并拆分成更小的块。因此,您可以获得许多参数作为免费奖励的解决方案,因为它们被分成较小的重构代码片段。
我从性能角度指出的一点是,根据您如何将参数传递给方法,按值传递大量参数会降低程序速度,因为每个参数都必须复制然后放在堆栈上。
使用单个类来包含所有参数会更好地工作,因为通过引用传递的单个参数将更优雅,更清洁,更快!
我的经验法则是,我需要能够长时间记住这些参数,以便查看呼叫并告诉它它的作用。因此,如果我不能查看该方法,然后转到方法调用并记住哪个参数做了什么,那么就有太多了。
对我而言,相当于约5,但我并不那么聪明。你的旅费可能会改变。
您可以创建一个具有属性的对象来保存参数,如果超出您设置的限制,则传入该参数。请参阅Martin Fowler的重构书和关于使方法调用更简单的章节。
根据我的说法,可能会有超过4或某些固定数字的情况。
需要注意的事情可能是
从易用性或易于阅读代码的角度来看,我认为当你需要对自己的方法签名进行"自动换行"时,这应该让你停下来思考,除非你感到无助,并且所有努力使签名更小导致没有结果。过去和现在的一些非常好的图书馆使用超过4-5个婴儿车。
我同意3是好的,4是太多的指南。有超过3个参数,你不可避免地要做一个以上的任务。应该将多个任务分成多个单独的方法。
但是,如果我查看我所研究的最新项目,那么例外情况会比比皆是,而且大多数案例都难以达到3个参数。
根据亚马逊成名的杰夫贝索斯的说法,只有两种披萨可以喂食:
我同意Robert Martin在清洁代码中的引用(如上所述):参数越少越好。超过5-7个参数和方法调用变得非常难以理解。如果某些参数是可选的(因此采用空值),或者如果所有参数具有相同的类型(使得更难以确定哪个参数是哪个),事情会变得特别糟糕。如果您可以将参数捆绑到客户和帐户等内聚域对象中,那么您的代码将更加愉快。
有一个边缘情况:如果你有一个方法调用,它接受形成逻辑集的可变数量的参数,那么具有更多参数的认知开销就会减少。例如,您可能需要一种方法,根据重试之间的毫秒数指定多个HTTP请求重试次数。在1s,2s和3s间隔的三次重试可以指定为:
1 | retries(1000, 2000, 3000) |
在这种有限的情况下,为呼叫添加更多参数并不会增加太多的心理超负荷。
另一个考虑因素是,如果您的语言支持命名参数列表,并允许您省略可选参数。较大的命名参数列表比较大的未命名参数列表更容易理解。
但是,我仍然会考虑更少而不是更多的参数。
我的答案基于调用函数的频率。
如果它是一个只被调用一次的init函数,那么让它需要10个或更多的parms,谁在乎。
如果每帧调用一次,那么我倾向于创建一个结构,只是传递一个指针,因为它往往更快(假设你不是每次都重建结构)。
众所周知,人们平均可以一次保持7 +/- 2件事。我喜欢用参数原理。假设程序员都是高于平均水平的聪明人,我会说10+的内容太多了。
顺便说一句,如果参数以任何方式相似,我会将它们放在向量或列表中而不是结构或类中。
如果我在一个例程中有7-10个参数,我会将它们捆绑到一个新类中但是如果该类只是一堆带有getter和setter的字段,那么新类除了shuffle值之外还要做其他事情。出。否则我宁愿忍受长参数列表。
它在很大程度上取决于您正在使用的环境。例如javascript。在javascript中,传递参数的最佳方法是使用具有键/值对的对象,这在实践中意味着您只有一个参数。在其他系统中,最佳位置将是三或四。
最后,这一切都归结为个人品味。
我认为实际数字实际上取决于对函数上下文的逻辑意义。我同意大约4-5个参数开始变得拥挤。
在设置标志的情况下,处理这种情况的一种好方法是枚举值并将它们组合在一起。
IMO,长参数列表的理由是数据或上下文本质上是动态的,想想printf();使用varargs的一个很好的例子。处理此类情况的更好方法是通过传递流或xml结构,这再次最小化参数的数量。
机器肯定不会介意大量的参数,但开发人员也会考虑维护开销,单元测试用例和验证检查的数量。设计人员也讨厌冗长的args列表,更多的参数意味着更改接口定义,无论何时进行更改。关于耦合/内聚弹簧的问题来自上述方面。
我会说,只要你有超过2-4的超载,如果你需要的话你可以提高。