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?
exceptions
methods
state
x2bool
fonte
fonte
Respostas:
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
Stopped
paraStopped
por meio do métodoStop()
é 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.
fonte
MediaPlayer.stop()
método que lançou umIllegalStateException
código em vez de passar horas depurando e me perguntando por que o inferno "nada acontece" (ou seja, "simplesmente não funciona").StopOrThrow()
parece horrível. Se você está indo para o beco, por que não usar o padrão deTryStop()
? 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.Existem dois tipos distintos de ações que você pode querer executar:
Teste simultaneamente se algo está em um estado e mude para outro.
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.
fonte
bool TryStop()
e umvoid Stop()
exemplo de código que fazem deste uma resposta verdadeiramente excelente.TryStop
implica 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.isPlaying()
.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.
fonte
O objetivo das exceções não é sinalizar que algo ruim aconteceu; é sinalizar isso
Se o chamador tentar
Stop
um estadoMediaPlayer
que já está parado, isso não é um problema queMediaPlayer
nã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 oMediaPlayer
.Portanto, você não deve lançar uma exceção nesta situação.
fonte
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.
fonte
A regra é fazer o que o contrato do seu método exige.
Eu posso ver várias maneiras de definir contratos significativos para esse
stop
método. Poderia ser perfeitamente válido para ostop
mé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 ostopped
estado, e ostop
método faz isso - apenas fazendo a transição dostopped
estado 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:
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
playing
parastopping
astopped
. Nesse caso, não é necessário ter o método para lançar uma exceção. A chamadastop
nã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.
fonte
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 umboolean
valor 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.fonte
Aqui está como o Android faz: MediaPlayer
Em poucas palavras,
stop
quandostart
nã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í.fonte
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.
fonte
O que exatamente fazem
MediaPlayer.play()
eMediaPlayer.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 umIllegalStateException
porque o jogador deve ter algum tipo deisPlaying=true
ouisPlaying=false
estado (não precisa ser um sinalizador booleano real, como está escrito aqui) e, portanto, quando você chamaMediaPlayer.stop()
quandoisPlaying=false
, o O método não pode realmente "parar" oMediaPlayer
porque o objeto não está no estado apropriado a ser parado - veja a descrição dojava.lang.IllegalStateException
classe: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()
chamadaMediaPlayer.play()
eMediaPlayer.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ãoMediaPlayer.play()
invocados. Clico no botão "reproduzir" novamente e desta vez recebo umIllegalStateException
: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.
fonte
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()
eMediaPlayer.stop()
, você pode fornecerMediaPlayer.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
if (list.contains(x)) { list.remove(x) }
você só precisalist.remove(x)
). Mas também pode ocultar bugs.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 queMediaPlayer.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.fonte
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 umstop()
método e acabar com ele.playToggle
seria muito complicado de usar. Mas se o usuário também tivesse um botão de alternância,playToggle
seria mais fácil usar do que reproduzir / parar.