最小化函数参数数量的技术


13

在“干净代码”中,写有“函数的理想参数个数为零”。解释原因并讲得通。我所需要的是重构具有4个或更多参数的方法来解决此问题的技术。

一种方法是将参数提取到新的类中,但是那肯定会导致类的爆炸式增长吗?那些类可能以违反某些命名规则的名称结尾(以“ Data”或“ Info”等结尾)?

另一种技术是使多个函数使用的变量成为私有成员变量,以避免传递它们,但这扩大了变量的范围,可能使它向实际上不需要它的函数开放。

只是寻找使函数参数最小化的方法,就已经接受了这样做的好主意。


21
我完全不同意干净的代码。如果函数的参数数量为零,则表示该函数具有副作用,并且可能会在某处更改状态。虽然我确实同意,少于4个参数可能是一个很好的经验法则-我宁愿有一个带有8个参数的函数是静态的并且没有副作用,而不是有一个带有零参数的非静态函数来改变状态并具有副作用。
wasatz

4
在“干净代码”中,“函数的理想参数个数为零”这样写。 “真的吗?太错了!参数的理想数目是一个,其返回值确定地从该一个参数得出。实际上,参数的数量并不重要。重要的是,只要有可能,该函数就应该是纯函数(即,它仅从其参数中得出其返回值,而没有副作用)。
大卫·阿诺

2
好书后来确实指出了副作用是不可取的……
Neil Barnwell


Answers:


16

要记住的最重要的事情是,这些是准则,而不是规则。

在某些情况下,方法仅必须带有一个参数。+例如,考虑一下数字方法。或add用于收集的方法。

实际上,甚至可能会争辩说,将两个数字相加的含义取决于上下文,例如在ℤ中3 + 3 == 6,但是在ℤ | 5中 3 + 3 == 2,因此,加法运算符实际上应该是上下文对象上的方法,该方法采用两个参数而不是a带一个参数的数字的方法。

同样,用于比较两个对象的方法必须是一个对象将另一个作为参数的方法,或者是将两个对象作为参数的上下文的方法,因此,将比较方法与少于一个论点。

也就是说,可以通过以下几项操作来减少方法的参数数量:

  • 使方法本身更小:也许,如果方法需要那么多参数,那么它做得太多了吗?
  • 缺少抽象:如果参数紧密相关,也许它们属于同一类,那么您是否缺少抽象?(规范的教科书示例:传递一个Point对象,而不是两个坐标,或者传递一个对象,而不是传递用户名和电子邮件IdCard。)
  • 对象状态:如果参数需要多种方法,则它可能应该是对象状态的一部分。如果仅某些方法需要它,而其他方法则不需要,则该对象可能做得太多,实际上应该是两个对象。

一种方法是将参数提取到新的类中,但是那肯定会导致类的爆炸式增长吗?

如果您的域模型包含许多不同种类的对象,那么您的代码将最终包含许多不同种类的对象。没错。

那些类可能以违反某些命名规则的名称结尾(以“ Data”或“ Info”等结尾)?

如果找不到合适的名称,则可能是将太多参数组合在一起或将参数组合得很少。因此,您要么只是一个类的一部分,要么您有多个类。

另一种技术是使多个函数使用的变量成为私有成员变量,以避免传递它们,但这扩大了变量的范围,可能使它向实际上不需要它的函数开放。

如果您有一组方法都使用相同的参数,而另一组方法却不同,则它们可能属于不同的类。

请注意我经常使用“也许”一词吗?这就是为什么这些只是准则,而不是规则。也许您的带有4个参数的方法非常好!


7
@BrunoSchäpper:确定:(1)“ 使方法本身变小:也许,如果该方法需要那么多参数,那么它做得太多了吗? ”。参数数量对此是一个不好的考验。可选/布尔参数和大量代码行是方法执行过多操作的有力指示。许多参数充其量只是一个弱者。(2)“ 对象状态:如果多个方法需要该参数,则它可能应该是对象状态的一部分 ”。不,不,三次,不。最小化对象状态;不是功能参数。如果可能,请通过参数将值传递给所有方法以避免对象状态。
David Arno

您给出的加法示例完全错误。在add对自然数函数和add对整数环功能模N是在两个不同类型的两种不同的功能操作。我也不明白您所说的“上下文”是什么意思。
gardenhead 2016年

谢谢@DavidArno。1)同意,本身并不是一个强有力的指标。但是还是一个好人。我经常看到一些方法,并传递了一些对象。没有对象状态。很好,但是2)更好的选项IMHO正在重构这些方法,将隐式状态移动到新类,该类将所有这些参数作为显式参数。您最终得到了一个公共的零参数方法,以及许多零对一参数内部方法。国家不是公共的,不是全球性的,甚至不是长期存在的,但是代码要干净得多。
BrunoSchäpper16年

6

请注意,零参数并不表示副作用,因为您的对象是隐式参数。例如,看看Scala的不可变列表有多少种零度方法。

我将一种有用的技术称为“镜头聚焦”技术。聚焦摄像机镜头时,如果将焦点对准太远,则更容易看到其真实对焦点,然后将其向后对准正确的点。软件重构也是如此。

尤其是如果您使用的是分布式版本控制,则可以轻松地尝试软件更改,看看您是否喜欢它们的外观,如果不喜欢它们,则选择退出,但是出于某些原因,人们似乎常常不愿意这样做。

在您当前的问题中,这意味着编写零或一个参数版本,首先要编写几个分离的函数,然后相对容易地看出哪些函数需要组合以提高可读性。

请注意,作者还是测试驱动开发的忠实拥护者,由于您从琐碎的测试用例开始,因此一开始往往会产生低arity函数。


1
就像您的“镜头聚焦”类比-尤其是在重构时,使用广角镜头而不是近摄镜头很重要。而且查看#个参数实在太近了
tofro 16/09/23

0

一种简单的方法(天真-甚至我应该盲目地说)只是旨在减少可能错误的函数参数的数量。具有大量参数的函数绝对没有错。如果逻辑需要它们,那么它们也是必需的...只要正确地设置了格式并对其进行了注释以提高可读性,长参数列表根本不会让我担心。

如果参数的全部或子集属于一个唯一的逻辑实体,并且通常在整个程序中成组传递,则将它们分组到某个容器(通常是结构或其他对象)中可能是有意义的。典型示例可能是某种消息事件数据类型。

您可以轻松地过度使用该方法-一旦发现在此类运输容器之间进行装箱和拆箱所产生的开销超过了其提高可读性的开销,那么您可能已经走得太远了。

OTOH,较大的参数列表可能表明您的程序可能结构不正确-可能需要大量参数的函数只是试图做太多事情,应该分成几个较小的函数。我宁愿从这里开始,也不必担心参数数量。


5
当然,盲目减少争论的数量是错误的。但是我不同意 “具有大量参数的函数绝对没有错”。。我认为,当您命中具有大量参数的函数时,在所有情况中,有99.9%的情况下,有一种方法可以以有意的方式改善代码的结构,从而(也)减少了函数的参数数。
布朗

@DocBrown这就是为什么有最后一段以及从那里开始的建议的原因...。还有一个:您可能从未尝试过针对MS Windows API编程;)
tofro

“也许需要大量参数的函数只是试图做太多事情,应该分成几个较小的函数。” 我完全同意,尽管在实践中您不只是最终获得另一个更高的函数来调用那几个较小的函数吗?然后,您可以将它们重构为一个对象,但是该对象将具有ctor。您可以使用构建器等等等等。关键是它是无限回归的-在某个地方,软件要完成其工作需要许多值,并且它们必须以某种方式获取这些功能。
尼尔·巴恩威尔,2016年

1
@NeilBarnwell在理想情况下(值得重构),您可以将需要10个参数的函数拆分为三个需要3-4个参数的函数。在不太理想的情况下,您最终得到三个函数,每个函数需要10个参数(然后,最好不做任何处理)。关于您的上层堆栈参数:同意-可能是这样,但不是必须的-参数来自某个地方,并且检索也可能在堆栈内部的某个地方,只需要放在那里的适当位置-里程往往会有所不同。
tofro

软件逻辑永远不需要四个以上的参数。只有编译器可以。
医生

0

没有神奇的方法:您必须掌握问题域才能发现正确的体系结构。那是重构的唯一方法:掌握问题域。可以肯定地说,四个以上的参数可以确定您当前的体系结构是错误的还是错误的。

唯一的例外是编译器(元程序)和模拟,理论上限制为8,但可能只有5。

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.