Qual é a melhor IllegalStateException ou execução de método silencioso? [fechadas]

16

Digamos que eu tenha uma classe MediaPlayer que possua os métodos play () e stop (). Qual é a melhor estratégia a ser usada ao implementar o método stop, caso o método play não tenha sido chamado anteriormente. Eu vejo duas opções: lançar uma exceção porque o player não está no estado apropriado ou ignorar silenciosamente as chamadas para o método stop.

Qual deve ser a regra geral quando um método não deve ser chamado em alguma situação, mas sua execução não prejudica o programa em geral?

x2bool
fonte
21
Não use exceções para modelar comportamentos que não sejam excepcionais.
Alexander - Restabelece Monica 9/16
1
Ei! Eu não acho que é uma duplicata. Minha pergunta é mais sobre IllegalStateException e a pergunta acima é sobre o uso de exceções em geral.
x2bool
1
Em vez de APENAS falhar silenciosamente, por que não TAMBÉM registrar a anomalia como um aviso? Certamente o Java que você está usando suporta diferentes níveis de log (ERRO, WARN, INFO, DEBUG).
Eric Seastrand
3
Observe que Set.add não lança uma exceção se o item já estiver no conjunto. Seu objetivo é "garantir que o elemento esteja no conjunto", em vez de "adicionar o item ao conjunto", para que ele atinja seu objetivo sem fazer nada se o elemento já estiver no conjunto.
User253751
2
Palavra do dia: idempotência
Jules

Respostas:

37

Não existe regra. Depende inteiramente de como você deseja que sua API "pareça".

Pessoalmente, em um music player, acho que uma transição do estado Stoppedpara Stoppedpor meio do método Stop()é uma transição de estado perfeitamente válida. Não é muito significativo, mas é válido. Com isso em mente, lançar uma exceção pareceria pedante e injusto. Isso faria a API parecer o equivalente social de conversar com o garoto irritante no ônibus escolar. Você está preso à situação chata, mas pode lidar com isso.

Uma abordagem mais "sociável" é reconhecer que a transição é inofensiva e ser gentil com seus desenvolvedores consumidores, permitindo-a.

Então, se dependesse de mim, eu pularia a exceção.

MetaFight
fonte
15
Essa resposta nos lembra que, às vezes , é uma boa ideia esboçar as transições de estado para interações simples.
6
Com isso em mente, lançar uma exceção pareceria pedante e injusto. Isso faria a API parecer o equivalente social de conversar com o garoto irritante no ônibus escolar. > Exceções não foram projetadas para punir você: foram projetadas para lhe dizer que algo inesperado aconteceu. Pessoalmente, ficaria muito grato por um MediaPlayer.stop()método que lançou um IllegalStateExceptioncódigo em vez de passar horas depurando e me perguntando por que o inferno "nada acontece" (ou seja, "simplesmente não funciona").
Errantlinguist # 9/16
3
Obviamente, se seu design disser que a transição de loopback é inválida, SIM você deve lançar uma exceção. No entanto, minha resposta foi sobre um design em que essa transição está perfeitamente correta.
MetaFight 9/16
1
StopOrThrow()parece horrível. Se você está indo para o beco, por que não usar o padrão de TryStop()? Além disso, geralmente, quando o comportamento de uma API não é claro (como a maioria é), não se espera que os desenvolvedores adivinhem ou experimentem, eles devem olhar a documentação. É por isso que gosto tanto do MSDN.
MetaFight 10/05
1
Pessoalmente, eu prefiro a opção fail-fast, então eu usaria o IllegalStateException para indicar ao usuário da API que há algo errado em sua lógica, mesmo que seja inofensivo. Eu muitas vezes obter me essas exceções de meu próprio código, realmente prestável para detectar que o meu trabalho unfisnihed já está errado
Walfrat
17

Existem dois tipos distintos de ações que você pode querer executar:

  1. Teste simultaneamente se algo está em um estado e mude para outro.

  2. Defina algo para um estado específico, sem considerar o estado anterior.

Alguns contextos requerem uma ação e alguns exigem a outra. Se um reprodutor de mídia que atingir o final do conteúdo permanecerá em um estado de "reprodução", mas com a posição congelada no final, um método que afirme simultaneamente que o reprodutor estava em um estado de reprodução enquanto o define como "parar" O estado pode ser útil se o código quiser garantir que nada tenha causado falha na reprodução antes da solicitação de parada (o que pode ser importante se, por exemplo, algo estiver gravando o conteúdo que está sendo reproduzido como forma de converter a mídia de um formato para outro) . Na maioria dos casos, no entanto, o que importa é que após a operação o media player esteja no estado esperado (ou seja, parado).

Se um media player parar automaticamente quando chegar ao fim da mídia, uma função que afirme que o player estava em execução provavelmente seria mais irritante do que útil, mas, em alguns casos, saber se o player estava funcionando no momento em que foi interrompido pode seja útil. Talvez a melhor abordagem para atender a ambas as necessidades seja fazer com que a função retorne um valor indicando o estado anterior do jogador. O código que se preocupa com esse estado pode examinar o valor de retorno; código que não se importa pode simplesmente ignorá-lo.

supercat
fonte
2
+1 na observação sobre o retorno do estado antigo: Isso permite implementá-lo de uma maneira que torne toda a operação (consultar o status e fazer a transição para um status definido) atômica, o que é no mínimo um recurso interessante. Isso tornaria mais fácil escrever código de usuário robusto que não falha esporadicamente devido a alguma condição de corrida estranha.
Cmaster - restabelecer monica
Um bool TryStop()e um void Stop()exemplo de código que fazem deste uma resposta verdadeiramente excelente.
100430 RubberDuck
2
@RubberDuck: Isso não seria um bom padrão. O nome TryStopimplica que uma exceção não deve ser lançada se o jogador não puder ser forçado a um estado parado. Tentando parar o jogador quando ele já está parado não é uma condição excepcional, mas é uma condição um chamador pode ser interessado.
supercat
1
Então eu tenha entendido mal a sua resposta @supercat
RubberDuck
2
Gostaria de salientar que testar e definir dessa maneira NÃO é uma violação da lei do demeter, desde que a API também forneça uma maneira de testar o estado da reprodução sem alterá-lo. Este poderia ser um método completamente diferente, digamos isPlaying().
Candied_orange
11

Não há regra geral. Nesse caso específico, a intenção do usuário da sua API é impedir que o player reproduza a mídia. Se o player não estiver reproduzindo a mídia, MediaPlayer.stop()isso pode não fazer nada e o objetivo do responsável pela chamada do método ainda será alcançado - a mídia não está sendo reproduzida.

Lançar uma exceção exigiria que o usuário da API verifique se o jogador está jogando no momento ou captura e lida com a exceção. Isso tornaria a API mais difícil de usar.

kmaczuga
fonte
11

O objetivo das exceções não é sinalizar que algo ruim aconteceu; é sinalizar isso

  1. Algo ruim aconteceu
  2. que eu não sei como consertar aqui
  3. que o interlocutor, ou qualquer outra coisa na pilha de chamadas, saiba como lidar com
  4. então, estou salvando rapidamente, interrompendo a execução do caminho do código atual para evitar danos ou corrupção de dados e deixando para o chamador a limpeza

Se o chamador tentar Stopum estado MediaPlayerque já está parado, isso não é um problema que MediaPlayernão pode ser resolvido (simplesmente não pode fazer nada.) Não é um problema que, se continuado, causará danos ou dados corrupção, uma vez que pode ser bem-sucedida simplesmente sem fazer nada. E não é realmente um problema que seja mais razoável esperar que o chamador seja capaz de resolver do que o MediaPlayer.

Portanto, você não deve lançar uma exceção nesta situação.

Mason Wheeler
fonte
+1. Esta é a primeira resposta que mostra por que uma exceção não é apropriada aqui. Exceções indicam que é provável que o chamador precise saber sobre o caso excepcional. Nesse caso, quase certamente o cliente da classe em questão não se importará se chamar 'stop ()' realmente causou uma transição de estado; portanto, provavelmente não gostaria de saber sobre uma exceção.
Jules
5

Olhe isto deste modo:

Se o cliente chamar Stop () quando o player não estiver tocando, Stop () será automaticamente bem-sucedido porque o player está no estado parado no momento.

17 de 26
fonte
3

A regra é fazer o que o contrato do seu método exige.

Eu posso ver várias maneiras de definir contratos significativos para esse stopmétodo. Poderia ser perfeitamente válido para o stopmétodo não fazer absolutamente nada se o jogador não estiver jogando. Nesse caso, você define sua API por seus objetivos. Você deseja fazer a transição para o stoppedestado, e o stopmétodo faz isso - apenas fazendo a transição do stoppedestado para si mesmo sem erros.

Existe alguma razão para você querer lançar uma exceção? Se o código do usuário se parecer com isso no final:

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

então não há benefício em ter essa exceção. Portanto, a pergunta é: o usuário se preocupará com o fato de o jogador já estar parado? Ele tem outro wa para consultá-lo?

O jogador pode ser observável, e já pode notificar observadores sobre certas coisas - por exemplo notificantes observadores quando se transitons partir playingpara stoppinga stopped. Nesse caso, não é necessário ter o método para lançar uma exceção. A chamada stopnão fará nada se o jogador já estiver parado, e a chamada quando não estiver parada notificará os observadores sobre a transição.

Em suma, depende de onde sua lógica terminará. Mas acho que existem melhores opções de design do que uma exceção.

Você deve lançar uma exceção se o chamador precisar intervir. Nesse caso, ele não precisa, você pode simplesmente deixar o jogador como está e continua a funcionar.

Polygnome
fonte
3

Existe uma estratégia geral simples que ajuda você a tomar essa decisão.

Considere como a exceção seria tratada se fosse lançada.

Imagine o seu music player, o usuário clica em parar e depois para novamente. Deseja exibir uma mensagem de erro nesse cenário? Ainda não vi um jogador que o faça. Deseja que o comportamento do aplicativo seja diferente de apenas clicar em parar uma vez (como registrar o evento ou enviar um relatório de erro em segundo plano)? Provavelmente não.

Isso significa que a exceção precisaria ser capturada e engolida em algum lugar. E isso significa que é melhor não lançar a exceção em primeiro lugar porque não deseja tomar uma ação diferente .

Isso não se aplica apenas a exceções, mas a qualquer tipo de ramificação: basicamente, se não houver diferença observável no comportamento, não haverá necessidade de ramificação.

Dito isto, não há muito mal em definir seu stop()método para retornar um booleanvalor ou um enum indicando se a parada foi "bem-sucedida". Você provavelmente nunca o usará, mas um valor de retorno pode ser mais fácil e naturalmente ignorado do que uma exceção.

biziclop
fonte
2

Aqui está como o Android faz: MediaPlayer

Em poucas palavras, stopquando startnão foi chamado não é um problema, o sistema permanece no estado parado, mas se chamado por um jogador que nem sabe o que está tocando, uma exceção é lançada, porque não há um bom motivo para chamar pare por aí.

njzk2
fonte
1

Qual deve ser a regra geral quando um método não deve ser chamado em alguma situação, mas sua execução não prejudica o programa em geral?

Vejo o que poderia ser uma pequena contradição em sua declaração que pode tornar a resposta clara para você. Por que o método não deve ser chamado e ainda assim sua execução não faz mal ?

Por que o método "não deveria ser chamado"? Isso parece uma restrição auto-imposta. Se não houver "dano" ou impacto na sua API, não haverá uma exceção. Se o método realmente não deve ser chamado porque pode criar estados inválidos ou imprevisíveis, uma exceção deve ser lançada.

Por exemplo, se eu fosse até um DVD player e pressione "parar" antes de pressionar "iniciar" e ele travar, isso não faria sentido para mim. Deve apenas ficar lá ou, na pior das hipóteses, reconhecer "parar" na tela. Um erro seria irritante e não ajudaria de forma alguma.

No entanto, posso inserir um código de alarme para desativá-lo quando ele já estiver desativado (chamando o método), o que pode fazer com que ele entre em um estado que impeça que o arme adequadamente mais tarde? Em caso afirmativo, gere um erro, embora tecnicamente "não houve danos", pois pode haver problemas mais tarde. Eu gostaria de saber sobre isso, mesmo que não tenha realmente entrado nesse estado. Apenas a possibilidade é suficiente. Isso seria um IllegalStateException.

No seu caso, se sua máquina de estado puder lidar com uma chamada inválida / desnecessária, ignore-a; caso contrário, gere um erro.

EDITAR:

Observe que um compilador não chama um erro se você definir uma variável duas vezes sem inspecionar o valor. Também não impede que você reinicialize uma variável. Existem muitas ações que podem ser consideradas um "bug" de uma perspectiva, mas são meramente "ineficientes", "desnecessárias", "inúteis" etc. etc. Tentei fornecer essa perspectiva na minha resposta - fazer essas coisas geralmente não é considerado um "bug" porque não resulta em nada inesperado. Você provavelmente deve abordar seu problema da mesma forma.

Jim
fonte
Não é para ser chamado porque chamá-lo indica um erro no chamador.
User253751
@immibis: Não necessariamente. Por exemplo, esse chamador pode ser um ouvinte ao clicar de algum botão da interface do usuário. (Como um usuário, você provavelmente não como uma mensagem de erro se acontecer de você clique acidentalmente o botão de parar se a mídia já está parado.)
meriton
@meriton Se fosse o ouvinte ao clicar de um botão da interface do usuário, a resposta seria óbvia - "faça o que quiser que aconteça se o botão for pressionado". Claramente não é, é algo interno ao aplicativo.
User253751
@immibis - editei minha resposta. Espero que ajude.
10246 Jim
1

O que exatamente fazem MediaPlayer.play()e MediaPlayer.stop()fazem? - são ouvintes de eventos para entrada do usuário ou são realmente métodos que iniciam algum tipo de fluxo de mídia no sistema? Se tudo o que são são ouvintes para a entrada do usuário, é perfeitamente razoável que eles não façam nada (embora seja uma boa ideia, pelo menos, registrá-los em algum lugar). No entanto, se eles afetarem o modelo que sua interface do usuário está controlando, eles poderão lançar um IllegalStateExceptionporque o jogador deve ter algum tipo de isPlaying=trueou isPlaying=falseestado (não precisa ser um sinalizador booleano real, como está escrito aqui) e, portanto, quando você chama MediaPlayer.stop()quando isPlaying=false, o O método não pode realmente "parar" o MediaPlayerporque o objeto não está no estado apropriado a ser parado - veja a descrição dojava.lang.IllegalStateException classe:

Sinais de que um método foi chamado em um momento ilegal ou inadequado. Em outras palavras, o ambiente Java ou o aplicativo Java não está em um estado apropriado para a operação solicitada.

Por que lançar exceções pode ser bom

Muitas pessoas parecem pensar que as exceções são ruins em geral, pois nunca devem ser lançadas (cf. Joel on Software ). No entanto, digamos que você tenha uma MediaPlayerGUI.notifyPlayButton()chamada MediaPlayer.play()e MediaPlayer.play()invoque vários outros códigos e em algum ponto abaixo da interface com a qual ele faz interface, por exemplo , o PulseAudio , mas eu (o desenvolvedor) não sei onde, porque não escrevi todo esse código.

Então, um dia, enquanto trabalhava no código, clico em "play" e nada acontece. Se MediaPlayerGUIController.notifyPlayButton()registrar algo, pelo menos eu posso olhar para os registros e verificar se o clique do botão foi realmente registrado ... mas por que não é reproduzido? Bem, digamos que há algo errado com os invólucros PulseAudio que são MediaPlayer.play()invocados. Clico no botão "reproduzir" novamente e desta vez recebo um 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)

Observando esse rastreamento de pilha, posso ignorar tudo antes MediaPlayer.play()durante a depuração e dedicar meu tempo a descobrir por que, por exemplo, nenhuma mensagem está sendo passada ao PulseAudio para iniciar um fluxo de áudio.

Por outro lado, se você não der uma exceção, não terei nada para começar a trabalhar além de: "O programa estúpido não toca música"; Embora não seja tão ruim quanto ocultar erros explícitos , como realmente engolir uma exceção que foi de fato lançada , você ainda está potencialmente desperdiçando o tempo dos outros quando algo der errado ... então seja gentil e jogue uma exceção neles.

errantlinguist
fonte
0

Prefiro projetar a API de uma maneira que torne mais difícil ou impossível para o consumidor cometer erros. Por exemplo, em vez de ter MediaPlayer.play()e MediaPlayer.stop(), você pode fornecer MediaPlayer.playToggle()quais alterna entre "parado" e "tocando". Dessa forma, é sempre seguro chamar o método - não há risco de entrar em um estado ilegal.

Obviamente, isso nem sempre é possível ou fácil de fazer. O exemplo que você deu é comparável a tentar remover um elemento que já foi removido da lista. Você pode ser

  • pragmático sobre isso e não lançar nenhum erro. Isso certamente simplifica muito código, por exemplo, em vez de precisar escrever, if (list.contains(x)) { list.remove(x) }você só precisa list.remove(x)). Mas também pode ocultar bugs.
  • ou você pode ser pedante e sinalizar um erro de que a lista não contém esse elemento. O código pode se tornar mais complicado às vezes, pois você precisa constantemente verificar se as pré-condições são satisfeitas antes de chamar o método, mas a vantagem é que eventuais erros são mais fáceis de rastrear.

Se a chamada MediaPlayer.stop()quando ela já estiver parada não prejudicar seu aplicativo, eu o deixaria executar silenciosamente, porque simplifica o código e eu gosto de métodos idempotentes. Mas se você tiver certeza absoluta de que MediaPlayer.stop()nunca seria chamado nessas condições, lançaria um erro, pois pode haver um bug em outro lugar no código e a exceção ajudaria a localizá-lo.

Pedro Rodrigues
fonte
3
Não concordo em playToggle()ser mais fácil de usar: para alcançar o efeito desejado (jogador tocando ou não tocando), você deveria saber seu estado atual. Portanto, se você receber informações do usuário solicitando que você pare o player, primeiro você precisa saber se o player está tocando no momento e alternar seu estado ou não, dependendo da resposta. Isso é muito mais complicado do que chamar um stop()método e acabar com ele.
Cmaster - restabelecer monica
Bem, eu nunca disse que era mais fácil de usar - minha afirmação era de que é mais seguro. Além disso, seu exemplo pressupõe que o usuário receba botões de parada e reprodução separados. Se esse fosse realmente o caso, então sim, playToggleseria muito complicado de usar. Mas se o usuário também tivesse um botão de alternância, playToggleseria mais fácil usar do que reproduzir / parar.
Pedro Rodrigues