在函数中对参数进行排序的最佳实践是什么?


52

有时候(很少),创建一个需要大量参数的函数似乎是最好的方法。但是,当我这样做时,我觉得我经常随机选择参数的顺序。我通常按​​照“重要性顺序”进行操作,首先要考虑最重要的参数。

有一个更好的方法吗?是否有一种“最佳做法”的参数排序方式可以提高清晰度?


5
命名参数使这个问题不那么严重。Python将其发挥到了极致,对其进行了研究。一个很好的例子是C#-多种调用方法MessageBox.Show。还要研究一下。
工作

8
在Haskell中,部分函数应用程序的可能有用性通常表明自然参数排序。
tdammers 2011年

您认为合适的参数量是多少?
克里斯(Chris)

我在想>2。尽管我猜想排序同样适用于2个参数。
凯西·帕顿

3
@tdammers对于任何直接支持currying
jk的

Answers:


55

通常:使用它

为您的功能编写一个测试,一个真实的测试。
东西你实际上喜欢做的事与该功能

看看您按什么顺序放下了它们。

除非您已经拥有(或知道)一些功能类似的功能。
在这种情况下:至少在第一个论点上,遵循他们已经做的事情。

例如,他们是否都将文档/对象/文件指针/值系列/坐标作为第一个参数?看在上帝的份上,顺应那些论点

避免混淆您的同事您未来的自我


12
一般来说,“避免混淆……您的未来自我”听起来像是一种好的哲学。
Jeanne Pindar

28

我通常会遵循这些规则,尽管并不总是具有相同的优先级。我想这是一个自动的思考过程,除了公共API设计之外,我并没有考虑太多

选择渠道

  1. 语义学
  2. 重要性/相关性
  3. 使用频率
  4. I / O问题

1.语义优先

尤其是在OOP中,请根据参数对操作或消息的语义重要性选择参数。参数名称良好的方法的签名应:

  • 打电话自然
  • 在意图和行为方面具有自我描述性。

(由于这些原因,有时使用自定义类型或别名而不是基元可能会增加签名的表现力。)

2.然后的重要性

最“重要”的参数排在第一位(或下一个...)

3.然后频率

频率也很重要,尤其是在您没有命名参数但可以在位置参数上具有默认值的语言中。这意味着参数的顺序没有变化,并且很明显,如果您想强制使用第N个参数的默认值,则无法设置N + 1个参数(除非您的语言具有占位符参数的概念, )。

对您来说,好消息是,频率通常与重要性相关,因此与上一点紧密相关。然后,可能要由您自己设计API以使其具有适当的语义。

4.让我们不要忘记I / O

如果您的方法/函数接受一些输入并产生一个输出,而该输出不被“返回”(通过return语句)或“抛出”(使用异常系统),则您可以选择传递使用您的其他参数(或输入参数)将值返回给调用方。这与语义有关,在大多数情况下,让第一个参数定义输出,而最后一个参数接收输出将是有意义的。

另外,具有较少参数并最大化语义的另一种方法是使用功能性方法或定义Builder模式,以便您可以清楚地堆叠输入,定义输出并在需要时检索它们。

(注意,我没有提到全局变量,因为为什么要使用一个,对吗?)


一些事情要考虑

  • 易用性

    如果您遵循ZJR的建议,上面的大多数内容将很自然地显示出来:使用它!

  • 考虑重构

    如果您担心参数排序,则可能是在上面以及您的API设计错误的事实中找到了它的根源。如果参数太多,则很可能会对某些内容进行组件化/模块化和重构

  • 考虑性能

    请记住,使用参数时,某些语言的实现会对运行时内存管理产生非常重要的影响。因此,许多语言的样式手册建议保持参数列表简洁明了的原因。以最大4个参数为例。我将其作为练习让您找出原因。

  • Bevan的回答和对Clean Code的建议的提及也同样重要!


18

我谨此提出,担心参数排序是担心错误的事情。

在鲍勃叔叔的书《干净的代码》中,他有说服力地主张,方法决不能有两个以上的参数-大多数应该只有一个参数(如果有的话)。在这种情况下,排序是显而易见的或不重要的。

无论多么不完美,我都在尝试遵循Bob叔叔的建议-它正在改进我的代码。

在一种罕见的方法似乎需要更多信息的情况下,引入参数对象是一个好主意。通常,我发现这是发现对我的算法至关重要的新概念(对象)的第一步。


我绝对同意你的观点!我的第一个声明旨在指出这对我来说不是典型的事情,哈哈。但是,如果我确实确实需要传递一些参数,并且重构整个程序不值得,我想知道是否要订购。
Casey Patton

3
咳嗽 PHP 咳嗽。抱歉-您是在说不用担心参数的顺序吗?
Craige

那么,您是否就不会有一个参数对象的构造函数,而该参数对象却需要多少个参数呢?
确实是2012年

否-因为可以以更具可读性的方式在多个语句上“构建”参数对象。
Bevan 2012年

2
@贝文:这是值得商bat的。用这种方式建立对象意味着它们不再是不变的。使对象尽可能保持不变是增加可维护性的另一种好方法。
tdammers 2012年

10

我尝试将IN参数放在第一位,将OUT参数放在第二位。也有一些自然顺序,例如createPoint(double x, double y),强烈推荐使用createPoint(double y, double x)


5
有时相反的情况更好,例如,当总是只有一个OUT参数和可变数量的in参数时,例如in addAllTo(target, something1, something2)
maaartinus

尽管我遵循相同的规则,但我还是尽可能地避免使用OUT参数,对于大多数优秀的程序员也是如此。因此,该建议实际上可以帮助OP的功能数量应该很少。
布朗

7
@DocBrown您真的不应该避免避开优秀的程序员,因为他们对您有帮助。
RyanfaeScotland

@RyanfaeScotland:该死,我应该在发表评论之前先阅读我的评论两次(或者至少在五分钟之内就可以更改它们);-)
Doc Brown

6

我从未见过关于此特定主题的文档化“最佳实践”,但是我的个人标准是按照它们在所使用的方法中出现的顺序列出它们,或者如果该方法更多是在传递给数据层时,我将按照它们在db模式或数据层方法中出现的顺序列出它们。

另外,当一个方法有多个重载时,我注意到典型的方式是从所有(或大多数)方法共有的参数开始列出它们,并在每个方法重载的末尾附加每个不同的方法,例如:

void func1(string param) { }
void func2(string param, int param2) { }
void func3(string param, string param3) { }
void func3(string param, int param2, string param3) { }


3

我经常遵循C / C ++惯例,即const首先放置参数(即,按值传递的参数),然后是按引用传递的参数。这不一定是调用函数的正确方法,但是,如果您对每个编译器如何处理参数感兴趣,请查看以下链接,以了解控制规则和/或将参数压入堆栈的顺序。

http://msdn.microsoft.com/zh-CN/library/zthk2dkh%28v=vs.80%29.aspx

http://en.wikipedia.org/wiki/Calling_convention


您可能会感兴趣的另一个链接是 msdn.microsoft.com/en-us/library/984x0h58%28v=vs.71%29.aspx 对于递归函数,请查看以下链接。 publications.gbdirect.co.uk/c_book/chapter4/… 不是我忘记了,但是作为一个新用户,我无法发表包含两个以上链接的评论。
条例草案

1

我通常只是按照“看上去不太像疯子”的参数排序。我去方法/函数定义的次数越少越好。命名参数很好地描述了它们的用途,这很好,这样,当弹出小工具提示(VS)时,它将变得更加容易。

如果您有几行和几行参数,则可能需要考虑其他设计。退后一步,看看如何将其分解为更多的功能/方法。只是一个想法,但是当我的函数中有十二个参数时,几乎总是不是参数问题,而是设计问题。


2
“ cyprtic”:一种表达观点的好方法。
Bevan

2
你以前从未打过字,@ Bevan?

1
我一直都有错别字,写作赛普里斯太好了,我以为是故意的。请把它改回来,即使是偶然的情况,也可以帮助您阐明观点。
Bevan

1
@Bevan,哈哈,好吧,我明白你在说什么。好点子!它变回=)

1

有时候(很少),创建一个需要大量参数的函数似乎是最好的方法。

使用多个参数通常可以清楚地表明,您违反了此方法中的SRP。需要许多参数的方法不可能只做一件事。执行可能是数学函数或配置方法,实际上确实需要几个参数。我会避免使用多个参数,因为魔鬼会避开圣水。方法中使用的参数越多,该方法(过于)复杂的机会就越大;越复杂意味着:难以维护,就越不可取。

但是,当我这样做时,我觉得我经常随机选择参数的顺序。我通常按​​照“重要性顺序”进行操作,首先要考虑最重要的参数。

在priniple中,您是随机选择。当然,您可能会认为参数A比参数B更相关;但对于您的API用户而言却并非如此,他们认为B是最相关的参数。因此,即使您专注于选择顺序-对于其他人,它似乎也是随机的

有一个更好的方法吗?是否有一种“最佳做法”的参数排序方式可以提高清晰度?

有几种解决方法:

a)普通情况:不要使用多个参数。

b)如您未指定的那样,选择了哪种语言,就有可能选择了带有命名参数的语言。这是一个不错的语法糖,它使您可以放宽参数的排序的意义:fn(name:"John Doe", age:36)

并非每种语言都允许如此精美。那又如何呢?

c)您可以使用Dictionary / Hashmap / Associative Array作为参数:例如,Javascript将允许以下操作:fn({"name":"John Doe", age:36})与(b)相距不远。

d)当然,如果您使用的是Java之类的静态类型语言。您可以使用Hashmap,但是HashMap<String, Object>当参数具有不同的类型(并且需要强制转换)时,您会丢失类型信息(例如,使用时)。

下一步的逻辑步骤是传递一个Object具有适当属性的(如果使用Java的话)或更轻巧的结构(例如编写C#或C / C ++)。

经验法则:

1)最佳情况-您的方法根本不需要任何参数

2)好的情况-您的方法需要一个参数

3)可容忍的情况-您的方法需要两个参数

4)所有其他情况应重构


0

通常,将复杂的对象用作参数会更好-在大多数平台上都可以使用穷人版本的命名参数。并打开带有行为引导参数的门。

对于大多数事情,您可能不想要做一个例子,您可能想尝试阅读PHP的标准库文档。


0

我通常先按照要求订购它们,而不是根据“感觉”(可以视为ORDER BY required DESC, SOME_MAGIC_FEELING(importancy,frequency)),而不是根据任何特定的实践,结合重要性和使用频率的某种综合量度。

但是,正如其他人所指出的那样,我认为使该问题成为问题的根本问题是使用了太多的参数(恕我直言,任何大于3的参数都太多了),这是您应该解决的真正问题。丽贝卡·莫菲(Rebecca Murphey)的博客上有一篇有趣的文章。

我认为,当您只有1-3个参数时,正确的排序非常明显,而您只是“感觉”对了。


0

与@Wyatt Barnetts的答案类似,除了该方法的一些参数或非常明确的参数外,我建议改为传递一个对象。这通常更易于更新/维护,更清晰易读,并且消除了担心订购的必要性。同样,方法的太多参数是代码的味道,您可以遵循一些常见的重构模式来帮助纠正它。

明确的例子:

public int add(int left, int right)
{
  return left + right;
}

由于这是一个非常明确定义的示例,并且加法是可交换的(顺序无关紧要),所以就随它去吧。

但是,如果添加一些更复杂的内容:

public SomeComplexReturnValue Calculate(int i, float f, string s, object o)
{
  // do work here
}

会成为:

public class SomeComplexInputForCalculation
{
  public int i; 
  public float f;
  public string s; 
  public object o;
}

public SomeComplexReturnValue Calculate(SomeComplexInputForCalculation input)
{
  // do work here
}

希望这可以帮助...


0

以您认为curry最有可能从中受益的任何方式订购它。例如,首先使用功能参数。


0

“重要至上”并不意味着很多。有一些约定。

如果您传递了调用对象(通常称为发送者),则该方法优先。

列表应该有一定的流利性,这意味着您在阅读时应该知道参数的含义。例:

CopyFolder(字符串路径,bool递归);

如果将递归放在首位,那会造成混乱,因为还没有上下文。如果现在已经要复制(1),文件夹(2),则递归参数就变得有意义了。

这也使类似IntelliSense的功能可以很好地发挥作用:用户在填写自变量时会学习,他会逐渐了解该方法及其作用。同样,对于相等的零件,过载应保持相同的顺序。

然后,您可能仍然会发现在这方面存在“相等”的参数,并且您可能会想让顺序取决于参数的类型。仅仅因为连续有几个字符串然后有几个布尔值看起来更好。在某种程度上,这将成为一种艺术而不是一种技能。

我不太关心您应该将所有参数都包装到一个对象中以仅仅得到一个参数的说法。那只是在自欺欺人(它不会使方法变得更简单,它仍然具有所有这些参数,您只是将它们隐藏了)。如果将方法之间的设置传递几次,这可能仍然很方便,重构肯定会变得容易得多,但是在设计方面,这只是假装您有所作为。最终您将不会得到一个有意义的对象来表示某些东西,它只是一堆参数,与方法声明中的列表没有什么不同。这不会使鲍伯叔叔高兴。

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.