指定可选参数名称,即使不是必需的?


25

请考虑以下方法:

public List<Guid> ReturnEmployeeIds(bool includeManagement = false)
{

}

和以下调用:

var ids = ReturnEmployeeIds(true);

对于刚接触该系统的开发人员来说,很难猜得出是什么true。您要做的第一件事是将鼠标悬停在方法名称上或转到定义(都不是丝毫大的任务)。但是,出于可读性考虑,编写以下代码是否有意义:

var ids = ReturnEmployeeIds(includeManagement: true);

在编译器不需要您时,是否有任何地方正式讨论是否明确指定可选参数?

以下文章讨论了某些编码约定:https : //msdn.microsoft.com/zh-cn/library/ff926074.aspx

与上面的文章类似的东西会很棒。


2
这是两个不相关的问题:(1)为清晰起见,您是否应该写出可选参数?(2)一个应该使用boolean方法参数吗?
凯莉安

5
这一切都归结为个人喜好/风格/意见。就个人而言,当传入的值是文字时,我喜欢参数名称(变量可能已经通过其名称传达了足够的信息)。但是,那是我个人的看法。
MetaFight

1
也许在您的问题中包含该链接作为示例来源?
MetaFight

4
如果添加了任何内容,我可以通过StyleCop和ReSharper运行它-两者都没有清晰的视图。ReSharper的只是说命名参数可以被移除,然后接着说命名参数可以被添加。咨询是免费的,原因是我猜...:-/
Robbie Dee

Answers:


28

我想说在C#世界中,枚举将是这里最好的选择之一。

这样一来,您就不得不说出自己在做什么,任何用户都可以看到正在发生的事情。对于将来的扩展也很安全;

public enum WhatToInclude
{
    IncludeAll,
    ExcludeManagement
}

var ids = ReturnEmployeeIds(WhatToInclude.ExcludeManagement);

因此,我认为这里:

枚举>可选枚举>可选布尔

编辑:由于下面与LeopardSkinPillBoxHat讨论了一个[Flags]枚举,在这种特定情况下该枚举可能是一个合适的选择(因为我们专门在谈论包含/排除事物),所以我建议使用ISet<WhatToInclude>作为参数。

这是一个具有多个优点的“现代”概念,主要是因为它适合LINQ系列产品,但[Flags]最多也有32个组。使用的主要缺点ISet<WhatToInclude>是C#语法对集的支持程度:

var ids = ReturnEmployeeIds(
    new HashSet<WhatToInclude>(new []{ 
        WhatToInclude.Cleaners, 
        WhatToInclude.Managers});

某些辅助功能可能会缓解其中的一些功能,如生成集合和创建“快捷集合”,例如

var ids = ReturnEmployeeIds(WhatToIncludeHelper.IncludeAll)

我倾向于同意;并且如果在未来可能会扩大条件的情况下,通常会在我的新代码中使用枚举。当某些东西变成三态时,用枚举代替布尔会导致产生真正的噪音差异并使责备代码变得更加乏味。当您最终不得不在一年后再次对代码进行翻新以获取第三个选项时。
Dan Neely

3
我喜欢这种enum方法,但是我不是混合的忠实拥护者IncludeExclude而且同样enum很难掌握正在发生的事情。如果您希望此方法真正可扩展,则可以将其设置为a [Flags] enum,将其全部设置IncludeXxx,然后提供一个IncludeAll设置为Int32.MaxValue。然后,您可以提供2个ReturnEmployeeIds重载-一个重载所有雇员的重载,以及一个重载WhatToInclude过滤器参数(可以是枚举标志的组合)的重载。
LeopardSkinPillBoxHat

@LeopardSkinPillBoxHat好的,我同意排除/包含。因此,这是一个人为的示例,但我可以将其称为IncludeAllButManagement。关于您的另一点,我不同意-我认为这[Flags]是遗物,仅应出于遗留或性能原因使用。我认为a ISet<WhatToInclude>是一个更好的概念(不幸的是C#缺少一些语法糖来使它易于使用)。
NiklasJ

@NiklasJ-我喜欢这种Flags方法,因为它允许调用者WhatToInclude.Managers | WhatToInclude.Cleaners按位传递和准确地表达调用者将如何处理它(即管理人员或清洁工)。直观的语法糖:-)
LeopardSkinPillBoxHat

确实,@ LeopardSkinPillBoxHat,但这是该方法的唯一优势。缺点包括例如数量有限的选择,并且与其他类似于linq的概念不兼容。
NiklasJ

20

写这样有意义吗:

 var ids=ReturnEmployeeIds(includeManagement: true);

如果这是“好样式”,这是有争议的,但是恕我直言,这至少是不错的样式,并且有用,只要代码行不会变得“太长”即可。没有命名参数,引入解释变量也是一种常见的样式:

 bool includeManagement=true;
 var ids=ReturnEmployeeIds(includeManagement);

在C#程序员中,这种样式可能比命名参数变体更常见,因为命名参数不是4.0版之前的语言的一部分。对可读性的影响几乎相同,只需要增加一行代码即可。

因此,我的建议是:如果您认为使用一个具有两个不同名称的枚举或两个函数是“过大杀手”,或者您不想或由于其他原因无法更改签名,请继续使用命名参数。


可能要注意,您在第二个示例上使用了额外的内存
Alvaro

10
@Alvaro在这种琐碎的情况下(变量具有恒定值),这种可能性极小。即使是这样,也几乎不值得一提。如今,我们大多数人都没有在4KB的RAM上运行。
克里斯·海斯

3
由于这是C#,因此您可以将其标记includeManagement为本地,const并且完全避免使用任何其他内存。
雅各布·克拉尔

8
伙计们,我不会在答案中添加任何内容,这可能只会分散我在这里认为重要的内容。
布朗

17

如果遇到类似以下的代码:

public List<Guid> ReturnEmployeeIds(bool includeManagement = false)
{

}

您几乎可以保证里面的内容{}将是这样的:

public List<Guid> ReturnEmployeeIds(bool includeManagement = false)
{
    if (includeManagement)
        return something
    else
        return something else
}

换句话说,布尔值用于在两个操作过程之间进行选择:该方法具有两个职责。这几乎保证了代码的味道。我们应该始终努力确保方法只能做一件事情;他们只有一个责任。因此,我认为您的两种解决方案都是错误的方法。使用可选参数表明该方法正在做一件事。布尔参数也表明了这一点。两者都肯定会设置闹钟铃声。因此,您应该做的是将两组不同的功能重构为两种单独的方法。给方法命名以明确说明它们的作用,例如:

public List<Guid> GetNonManagementEmployeeIds()
{

}

public List<Guid> GetAllEmployeeIds()
{

}

这既提高了代码的可读性,又确保您更好地遵循SOLID原则。

编辑 如评论中所指出的,在这种情况下,这两种方法将可能具有共享功能,并且共享功能可能最好封装在私有函数中,该私有函数需要布尔参数来控制其功能的某些方面。

在这种情况下,即使编译器不需要,也应提供参数名称吗?我建议对此没有简单的答案,并且需要根据具体情况进行判断:

  • 指定名称的理由是其他开发人员(包括六个月内的您自己)应该是代码的主要受众。编译器绝对是第二读者。因此,始终编写代码以使其他人更容易阅读;而不只是提供编译器需要的东西。我们每天如何使用此规则的一个示例是,我们不使用变量_1,_2等填充代码,而是使用有意义的名称(这对编译器没有任何意义)。
  • 反论点是私有方法是实现细节。可以合理地期望任何人在查看下面的方法时,都将遍历代码以了解布尔参数的用途,因为它们位于代码的内部:
public List<Guid> GetNonManagementEmployeeIds()
{
    return ReturnEmployeeIds(true);
}

源文件和方法是否小巧且易于理解?如果是,则命名参数可能等于噪音。如果没有,那将很有帮助。因此,应根据情况应用规则。


12
我听到了你在说什么,但是你有点不明白我在问什么。假设一个场景,由此它最适合使用可选参数(这些情况确实存在),是否可以将命名参数,即使它是不必要的?
JᴀʏMᴇᴇ

4
完全正确,但问题在于有关此类方法的调用。同样,flag参数根本不能保证方法中的分支。它可能只是传递给另一层。
罗比·迪

这没有错,但是过于简单。在实际的代码中,您会发现public List<Guid> ReturnEmployeeIds(bool includeManagement) { calculateSomethingHereForBothBranches; if (includeManagement) return something else return something else },因此将其简单地分为两个方法可能意味着这两个方法需要将第一次计算的结果作为参数。
布朗

4
我认为我们都在说您的类的公开面孔可能应该公开两个方法(每个方法都有一个责任),但是在内部,这些方法中的每一个都可以合理地将请求转发给具有分支布尔参数的单个 私有方法。
MetaFight

1
我可以想象3种情况,这两种功能之间的行为有所不同。1.预处理是不同的。使公共函数将参数预处理为私有函数。2.后期处理有所不同。让公共职能对私有职能的结果进行后处理。3.中间行为有所不同。假设您的语言支持,则可以作为lambda传递给私有函数。通常,这会导致您以前从未真正想到过的可重用的新抽象。
jpmc26 2016年

1

对于刚接触该系统的开发人员来说,很难猜测是真的。您要做的第一件事是将鼠标悬停在方法名称上或转到定义(都不是丝毫大的任务)

没错。自我记录代码是一件美丽的事情。正如其他答案所指出的那样,有一些方法可以ReturnEmployeeIds通过使用枚举,不同的方法名称等来消除对调用的歧义。但是,有时候您无法真正避免这种情况,但是您不想离开并使用它们无处不在(除非您喜欢Visual Basic的冗长性)。

例如,它可能有助于弄清一个呼叫,但不一定有助于另一个。

命名参数在这里可能会有所帮助:

var ids = ReturnEmployeeIds(includeManagement: true);

没有增加更多的清晰度(我想这是在解析一个枚举):

Enum.Parse(typeof(StringComparison), "Ordinal", ignoreCase: true);

它实际上可能会降低清晰度(如果一个人不了解列表上的容量是什么):

var employeeIds = new List<int>(capacity: 24);

或无关紧要的地方,因为您使用的是好的变量名:

bool includeManagement = true;
var ids = ReturnEmployeeIds(includeManagement);

在编译器不需要您时,是否有任何地方正式讨论是否明确指定可选参数?

不是AFAIK。不过,让我们同时解决这两个部分:唯一需要显式使用命名参数的原因是编译器要求您这样做(通常是消除语句的歧义)。因此,除此之外,您还可以随时使用它们。MS的这篇文章对何时使用它们提供了一些建议,但是并不是特别明确的https://msdn.microsoft.com/en-us/library/dd264739.aspx

就我个人而言,我发现在创建自定义属性或使用新方法修改参数可能会更改或重新排序时(b / c命名属性可以以任何顺序列出),我经常使用它们。另外,我仅在提供静态值内联时使用它们,否则,如果我传递变量,则尝试使用良好的变量名。

TL; DR

最终归结为个人喜好。当然,在某些情况下,必须使用命名参数,但是之后需要进行判断调用。IMO-在您认为将有助于记录代码,减少歧义或保护方法签名的任何地方使用它。


0

如果该参数是显而易见的方法(完全限定)的名称,它的存在是很自然的是什么方法应该做的事,你应该使用命名参数语法。例如,ReturnEmployeeName(57)非常明显的57是员工的ID,因此用命名参数语法对其进行注释是多余的。

随着ReturnEmployeeIds(true),像你说的,除非你看看函数的声明/文档,这是不可能的身影什么true手段-所以你应该使用命名参数语法。

现在,考虑第三种情况:

void PrintEmployeeNames(bool includeManagement) {
    foreach (id; ReturnEmployeeIds(includeManagement)) {
        Console.WriteLine(ReturnEmployeeName(id));
    }
}

此处未使用命名参数语法,但是由于我们将变量传递给ReturnEmployeeIds,并且该变量具有名称,并且该名称恰好与我们将其传递给参数的含义相同(通常是这种情况-但并非总是如此!)-我们不需要命名参数语法即可从代码中了解其含义。

因此,规则很简单-如果您可以仅从方法调用中轻松了解该参数的含义,请不要使用命名参数。如果不能,则这是代码可读性的缺陷,您可能想要修复这些问题(当然,不惜一切代价,但是您通常希望代码可读性高)。该修补程序不必命名为参数-您也可以先将值放在变量中,也可以使用其他答案中建议的方法-只要最终结果易于理解参数的作用即可。


7
我不同意这ReturnEmployeeName(57)一点,使它立即显而易见57是ID。GetEmployeeById(57)另一方面...
雨果·辛克

@HugoZink我最初想在示例中使用类似的东西,但是我故意将其更改ReturnEmployeeName为演示这样的情况:即使没有在方法名称中明确提及参数,也可以合理地期望人们弄清楚它的含义。无论如何,值得注意的是,与不同ReturnEmployeeName,您不能合理地重命名ReturnEmployeeIds为表明参数含义的名称。
Idan Arye

1
无论如何,我们几乎不可能在真实程序中编写ReturnEmployeeName(57)。我们更有可能编写ReturnEmployeeName(employeeId)。我们为什么要对员工ID进行硬编码?除非这是程序员的ID,否则会发生某种欺诈行为,例如,向该员工的薪水加一点点。:-)
Jay

0

是的,我认为这是很好的风格。

这对于布尔常量和整数常量最有意义。

如果您要传递变量,则变量名称应使含义清楚。如果不是,则应给该变量起一个更好的名称。

如果要传递字符串,则含义通常很清楚。就像我看到对GetFooData(“ Oregon”)的调用一样,我认为读者几乎可以猜测该参数是状态名称。

当然,并非所有字符串都是显而易见的。如果我看到GetFooData(“ M”),则除非我知道该函数,否则我不知道“ M”是什么意思。如果是某种代码,例如M =“ management-level employees”,那么我们可能应该使用枚举而不是文字,并且枚举名称应该清楚。

而且我认为字符串可能会引起误解。在上面的示例中,“俄勒冈州”可能不是指州,而是指产品或客户或其他任何东西。

但是GetFooData(true)或GetFooData(42)...可能意味着任何事情。命名参数是使其自我记录的绝佳方法。我已经在许多场合将它们用于此目的。


0

没有什么超级清楚的理由可以说明为什么要首先使用这种语法。

通常,请尝试避免使用布尔参数,以使布尔参数看起来是任意的。includeManagement(最有可能)将极大地影响结果。但是论点似乎带有“很小的分量”。

已经讨论了枚举的使用,不仅看起来像参数“承担更多的重量”,而且还为该方法提供了可伸缩性。但是,这可能并不是所有情况下的最佳解决方案,因为您的ReturnEmployeeIds-方法必须与WhatToInclude-enum 一起扩展(请参见NiklasJ的答案)。稍后可能会让您头疼。

请考虑以下事项:如果缩放WhatToInclude-enum,而不是ReturnEmployeeIds-method。然后,它可能会抛出ArgumentOutOfRangeException(最佳情况)或返回完全不需要的内容(null或为空List<Guid>)。在某些情况下,这可能会使程序员ReturnEmployeeIds感到困惑,特别是如果您在类库中,而源代码不容易获得。

看到受训者是所有人的“子集” ,WhatToInclude.Trainees就可以假设这样做是WhatToInclude.All可行的。

(确实),这取决于如何 ReturnEmployeeIds实现。


在可以传递布尔参数的情况下,根据您的情况,我尝试将其分解为两个方法(如果需要,可以分为两个方法);有人可能会提取ReturnAllEmployeeIdsReturnManagementIdsReturnRegularEmployeeIds。这涵盖了所有基础,并且对于实施它的任何人都是绝对不言自明的。这也将没有上面提到的“缩放”问题。

因为具有布尔参数的事物只有两个结果。实现两种方法几乎不需要额外的工作。

更少的代码,很少更好。


与此说,也一些情况下,显式声明的参数提高了可读性。考虑一下。GetLatestNews(max: 10)GetLatestNews(10)仍然不言自明,的明确声明max将有助于消除任何混乱。

而且,如果您绝对必须具有布尔参数,则不能仅通过阅读true或来推断出哪种用法false。然后我会说:

var ids = ReturnEmployeeIds(includeManagement: true);

..绝对比:

var ids = ReturnEmployeeIds(true);

因为在第二个示例中,true可能绝对没有任何意义。但是我会尽量避免这种争论。

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.