我注意到我使用的一些函数有6个或更多参数,而在大多数我使用的库中,很少会找到一个需要3个以上参数的函数。
通常,这些额外的参数中有很多是二进制选项,可以更改功能行为。我认为其中一些参数设置的函数可能应该重构。有多少个数字的准则吗?
我注意到我使用的一些函数有6个或更多参数,而在大多数我使用的库中,很少会找到一个需要3个以上参数的函数。
通常,这些额外的参数中有很多是二进制选项,可以更改功能行为。我认为其中一些参数设置的函数可能应该重构。有多少个数字的准则吗?
Answers:
我从未见过任何指导方针,但是以我的经验,使用超过三个或四个参数的函数表示以下两个问题之一:
没有更多信息,很难说出您正在寻找什么。您可能需要进行的重构是将函数拆分为一些较小的函数,这些函数根据当前传递给该函数的那些标志从父级调用。
这样做有一些好处:
if
要比用一个方法全部完成的结构要容易得多,“规则列表”由一个调用具有描述性名称的方法的结构组成。根据“清洁代码:敏捷软件技巧手册”,理想情况是零,一个或两个是可以接受的,在特殊情况下为三个,四个或更多,从不!
作者的话:
函数的理想参数个数为零(尼拉度)。接下来是一个(单声道),紧接着是两个(双声道)。在可能的情况下,应避免使用三个参数(三重性)。超过三个(多义词)需要非常特殊的理由-因此无论如何都不应使用。
本书有一章只讨论了讨论大量参数的函数,因此我认为这本书可以很好地指导您需要多少参数。
在我个人看来,一个参数总比没有参数好,因为我认为更清楚发生了什么。
例如,在我看来,第二种选择更好,因为更清楚该方法正在处理什么:
LangDetector detector = new LangDetector(someText);
//lots of lines
String language = detector.detectLanguage();
与
LangDetector detector = new LangDetector();
//lots of lines
String language = detector.detectLanguage(someText);
关于许多参数,这可能表明某些变量可以分组为一个对象,或者在这种情况下,很多布尔值可以表示函数/方法在完成某件事上的作用,在这种情况下,更好地将这些行为中的每个行为重构为不同的功能。
String language = detectLanguage(someText);
。在任何一种情况下,您传递的参数数量都完全相同,只是由于语言不佳而将函数执行分为两部分。
Matrix.Create(input);
在那里input
的,比方说,一个.NET IEnumerable<SomeAppropriateType>
?这样,你还当你想创建一个矩阵保存10个元素,而不是9,不需要单独的过载
如果应用程序中的域类设计正确,则传递给函数的参数数量将自动减少-因为这些类知道如何执行其工作,并且它们具有足够的数据来执行其工作。
例如,假设您有一个经理班级,要求三年级班级完成作业。
如果建模正确,
3rdGradeClass.finishHomework(int lessonId) {
result = students.assignHomework(lessonId, dueDate);
teacher.verifyHomeWork(result);
}
这很简单。
如果您没有正确的模型,则方法将如下所示
Manager.finishHomework(grade, students, lessonId, teacher, ...) {
// This is not good.
}
正确的模型总是会减少方法调用之间的函数参数,因为正确的函数被委派给了自己的类(单一职责),并且它们具有足够的数据来完成其工作。
每当看到参数数量增加时,我都会检查模型以查看是否正确设计了应用程序模型。
但是,有一些例外:当我需要创建一个传输对象或配置对象时,在构造一个大的配置对象之前,我将首先使用一个生成器模式来生成一个小的生成的对象。
其他答案不会占用的一个方面是性能。
如果使用足够低级的语言(C,C ++,汇编语言)进行编程,则大量参数可能会对某些体系结构的性能造成极大的损害,尤其是在调用函数多次的情况下。
例如,当在ARM中进行函数调用时,前四个参数将放置到的寄存器r0
中r3
,其余的参数必须被压入堆栈。将关键参数的数量保持在五个以下可以对关键函数产生很大的影响。
对于被称为非常频繁的功能,甚至认为程序有设置参数之前每次调用可能会影响性能(其实r0
到r3
可以被调用的函数被覆盖,并且将在下次调用之前被替换),所以在这方面,零参数是最好的。
更新:
KjMag提出了有趣的内联主题。内联将以某种方式减轻这种情况,因为内联将使编译器执行与纯汇编编写时相同的优化。换句话说,编译器可以看到被调用函数使用了哪些参数和变量,并且可以优化寄存器使用率,从而使堆栈的读/写操作最小化。
但是内联有一些问题。
inline
在所有情况下都将其视为强制性的,二进制增长将会爆炸。inline
在遇到它们时会完全忽略或实际上给您错误。inline
。)
当参数列表增加到五个以上时,请考虑定义“上下文”结构或对象。
这基本上是一个结构,其中包含所有可选参数以及一些合理的默认设置。
在C程序世界中,将使用简单的结构。在Java,C ++中,一个简单的对象就足够了。不要弄乱getter或setter方法,因为对象的唯一目的是保存“ public”可设置的值。
不,没有标准指南
但是,有些技术可以使带有许多参数的函数更易于使用。
您可以使用list-if-args参数(args *)或Dictionary-of-args参数(kwargs **
)
例如,在python中:
// Example definition
def example_function(normalParam, args*, kwargs**):
for i in args:
print 'args' + i + ': ' + args[i]
for key in kwargs:
print 'keyword: %s: %s' % (key, kwargs[key])
somevar = kwargs.get('somevar','found')
missingvar = kwargs.get('somevar','missing')
print somevar
print missingvar
// Example usage
example_function('normal parameter', 'args1', args2,
somevar='value', missingvar='novalue')
输出:
args1
args2
somevar:value
someothervar:novalue
value
missing
或者您可以使用对象文字定义语法
例如,这是一个JavaScript jQuery调用,用于启动AJAX GET请求:
$.ajax({
type: 'GET',
url: 'http://someurl.com/feed',
data: data,
success: success(),
error: error(),
complete: complete(),
dataType: 'jsonp'
});
如果您看一下jQuery的ajax类,则可以设置很多(大约30个)更多的属性。主要是因为ajax通信非常复杂。幸运的是,对象文字语法使生活变得轻松。
C#intellisense提供了有效的参数文档,因此看到重载方法的非常复杂的排列并不少见。
像python / javascript这样的动态类型语言没有这种功能,因此,看到关键字参数和对象文字定义的情况要普遍得多。
我更喜欢使用对象文字定义(甚至在C#中)来管理复杂的方法,因为在实例化对象时,您可以显式查看正在设置的属性。您将需要做更多的工作来处理默认参数,但是从长远来看,您的代码将更具可读性。使用对象字面量定义,您可以摆脱对文档的依赖,从而乍看之下您的代码在做什么。
恕我直言,重载的方法被高估了。
注意:如果我没记错的话,只读访问控制应该适用于C#中的对象文字构造函数。它们的工作原理与在构造函数中设置属性相同。
如果您从未用过基于动态类型(python)和/或基于Java脚本的功能/原型的语言编写任何重要的代码,我强烈建议您尝试一下。这可能是一个启发性的经历。
首先,打破对功能/方法初始化的所有一切方法的参数的依赖可能会很可怕,但是您将学习到用代码做更多的事情而不必增加不必要的复杂性。
更新:
我可能应该提供示例来演示在静态类型语言中的用法,但是我目前不在静态类型上下文中考虑。基本上,我在动态类型的上下文中做了太多的工作,以至于突然间切换回去。
我所知道的是对象文字定义语法在静态类型的语言中(至少在C#和Java中)是完全可能的,因为我以前使用过它们。在静态类型的语言中,它们称为“对象初始化器”。以下是一些链接,显示了它们在Java和C#中的用法。
就像Evan Plaice所说的那样,我非常喜欢在可能的情况下简单地将关联数组(或您的语言的类似数据结构)传递给函数。
因此,代替(例如)这样做:
<?php
createBlogPost('the title', 'the summary', 'the author', 'the date of publication, 'the keywords', 'the category', 'etc');
?>
去做:
<?php
// create a hash of post data
$post_data = array(
'title' => 'the title',
'summary' => 'the summary',
'author' => 'the author',
'pubdate' => 'the publication date',
'keywords' => 'the keywords',
'category' => 'the category',
'etc' => 'etc',
);
// and pass it to the appropriate function
createBlogPost($post_data);
?>
WordPress用这种方式做了很多事情,我认为它运作良好。(尽管我上面的示例代码是虚构的,本身并不是Wordpress的示例。)
这种技术使您可以轻松地将大量数据传递到函数中,而无需记住每个函数必须传递的顺序。
当需要重构时,您还将欣赏此技术-不必潜在地更改函数参数的顺序(例如,当您意识到需要传递“其他参数”时),而无需更改函数的参数列出所有。
这不仅使您不必重新编写函数定义,而且使您不必每次调用函数时都更改参数的顺序。那是一个巨大的胜利。
为了在Robert Martin的“ Clean Code:A Agile Software Craftsmanship手册”中为理想的函数参数个数为零的建议提供更多的背景信息,作者说了以下几点:
争论很难。他们具有很大的概念力。这就是为什么我从示例中删除了几乎所有它们的原因。例如,考虑
StringBuffer
示例中的。我们本可以将其作为参数传递,而不是将其作为实例变量,但是这样,我们的读者将不得不在每次看到它时对其进行解释。当您阅读模块讲述的故事时,includeSetupPage()
比容易理解includeSetupPageInto(newPageContent)
。参数处于不同的抽象层次上,即函数名称会强制您知道一个细节(StringBuffer
即),该细节在那时并不特别重要。
对于includeSetupPage()
上面的示例,这是本章末尾重构的“干净代码”的一小段:
// *** NOTE: Commments are mine, not the author's ***
//
// Java example
public class SetupTeardownIncluder {
private StringBuffer newPageContent;
// [...] (skipped over 4 other instance variables and many very small functions)
// this is the zero-argument function in the example,
// which calls a method that eventually uses the StringBuffer instance variable
private void includeSetupPage() throws Exception {
include("SetUp", "-setup");
}
private void include(String pageName, String arg) throws Exception {
WikiPage inheritedPage = findInheritedPage(pageName);
if (inheritedPage != null) {
String pagePathName = getPathNameForPage(inheritedPage);
buildIncludeDirective(pagePathName, arg);
}
}
private void buildIncludeDirective(String pagePathName, String arg) {
newPageContent
.append("\n!include ")
.append(arg)
.append(" .")
.append(pagePathName)
.append("\n");
}
}
作者的“思想流派”主张使用小的类,较少的函数参数(理想情况下为0)和很小的函数。尽管我也不完全同意他的观点,但我发现它发人深省,并且我认为将零函数参数作为理想的想法值得考虑。另外,请注意,即使上面的小代码段也具有非零参数函数,所以我认为这取决于上下文。
(正如其他人指出的那样,他还认为,从测试的角度来看,更多的论点会变得更加困难。但是,在这里,我主要想强调上面的示例以及他对零函数论点的依据。)
理想情况下为零。一两个可以,在某些情况下三个。
四个或更多通常是一个不好的做法。
除了其他人指出的单一责任原则之外,您还可以从测试和调试的角度来考虑它。
如果只有一个参数,则知道它的值,对其进行测试并使用它们查找错误相对“容易,因为只有一个因素。随着因素的增加,总的复杂性会迅速增加。对于一个抽象的例子:
考虑“在这种天气下穿什么”计划。考虑一下如何处理一个输入-温度。您可以想象,根据这一因素,穿什么衣服的结果非常简单。现在考虑如果程序实际上通过了温度,湿度,露点,降水等因素,程序可能/将/应该做什么。现在想象一下,如果程序给出了“错误”的答案,调试将有多么困难。