有什么更好的IllegalStateException或无声方法执行?[关闭]


16

假设我有一个MediaPlayer类,该类具有play()和stop()方法。如果以前没有调用过play方法,则在实现stop方法时使用的最佳策略是什么。我看到两个选项:由于播放器未处于适当状态而引发异常,或无提示地忽略对​​stop方法的调用。

当在某些情况下不应该调用某个方法但该方法的执行对程序总体无害时,一般规则应该是什么?


21
不要使用异常来对异常行为进行建模。
亚历山大–恢复莫妮卡

1
嘿! 我不认为这是重复的。我的问题更多是关于IllegalStateException,而上述问题是关于一般使用异常的。
x2bool,

1
为何不仅仅默默地失败,还为什么不将异常记录为警告?您所使用的Java当然可以支持不同的日志级别(ERROR,WARN,INFO,DEBUG)。
埃里克·海斯特兰德

3
请注意,如果该项已经在集合中,则Set.add不会引发异常。其目的是“确保元素在集合中”,而不是“将项目添加到集合中”,因此,如果元素已经在集合中,则不执行任何操作即可达到其目的。
user253751'5

2
今日词汇:幂等性
朱尔斯

Answers:


37

没有规则。这完全取决于您要如何使自己的API感到“满意”。

就个人而言,我认为在音乐播放器中,从状态StoppedStopped方法的转换Stop()是完全有效的状态转换。它不是很有意义,但是是有效的。考虑到这一点,抛出异常似乎是ped脚和不公平的。这将使API感觉就像在校车上与讨厌的孩子聊天一样具有社交感。您对烦人的情况感到困惑,但是可以解决。

一种更“善于交际”的方法是承认这种过渡在最坏的情况下是无害的,并且通过允许它对使用中的开发人员友好。

因此,如果由我决定,我将跳过该异常。


15
这个答案提醒我们,有时即使是简单的交互,也要勾勒出状态转换是一个好主意。

6
考虑到这一点,抛出异常似乎是脚和不公平的。这将使API感觉就像在校车上与讨厌的孩子聊天一样具有社交感。>异常并非旨在惩罚您:它们旨在告诉您发生了意外情况。我个人将非常感激这种MediaPlayer.stop()方法,IllegalStateException而不是花费数小时来调试代码,并且想知道为什么地狱“什么也没有发生”(即“它根本不起作用”)。
errantlinguist 2016年

3
当然,如果您的设计表明环回转换无效,则是,您应该抛出异常。但是,我的回答是关于一种过渡完全可以的设计。
MetaFight

1
StopOrThrow()听起来很恐怖。如果您要沿着那条小巷走,为什么不使用标准模式TryStop()?同样,通常,当API的行为不清楚(大多数情况下)时,不希望开发人员只是简单地猜测或尝试,就应该让他们查看文档。这就是为什么我如此喜欢MSDN。
MetaFight

1
就个人而言,我更喜欢使用fail-fast选项,所以我会选择IllegalStateException以便向API的用户表明他的逻辑中有问题,即使这是无害的。我经常从自己的代码中获取这些异常,这对于发现我未完成的工作已经是错误的
很有帮助

17

一个人可能希望执行两种不同的动作:

  1. 同时测试某事物处于一种状态,然后将其更改为另一种状态。

  2. 将某些内容设置为特定状态,而不考虑先前的状态。

有些情况需要一个动作,而另一些情况则需要另一种动作。如果到达内容结尾的媒体播放器将保持“播放”状态,但位置冻结在结尾,则该方法会同时断言播放器处于播放状态,同时将其设置为“停止”如果代码要确保在停止请求之前没有导致回放失败的状态,则state可能有用(如果例如正在录制正在回放的内容,以将媒体从一种格式转换为另一种格式,则这可能很重要) 。但是,在大多数情况下,重要的是在操作之后,媒体播放器处于预期状态(即停止)。

如果媒体播放器到达媒体末尾时会自动停止播放,则断言播放器正在运行的功能可能会比帮助有用,但是可能在某些情况下知道播放器在停止时是否正在运行有用。满足这两种需求的最佳方法可能是让函数返回一个指示玩家以前状态的值。关心那个状态的代码可以检查返回值;无关紧要的代码可以简单地忽略它。


2
+1有关返回旧状态的注释:允许以使整个操作(查询状态并转换为已定义状态)原子的方式实现它,至少这是一个不错的功能。这将使编写健壮的用户代码变得更加容易,而不会由于某些奇怪的竞争条件而偶尔失败。
cmaster-恢复莫妮卡

一个bool TryStop()void Stop()代码示例将使这是一个真正优秀的答案。
RubberDuck

2
@RubberDuck:那不是一个好的模式。该名称TryStop表示如果不能强迫播放器进入停止状态,则不应引发异常。试图在播放器已经停止时停止播放器并不是一个例外情况,而是呼叫者可能感兴趣的条件
。– supercat

1
然后我误解了您的答案@supercat
RubberDuck

2
我想指出的是,以这种方式进行测试和设置并不违反demeter法则,只要该API还提供了一种无需更改即可测试播放状态的方法。据说这可能是一种完全不同的方法isPlaying()
candied_orange

11

没有一般规则。在这种特定情况下,您的API用户的意图是阻止播放器播放媒体。如果播放器不在播放媒体,则媒体播放器MediaPlayer.stop()可能什么也不做,并且方法调用者的目标仍将实现-媒体不在播放。

抛出异常将要求API的用户检查播放器当前是否正在播放或捕获并处理该异常。这将使API更加难以使用。


11

例外的目的不是要预示发生了什么坏事。表示

  1. 坏事发生了
  2. 我不知道如何解决这里
  3. 调用者或调用栈中的对象应该知道如何处理
  4. 因此,我迅速采取行动,暂停执行当前代码路径,以防止损坏或破坏数据,并将其留给调用方进行清理

如果调用者试图Stop一个MediaPlayer一个已经处于停止状态,这不是一个问题,即MediaPlayer无法解析(也可以干脆什么也不做。)这不是一个问题,如果继续下去,会导致数据损坏或腐败,因为无所事事就可以成功。实际上,期望调用者能够解决比调用者更合理的问题并不是真正的问题MediaPlayer

因此,在这种情况下,您不应抛出异常。


+1。这是第一个答案,显示了为什么此处不适合使用异常。异常表明呼叫者可能需要了解例外情况。在这种情况下,几乎可以肯定,所涉及的类的客户端不会关心调用“ stop()”是否实际上导致了状态转换,因此可能不希望知道异常。
Jules

5

这样看:

如果客户端在不播放播放器时调用Stop(),则Stop()自动成功,因为播放器当前处于停止状态。


3

规则是您执行方法合同所要求的。

我可以看到为这种stop方法定义有意义的合同的多种方法。stop如果播放器不播放,则该方法完全不执行任何操作可能是完全有效的。在这种情况下,您可以根据其目标定义API。您想要过渡到stopped状态,该stop方法就可以做到这一点-只需从stopped状态无错误地过渡到自身即可。

有什么理由引发异常吗?如果用户代码最后看起来像这样:

try {
    player.stop();
} catch(IllegalStateException e) {
    // do nothing, player was already stopped
}

那就没有例外了。所以问题是,用户是否会担心播放器已经停止的事实?他还有另一个问题要查询吗?

玩家可能会观察到的,并且可能已经通知对某些事情的观察员-当它从transitons如通知观察者playingstoppingstopped。在这种情况下,不必使方法引发异常。stop如果播放器已经停止,调用将不执行任何操作,而在播放器未停止时调用将通知观察者有关过渡的信息。

总而言之,这取决于逻辑的最终结果。但是我认为有更好的设计选择,然后引发例外。

如果调用者必须干预,则应抛出异常。在这种情况下,他不必这样做,您可以简单地让播放器保持原样,然后继续工作。


3

有一个简单的一般策略可以帮助您做出此决定。

考虑如果要抛出异常,将如何处理该异常。

因此,想象一下您的音乐播放器,用户单击“停止”,然后再次停止。您要在这种情况下显示错误消息吗?我还没有看过这样的球员。您是否希望应用程序行为与仅单击一次停止(例如记录事件或在后台发送错误报告)有什么不同?可能不会。

这意味着需要在某个地方捕获并吞下异常。这意味着您最好不要首先抛出异常,因为您不想采取不同的行动

这不仅适用于异常,而且适用于任何种类的分支:基本上,如果行为上不需要有可观察的差异,就不需要分支。

就是说,定义您的stop()方法返回一个boolean或一个枚举值指示停止是否“成功” 并没有多大危害。您可能永远不会使用它,但是与异常相比,返回值更容易自然地被忽略。


2

这是android的工作方式:MediaPlayer

简而言之,stopstart没有被调用不是问题时,系统会保持停止状态,但是如果在甚至不知道正在播放什么的播放器上调用,则会引发异常,因为没有充分的理由调用停在那里


1

当在某些情况下不应该调用某个方法但该方法的执行对程序总体无害时,一般规则应该是什么?

我认为您的陈述中可能有一个小矛盾,可能会使您明白答案。为什么不应该调用方法执行方法却无害呢?

为什么该方法“不应该被调用”?这似乎是一个自我施加的限制。如果对您的API没有任何“危害”或影响,那么也不例外。如果确实不应该调用该方法,因为它可能会创建无效或不可预测的状态,则应引发异常。

例如,如果我走到DVD播放机上并在单击“开始”之前先按“停止”,然后它崩溃了,那对我来说就没有意义了。它应该坐在那里,或者更糟的是,在屏幕上确认“停止”。错误会很烦人,对您毫无帮助。

但是,如果已经关闭(调用该方法),我是否可以输入一个警报代码将其“关闭”,这可能会导致它进入一种状态,从而阻止以后对其进行适当的布防?如果是这样,则即使从技术上讲“没有造成任何伤害”,也将引发错误,因为以后可能会出现问题。我想知道这一点,即使它实际上并没有进入那种状态。只是可能性就足够了。这将是一个IllegalStateException

在您的情况下,如果您的状态机可以处理无效/不必要的调用,请忽略它,否则抛出错误。

编辑:

请注意,如果您两次设置变量而不检查值,则编译器不会调用错误。这也不会阻止您重新初始化变量。从一个角度看,有很多动作可以被认为是“错误”,而仅仅是“低效”,“不必要”,“毫无意义”等。我试图在我的回答中提供这种观点-通常不考虑做这些事情一个“错误”,因为它不会导致任何意外情况。您可能应该同样处理您的问题。


不应调用它,因为调用它表明调用者中存在错误。
user253751'5

@immibis:不一定。例如,该调用者可以是某个UI按钮的单击侦听器。(作为用户,如果介质已经停止,如果您偶然不小心单击了“停止”按钮,则可能不希望收到错误消息。)
优点

@meriton如果它是UI按钮的单击侦听器,则答案将很明显-“如果按下按钮,则要执行任何操作”。显然不是,这是应用程序内部的东西。
user253751 '16

@immibis-我编辑了回复。希望对您有所帮助。
吉姆(Jim)

1

究竟是做什么MediaPlayer.play()MediaPlayer.stop()做什么?-它们是事件监听器以供用户输入,还是实际上是在系统上启动某种媒体流的方法?如果它们都是用户输入的侦听器,那么他们什么也不做是完全合理的(尽管至少将它们记录在某个地方是个好主意)。但是,如果它们影响模型的UI被控制,那么他们可能会引发IllegalStateException因为球员应该有某种isPlaying=trueisPlaying=false状态(它不一定是一个实际的布尔标志喜欢这里写的),所以当你打电话MediaPlayer.stop()isPlaying=false,该方法实际上无法“停止”,MediaPlayer因为对象未处于要停止的适当状态-请参见java.lang.IllegalStateException 类:

表示已在非法或不适当的时间调用了方法。换句话说,对于所请求的操作,Java环境或Java应用程序没有处于适当的状态。

为什么抛出异常会很好

许多人似乎认为,一般来说,异常是不好的,因为它们永远都不会抛出(参见Joel on Software)。但是,假设您有一个MediaPlayerGUI.notifyPlayButton()which调用MediaPlayer.play(),并且MediaPlayer.play()调用了一堆其他代码,并且在它下面与例如PulseAudio接口的那一行进行调用,但是我(开发人员)不知道在哪里,因为我没有编写所有这些代码。

然后,有一天在编写代码时,我单击“播放”,但没有任何反应。如果MediaPlayerGUIController.notifyPlayButton()记录了某些内容,至少我可以查看日志并检查按钮的点击实际上是否已注册...但是为什么不播放?好吧,说一下,实际上MediaPlayer.play()调用的PulseAudio包装器有问题。我再次点击“播放”按钮,这次我得到了IllegalStateException

 Exception in thread "main" java.lang.IllegalStateException: MediaPlayer already in play state.

     at org.superdupermediaplayer.MediaPlayer.play(MediaPlayer.java:16)
     at org.superdupermediaplayer.gui.MediaPlayerGUIController.notifyPlayButton(MediaPlayerGUIController.java:25)
     at org.superdupermediaplayer.gui.MediaPlayerGUI.main(MediaPlayerGUI.java:14)

通过查看该堆栈跟踪,我可以MediaPlayer.play()在调试之前忽略所有内容,并花时间弄清楚为什么例如没有消息传递到PulseAudio以启动音频流。

另一方面,如果您抛出异常,那么除了“愚蠢的程序不会播放任何音乐”之外,我将无事可做。虽然不是那么糟糕,因为明确的错误隐藏,如实际吞咽其中一个例外在事实上抛出,你仍然有可能浪费别人的时候确实存在错误的时间......所以是很好,在他们抛出异常。


0

我更喜欢以一种使消费者更难或不可能出错的方式设计API。例如,您可以提供在“已停止”和“正在播放”之间切换的功能,而不必使用MediaPlayer.play()和。这样,该方法始终可以安全调用-不会进入非法状态。MediaPlayer.stop()MediaPlayer.playToggle()

当然,这并非总是可能或容易做到的。您提供的示例类似于尝试删除已从列表中删除的元素。你可以

  • 务实,不会抛出任何错误。这无疑简化了许多代码,例如,if (list.contains(x)) { list.remove(x) }您不必只需要编写代码list.remove(x)。但是,它也可以隐藏错误。
  • 否则您可能会脚,并提示错误列表不包含该元素。有时,代码会变得更加复杂,因为在调用该方法之前,您必须不断地验证前提条件是否满足,但是这样做的好处是,最终的错误更易于跟踪。

如果MediaPlayer.stop()在它停止后调用不会对您的应用程序造成伤害,那么我会让它静默运行,因为它简化了代码,并且我有幂等方法。但是,如果您绝对确定MediaPlayer.stop()在那种情况下永远不会调用它,那么我会抛出一个错误,因为代码中的其他地方可能存在错误,而异常会帮助您找到错误。


3
我不同意playToggle()使用起来更简单:要达到理想的效果(播放器播放还是不播放),您需要了解其当前状态。因此,如果收到要求停止播放器的用户输入,则首先需要查询播放器当前是否正在播放,然后根据答案切换其状态。这比仅调用一个stop()方法并完成它要麻烦得多。
cmaster-恢复莫妮卡

好吧,我从未说过它更容易使用-我声称它更安全。另外,您的示例假定将为用户提供单独的停止和播放按钮。如果确实如此,那么playToggle使用a 将非常麻烦。但是,如果还给用户提供了一个切换按钮,则playToggle它将比播放/停止更容易使用。
佩德罗·罗德里格斯
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.