何时何地应使用void(而不是例如bool / int)


30

我偶尔会碰到一些方法,开发人员选择返回一些对函数不重要的东西。我的意思是,当看代码时,它显然void和一样好,并且经过一会儿思考,我问“为什么?” 这听起来很熟悉吗?

有时我会同意,通常最好返回一个诸如boolor的值int,而不是仅仅做一个void。从总体上看,我不确定优缺点。

根据情况,返回int可以使调用方知道受该方法影响的行或对象的数量(例如,将5条记录保存到MSSQL)。如果“ InsertSomething”之类的方法返回布尔值,那么我可以将设计为true成功的方法返回,否则返回false。呼叫者可以选择对该信息采取行动或不采取行动。

另一方面,

  • 会导致方法调用的目的不明确吗?错误的编码经常迫使我仔细检查方法的内容。如果返回某些内容,它将告诉您该方法的类型必须对返回的结果进行某些处理。
  • 另一个问题是,如果您不知道方法的实现,那么开发人员决定返回什么功能并不关键的东西?您当然可以发表评论。
  • 当处理可以在方法的右括号中结束时,必须处理返回值。
  • 引擎盖下会发生什么?调用的方法是否false由于抛出错误而得到?还是由于评估结果而返回false?

您对此有何经验?您将如何处理?


1
从技术上讲,返回void的函数根本不是函数。这只是一个方法/过程。返回void至少让开发人员知道该方法的返回值无关紧要;它执行一个动作,而不是计算一个值。
KChaloux 2012年

Answers:


32

在布尔值返回值指示方法成功或失败的情况下,我更喜欢Try在.NET方法中使用的-prefix范式。

例如,void InsertRow()如果已经存在具有相同键的行,则方法可能引发异常。问题是,假设调用者在调用InsertRow之前确保其行是唯一的,是否合理?如果答案是否定的,那么我还将提供一个bool TryInsertRow()如果行已存在则返回false的值。在其他情况下,例如数据库连接错误,假设保持数据库连接是调用者的责任,TryInsertRow仍可能引发异常。


4
+1是区分预期失败和意外失败的方法-我自己的回答没有足够强调这一点。
彼得Török

尝试发明TryXX方法原理的绝对值是+1,这显然使try方法和main方法都更易于维护并且更易于理解其目的。
独立

+1说得好。该问题的最佳答案的关键是,有时您想给调用方一个断言方法的选项,该方法将引发异常。但是,在这些情况下,不应通过断言方法捕获异常并进行伪处理。关键是使呼叫者承担责任。另一方面,Try范式(或Monad)应捕获并处理异常,然后如果需要更多详细信息,则传递回bool或描述性Enum。请注意,呼叫者希望Try / Monad绝不会引发异常。这就像一个入口点。
Suamere

因此,由于预计Try / Monad永远不会发生异常,因此bool的描述性不足以通知调用者db连接失败,或者http请求具有许多以不同方式执行的返回值之一,您可以在尝试/单声道中使用枚举而不是布尔值。
Suamere

如果您有TryInsertRow-返回布尔值-例如,如果数据库中有多个唯一约束-您如何确切知道错误是什么-并将其传达给用户?应该使用包含错误消息/代码和成功布尔值的更丰富的返回类型吗?
niico

28

恕我直言,返回“状态代码”源于历史,当时在中高级语言(例如C#)中,异常变得司空见惯。如今,如果某些意外错误阻止您的方法成功,则最好引发异常。这样可以确保不会引起错误,并且调用者可以适当地处理它。因此,在这些情况下,方法最好不返回任何值(即void),而不是布尔状态标志或整数状态码。(当然,应该记录该方法可能引发什么异常以及何时抛出该异常。)

另一方面,如果严格意义上讲它是一个函数,即它基于输入参数执行一些计算,并且期望返回该计算的结果,则返回类型很明显。

有两个,当一个人之间的灰色地带可能决定从该方法返回一些“额外”的信息,如果它被认为是有用的为它的调用者。就像您的示例中有关插入中受影响的行数的示例一样。或者对于将元素放入关联集合的方法,返回与该键关联的先前值(如果有的话)可能很有用。可以通过仔细分析API的(已知和预期)使用方案来识别此类使用。


4
+1表示状态码例外。例外(打算使用双关语)是常用的TryXX模式。
安迪·洛瑞

我和你在一起。TcF并不会使错误消失!不过,可能是灰色区域。有可能永远是使用一些回报。受影响的行,最新插入的ID,运行的软件的PID,但我仍然认为在开发时更容易想到“是的,我返回了这个”。然后,一年后,当扩展时,“什么?这是一个'int someThing = RunProcess(x)',如果它不能运行会发生什么呢?”
独立

+1额外信息是为什么我用C#这样的高级语言编写此类方法的原因。这样可以在需要时轻松使用额外的信息。
ashes999 2011年

2
-1哇,你自己的话太矛盾和错了。绝对不要将异常用于控制流或通信。它们比有条件的昂贵得多。什么时候是“异常变得普遍之前”?...您的意思是在try / catch块变得普遍之前?从一开始,例外就很普遍。“如果发生意外错误,则引发异常...”您如何对意外事件采取措施?您为什么要捕获异常,然后重新抛出呢?那太贵了。为什么说“错误”?错误和异常是不同的东西。
Suamere

1
谁说抛出异常不会被忽略?什么都没有强迫任何人登录catch块,为什么不登录“ then”块呢?为什么要“记录该方法可能在何时抛出哪些异常”?如果我们编写自文档代码,而一个方法仅返回带有该方法的预期最终状态的枚举,该怎么办?如果通过检查状态可以避免可能的异常,则应将其捕获和处理而不会抛出异常。
Suamere

14

彼得的答案很好地涵盖了例外情况。这里的其他考虑还涉及命令查询分离

CQS原则说,方法应该是命令或查询,而不是两者。命令永远不要返回值,只能修改状态,查询应该只返回而不修改状态。这样可以使语义非常清晰,并有助于使代码更具可读性和可维护性。

有少数情况是违反CQS原则是一个好主意。这些通常围绕性能或线程安全性。


1
-1错误和错误。首先,CQS的重点在于Q。调用方应该能够通过某些有状态服务获得计算或外部调用,而不必担心更改该服务的状态。同样,尽管CQS是一个有用的原则,可以宽松地遵循,但只有在特定设计中才严格遵循。最后,即使在严格的CQS中,也没有人说命令不能返回值,它说它不应该在计算或调用外部计算并返回数据。如果C或Q对调用者有用,则可以随意返回其最终状态。
Suamere

嗯,但是受命令影响的行数使从旧命令创建新命令变得容易得多。资料来源:多年的经验。
约书亚记

@Joshua:同样,“添加新记录并返回在添加过程中生成的ID(对于某些结构是行号)”可以用于无法有效实现的目的。
超级猫

您不需要返回受影响的行。该命令应该知道有多少受影响。如果除了那个#以外,它应该回滚并引发异常,因为发生了一些奇怪的事情。因此,调用者实际上并不关心该信息(除非用户需要知道真实的#,并且您不能从给定的数据中分辨出来)。仍然存在灰色区域,例如DB生成的ID,堆栈/队列,在这些灰色区域中获取数据会更改其状态,但是我喜欢在这些情况下创建“ CommandQuery”,因此没有人会尝试做“怪异”的事情。
丹尼尔·洛伦兹

3

无论走什么路,您都将沿着这个道路走。那里没有返回值以备将来使用(例如,函数总是返回true),这是非常浪费的工作,并且不会使代码更清晰易读。当您有返回值时,应该始终使用它,并且要能够使用它,您至少应具有2个可能的返回值。


+1这不能回答问题,绝对是正确的信息。做出决策时,决策应基于需求。如果调用者发现返回数据不有用,则不应这样做。没有人会发现在100%的时间返回真正的“未来证明”方面有用。当然,如果“未来”就像……从现在开始几天,并且存在一项任务,那就大不一样了,哈哈。
Suamere

1

我曾经写很多void函数。但是,由于我掌握了整个方法的链式破解方法,因此我开始返回此方法而不是返回void -最好还是让某人利用该返回值,因为您无法蹲空。而且,如果他们不想对此做任何事情,则可以忽略它。


1
方法链接可能会使继承变得麻烦且困难。除非您有充分的理由要这样做,否则我不会进行方法链接,除了不想给您的方法“空”。
2011年

真正。但是继承可能很麻烦且难以使用。
Wyatt Barnett

看着一个未知的代码返回什么(提到整个对象)非常注意,只需检查内部和外部方法的流程即可
独立

我不认为您会Ruby在某个时候加入?这就是向我介绍方法链接的原因。
KChaloux 2012年

我对方法链接有一个疑问,那就是它使得不清楚类似的语句return Thing.Change1().Change2();是期望更改Thing并返回对原始对象的引用,还是希望返回对不同于原始对象的新事物的引用,同时又保留了原始不变。
超级猫
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.