输出参数有什么问题?

12

在SQL和C#中,我从未真正喜欢过输出参数。在VB6中,我也从不传递ByRef参数。依赖副作用来完成某些事情,总有些令人不安。

虽然我知道它们是解决无法从函数返回多个结果的一种方式,但在SQL中使用行集或在C#和VB中使用复杂数据类型同样有效,并且对我来说更加易于自我说明。

我的想法有问题吗?是否有权威来源的资源支持我的想法?你对此有何个人看法,并为什么?我应该如何说服想要使用输出参数进行设计的同事改用其他结构?

编辑:有趣的转折 - 我提出这个问题的输出参数是用于替代返回值的。当返回值为“ERROR”时,调用者应将其视为异常处理。我正在这样做,但是对此想法并不满意。由于一位同事没有被告知需要处理此条件,因此程序默默地失败导致损失了大量资金!


1
这是主观的,应该成为社区维基以保持存在。我注意到已经有一个相似的了。除非你不标记它为CW,否则我不会关闭它。 - Randolpho
4
这个问题听起来像是一个关于设计权衡的公正问题,但我知道什么呢。 - JeffH
将标签更改为在问题中包括 SQL + VB 引用 - gbn
@Randolpho,如果我编辑问题并包含一个具体的示例,并寻求重构的帮助,这样会减少主观性吗? - Chris McCall
没事,就这样吧。第二遍阅读时,我可能有点过于苛刻了。 :) - Randolpho
11个回答

28

输出参数可能是一个代码坏味道,表明你的方法做得太多。如果需要返回多个值,则该方法很可能在做多个事情。如果数据紧密相关,则可能受益于持有两个值的类。

当然,这并非总是如此,但我发现通常是这种情况。

换句话说,我认为你避免它们是正确的。


20

它们有各自的用处。Int32.TryParse方法是使用out参数有效的好例子。

bool result = Int32.TryParse(value, out number);
if (result)
{
    Console.WriteLine("Converted '{0}' to {1}.", value, number);         
}

1
好的例子。TryParse是我见过的仅有的使用out参数并且有充分理由这么做的方法之一。 - Jagd
10
我个人更倾向于使用"int? Int32.TryParse(string value)"这个函数签名,因为这样你可以使用 ?? 运算符来指定备用值。但是,这个函数是在可 null 类型被引入之前编写的。 - Jimmy
5
我不同意... Int32.TryParse() 应该返回 int?,任何 Try* 方法都应该返回可空类型... 这样更易于阅读和使用。我为字符串创建了扩展方法,可以精确地实现此功能。“1”.AsInt()、“1.5”.AsDouble()、“true”.AsBool() 等。 - Brian Genisio
1
@Pavel:我认为显著的区别在于,使用out参数时,变量需要单独声明。任何减少临时变量数量的机会都是使我的代码更易读的机会,在我的书中这是一种胜利 :) - Brian Genisio
1
大多数情况下,当我需要检查一个字符串是否正确解析为另一种类型时,我也关心其值。因此,我认为使用这个 bool TryParse(out) 没有任何价值。即使在你不关心值并想编写丑陋代码的罕见情况下,if(int.TryParse(s).HasValue) 也可以正常工作。 - Samantha Branham
显示剩余5条评论

11

Bob Martin提到了Clean Code的概念,输出参数违背了函数的基本原则。

output = someMethod(input)


存储过程虽然不是函数,但我理解其中的原因。 - gbn
1
看起来 Bob Martin 的链接现在已经失效了。 - PaulBinder

1

我认为它们对于在同一SQL命令中获取新插入行的ID非常有用,但我不认为我还用它们做了其他很多事情。


1

我也很少使用out/ref参数,尽管在SQL中,通过参数返回值有时比通过结果集更容易(这将需要使用DataReader等)

不过,碰巧的是,今天我刚刚在C#中创建了一个这样罕见的函数。它验证了一个类似表格的数据结构,并返回了其中的行数和列数(由于该表格可能具有像HTML中的行跨度/列跨度一样的行跨度/列跨度,因此计算起来比较棘手)。在这种情况下,两个值的计算同时进行。将其分成两个函数将导致代码、内存和CPU时间要求翻倍。对于仅用于返回此功能的自定义类型,对我来说似乎也有些过度。

因此 - 有时它们是最好的选择,但大多数情况下,您可以完全没有它们。


1

SQL Server 2005及以后版本中的OUTPUT语句是获取受DML语句影响的行的任何字段值的重要一步。我认为在许多情况下,这可以省去输出参数的使用。

Vb6中,ByRef参数对于传递ADO对象非常有用。

除了这两种特定情况之外,我倾向于避免使用它们。


1

在SQL中...

存储过程的输出参数非常有用。

  1. 假设你需要返回一个值。你是要"创建#表,插入...执行,选择@var = ",还是使用输出参数?

  2. 对于客户端调用,输出参数比处理记录集要快得多。

  3. 使用RETURN值仅限于有符号整数。

  4. 更容易重复使用(例如安全检查辅助程序)

  5. 当同时使用时:记录集=数据,输出参数=状态/消息/行数等

  6. 存储过程记录集输出无法像UDF或客户端代码那样强类型化

  7. 您并不总是可以使用UDF(例如在上面进行安全检查期间记录日志)

然而,只要通常不将同一参数用于输入和输出,那么在SQL完全改变之前,您的选择是有限的。话虽如此,我有一个情况,在这种情况下,我使用参数作为输入和输出值,但我有一个很好的理由。


1

我的两分钱意见:
我同意输出参数是一个令人担忧的做法。VBA通常由编程新手维护,如果维护代码的人没有注意到参数是ByRef,他们可能会引入一些严重的逻辑错误。此外,它确实破坏了属性/函数/子程序范例。
使用输出参数的另一个原因是不好的做法是,如果您真的需要返回多个值,那么很有可能应该将这些值放在数据结构中,例如类或用户定义类型。
然而,它们可以解决一些问题。VB5(因此Office 97的VBA)不允许函数返回数组。这意味着任何返回或更改数组的内容都必须通过“out”参数进行。在VB6中,已经添加了这种能力,但VB6仍然强制数组参数为按引用传递(以防止在内存中进行过多的复制)。现在您可以从修改数组的函数中返回值。但是它会稍微慢一点(由于幕后正在进行的杂技表演),它还会让新手混淆输入的数组是否会被更改(只有在某人特别构造它的方式时才是真的)。因此,如果我有一个修改数组的函数,我发现使用子程序而不是函数可以减少混淆(它也会稍微快一点)。
另一个可能的情况是,如果您正在维护代码并且想要添加一个输出值而不破坏接口,则可以添加一个可选的输出参数,并确信您不会破坏任何旧代码。这不是一个好的做法,但如果有人想要立即修复某些东西,而您没有时间以“正确的方式”重新构建所有内容,那么这可能是您工具箱中方便的补充。
但是,如果您从头开始开发事物并且需要返回多个值,则应考虑:
1. 分解函数。
2. 返回UDT。
3. 返回类。


0

我通常不使用它们,因为我认为它们容易混淆并且容易被滥用。我们偶尔会使用ref参数,但这更多是与传递结构有关而不是获取它们。


0

我认为你的观点很有道理。

输出参数的另一个缺点是需要额外的代码来传递一个函数到另一个函数的结果。您必须声明变量,调用函数以获取它们的值,然后将这些值传递给另一个函数。您不能只嵌套函数调用。这使得代码的阅读非常命令式,而不是声明式的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接