Cos'è meglio IllegalStateException o l'esecuzione di un metodo silenzioso? [chiuso]


16

Diciamo che ho una classe MediaPlayer che ha metodi play () e stop (). Qual è la migliore strategia da utilizzare quando si implementa il metodo stop nel caso in cui il metodo play non sia mai stato chiamato prima. Vedo due opzioni: lanciare un'eccezione perché il giocatore non è nello stato appropriato o ignorare silenziosamente le chiamate al metodo di arresto.

Quale dovrebbe essere la regola generale quando un metodo non dovrebbe essere chiamato in qualche situazione, ma la sua esecuzione non danneggia il programma in generale?


21
Non usare eccezioni per modellare un comportamento non eccezionale.
Alexander - Ripristina Monica

1
Hey! Non penso sia un duplicato. La mia domanda riguarda maggiormente IllegalStateException e la domanda precedente riguarda l'utilizzo delle eccezioni in generale.
x2bool

1
Invece di SOLO fallire silenziosamente, perché non registrare ANCHE l'anomalia come avvertenza? Sicuramente il Java che stai utilizzando supporta diversi livelli di registro (ERRORE, AVVISO, INFO, DEBUG).
Eric Seastrand,

3
Si noti che Set.add non genera un'eccezione se l'elemento è già nel set. Il suo scopo è "assicurarsi che l'elemento sia nell'insieme", piuttosto che "aggiungere l'elemento all'insieme", quindi raggiunge il suo scopo non facendo nulla se l'elemento è già nell'insieme.
user253751

2
Parola del giorno: idempotenza
Jules,

Risposte:


37

Non c'è una regola. Dipende interamente da come vuoi "sentire" la tua API.

Personalmente, in un lettore musicale, penso una transizione dallo stato Stopped al StoppedmetodoStop() sia una transizione di stato perfettamente valida. Non è molto significativo, ma è valido. Con questo in mente, lanciare un'eccezione sembrerebbe pedante e ingiusto. Farebbe sembrare all'API l'equivalente sociale di parlare con il bambino fastidioso sullo scuolabus. Sei bloccato con la situazione fastidiosa, ma puoi affrontarla.

Un approccio più "socievole" è riconoscere che la transizione è, nella peggiore delle ipotesi, innocua ed essere gentile con gli sviluppatori che consumano consentendola.

Quindi, se dipendesse da me, salterei l'eccezione.


15
Questa risposta ci ricorda che a volte è una buona idea delineare le transizioni di stato per interazioni anche semplici.

6
Con questo in mente, lanciare un'eccezione sembrerebbe pedante e ingiusto. Farebbe sentire l'API come l'equivalente sociale di parlare con il bambino fastidioso sullo scuolabus. > Le eccezioni non sono progettate per punirti: sono progettate per dirti che è successo qualcosa di inaspettato. Personalmente sarei molto grato per un MediaPlayer.stop()metodo che ha lanciato IllegalStateExceptioninvece di passare ore a eseguire il debug del codice e chiedermi perché l'inferno "non succede nulla" (cioè "semplicemente non funziona").
errantlinguist

3
Naturalmente, se il tuo progetto dice che la transizione di loopback non è valida, allora SÌ dovresti generare un'eccezione. Tuttavia, la mia risposta è stata su un progetto in cui quella transizione è perfettamente OK.
MetaFight

1
StopOrThrow()sembra orribile. Se vai in quel vicolo, perché non usare il modello standard di TryStop()? Inoltre, in genere, quando il comportamento di un'API non è chiaro (come la maggior parte di loro), gli sviluppatori non devono semplicemente indovinare o sperimentare, ma devono consultare la documentazione. Ecco perché mi piace così tanto MSDN.
MetaFight,

1
Personalmente preferisco l'opzione fail-fast, quindi preferirei IllegalStateException per indicare all'utente dell'API che c'è qualcosa di sbagliato nella sua logica, anche se è innocuo. Mi capita spesso di ottenere queste eccezioni dal mio codice, davvero utile per scoprire che il mio lavoro non corretto è già sbagliato
Walfrat,

17

Esistono due tipi distinti di azioni che si potrebbero desiderare di eseguire:

  1. Verifica simultaneamente che qualcosa si trova in uno stato e modificalo in un altro.

  2. Imposta qualcosa su uno stato particolare, senza riguardo per lo stato precedente.

Alcuni contesti richiedono un'azione e alcuni richiedono l'altra. Se un lettore multimediale che raggiunge la fine del contenuto rimarrà in uno stato di "riproduzione", ma con la posizione bloccata alla fine, un metodo che asserisce simultaneamente che il lettore si trovava in uno stato di riproduzione impostandolo su uno "stop" Lo stato potrebbe essere utile se il codice volesse garantire che nulla abbia causato il fallimento della riproduzione prima della richiesta di arresto (il che potrebbe essere importante se, ad esempio, qualcosa stesse registrando il contenuto riprodotto come mezzo per convertire i media da un formato all'altro) . Nella maggior parte dei casi, tuttavia, ciò che conta è che dopo l'operazione il lettore multimediale si trovi nello stato previsto (cioè interrotto).

Se un lettore multimediale si arresta automaticamente quando raggiunge la fine del supporto, una funzione che afferma che il lettore era in esecuzione sarebbe probabilmente più fastidiosa che utile, ma in alcuni casi sapere se il lettore era in esecuzione al momento in cui è stato arrestato potrebbe essere utile. Forse l'approccio migliore per soddisfare entrambe le esigenze sarebbe quello di avere la funzione di restituire un valore che indica lo stato precedente del giocatore. Il codice che si prende cura di quello stato potrebbe esaminare il valore di ritorno; il codice a cui non importa potrebbe semplicemente ignorarlo.


2
+1 per la nota sulla restituzione del vecchio stato: ciò consente di implementarlo in modo da rendere atomica l'intera operazione (interrogare lo stato e passare a uno stato definito), che è almeno una bella caratteristica da avere. Semplificherebbe la scrittura di un codice utente affidabile che non si guasta sporadicamente a causa di una strana condizione di competizione.
cmaster - ripristina monica

Un bool TryStop()ed un void Stop()esempio di codice che rendono questo una risposta davvero eccellente.
RubberDuck,

2
@RubberDuck: Non sarebbe un buon modello. Il nome TryStopimplicherebbe che un'eccezione non dovrebbe essere generata se il giocatore non può essere forzato a uno stato di arresto. Cercare di fermare il giocatore quando è già fermo non è una condizione eccezionale, ma è una condizione che potrebbe interessare un chiamante.
Supercat,

1
Poi ho frainteso la tua risposta @supercat
RubberDuck

2
Vorrei sottolineare che testare e impostare in questo modo NON è una violazione della legge di Demeter a condizione che l'API fornisca anche un modo per testare lo stato di riproduzione senza modificarlo. Questo potrebbe essere un metodo completamente diverso, diciamo isPlaying().
candied_orange

11

Non esiste una regola generale. In questo caso specifico, l'intenzione dell'utente dell'API è di impedire al lettore di riprodurre i media. Se il lettore non sta riproducendo il file multimediale, MediaPlayer.stop()potrebbe non fare nulla e l'obiettivo del chiamante del metodo sarà comunque raggiunto - il file multimediale non sta giocando.

Lanciare un'eccezione richiederebbe all'utente dell'API di verificare se il giocatore sta giocando o di catturare e gestire l'eccezione. Ciò renderebbe l'API più un compito ingrato da utilizzare.


11

Lo scopo delle eccezioni non è quello di segnalare che è successo qualcosa di brutto; è per segnalarlo

  1. È successo qualcosa di brutto
  2. che non so come risolvere qui
  3. che il chiamante o qualcos'altro dal callstack da esso dovrebbe sapere come gestire
  4. quindi sto salvando rapidamente, interrompendo l'esecuzione del percorso del codice corrente al fine di prevenire danni o corruzione dei dati e lasciandolo al chiamante per ripulire

Se il chiamante prova a Stopuno MediaPlayerche è già in uno stato di arresto, questo non è un problema che MediaPlayernon può risolvere (non può semplicemente fare nulla.) Non è un problema che, se continuato, porterà a danni o dati corruzione, dal momento che può avere successo semplicemente non facendo nulla. E non è davvero un problema che è più ragionevole aspettarsi che il chiamante sia in grado di risolvere ilMediaPlayer .

Pertanto, non dovresti gettare un'eccezione in questa situazione.


+1. Questa è la prima risposta che mostra perché un'eccezione non è appropriata qui. Le eccezioni indicano che è probabile che il chiamante debba conoscere il caso eccezionale. In questo caso, quasi sicuramente il client della classe in questione non si preoccuperà se la chiamata 'stop ()' abbia effettivamente causato una transizione di stato, quindi probabilmente non vorrebbe sapere un'eccezione.
Jules,

5

Guardate in questo modo:

Se il client chiama Stop () quando il giocatore non sta giocando, allora Stop () ha successo automaticamente perché il giocatore è attualmente nello stato di arresto.


3

La regola è di fare ciò che richiede il contratto del tuo metodo.

Vedo un modo multiplo per definire contratti significativi per tale stopmetodo. Potrebbe essere perfettamente valido per il stopmetodo di non fare assolutamente nulla se il giocatore non sta giocando. In tal caso, definisci l'API in base ai suoi obiettivi. Vuoi passare allo stoppedstato e il stopmetodo lo fa - semplicemente passando dallo stoppedstato a se stesso senza errori.

C'è qualche motivo per cui si vuole per generare un'eccezione? Se il codice utente apparirà così alla fine:

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

quindi non vi è alcun vantaggio ad avere tale eccezione. Quindi la domanda è: l'utente si preoccuperà del fatto che il giocatore sia già stato fermato? Ha un altro wa per interrogarlo?

Il giocatore potrebbe essere osservabile, e potrebbe già comunicare osservatori su certe cose - ad esempio notificanti osservatori quando si transitons da playinga stoppinga stopped. In questo caso, non è necessario che il metodo generi un'eccezione. Chiamare stopnon farà nulla se il giocatore è già fermato e chiamarlo quando non è fermato avviserà gli osservatori della transizione.

tutto sommato, dipende da dove finirà la tua logica. Ma penso che ci siano scelte progettuali migliori rispetto all'eccezione.

È necessario generare un'eccezione se il chiamante deve intervenire. In questo caso non è necessario, puoi semplicemente lasciare che il giocatore sia com'è e continua a funzionare.


3

Esiste una semplice strategia generale che ti aiuta a prendere questa decisione.

Considera come sarebbe gestita l'eccezione se dovesse essere generata.

Quindi immagina il tuo lettore musicale, i clic dell'utente si fermano e poi si fermano di nuovo. Desideri visualizzare un messaggio di errore in quello scenario? Non ho ancora visto un giocatore che lo fa. Vuoi che il comportamento dell'applicazione sia diverso dal solo fare clic su stop una volta (come registrare l'evento o inviare un rapporto di errore in background)? Probabilmente no.

Ciò significa che l'eccezione dovrebbe essere catturata e deglutita da qualche parte. Ciò significa che è meglio non gettare l'eccezione in primo luogo perché non si vuole intraprendere un'azione diversa .

Questo non si applica solo alle eccezioni, ma a qualsiasi tipo di ramificazione: fondamentalmente se non ci deve essere alcuna differenza osservabile nel comportamento, non è necessario ramificare.

Detto questo, non c'è molto danno nel definire il tuo stop()metodo per restituire un booleanvalore o un enum che indica se l'arresto ha avuto successo. Probabilmente non lo userai mai, ma un valore di ritorno può essere ignorato più facilmente e naturalmente di un'eccezione.


2

Ecco come funziona Android: MediaPlayer

In poche parole, stopquando startnon è stato chiamato non è un problema, il sistema rimane nello stato di arresto, ma se chiamato su un giocatore che non sa nemmeno cosa sta giocando, viene generata un'eccezione, perché non c'è una buona ragione per chiamare fermati lì.


1

Quale dovrebbe essere la regola generale quando un metodo non dovrebbe essere chiamato in qualche situazione, ma la sua esecuzione non danneggia il programma in generale?

Vedo quale potrebbe essere una piccola contraddizione nella tua affermazione che potrebbe chiarirti la risposta. Perché il metodo non dovrebbe essere chiamato e tuttavia la sua esecuzione non fa male ?

Perché il metodo "non dovrebbe essere chiamato"? Sembra una limitazione autoimposta. Se non vi sono "danni" o impatti sull'API, non esiste un'eccezione. Se il metodo in realtà non deve essere chiamato perché potrebbe creare stati non validi o imprevedibili, allora dovrebbe essere generata un'eccezione.

Ad esempio, se mi avvicinavo a un lettore DVD e premessi "stop" prima di premere "start" e si bloccasse, non avrebbe senso per me. Dovrebbe semplicemente sedersi lì o, nel caso peggiore, riconoscere "stop" sullo schermo. Un errore sarebbe fastidioso e non utile in alcun modo.

Tuttavia, posso inserire un codice di allarme per disattivarlo "quando è già spento (chiamando il metodo), che potrebbe indurlo a entrare in uno stato che gli impedirebbe di attivarlo correttamente in un secondo momento? In tal caso, quindi genera un errore anche se, tecnicamente, "non è stato fatto alcun danno" perché potrebbe esserci un problema in seguito. Vorrei sapere questo anche se in realtà non è andato in quello stato. Basta la possibilità. Questo sarebbe un IllegalStateException.

Nel tuo caso, se la tua macchina a stati è in grado di gestire la chiamata non valida / non necessaria, ignorala, altrimenti genera un errore.

MODIFICARE:

Si noti che un compilatore non invoca un errore se si imposta una variabile due volte senza ispezionare il valore. Inoltre, non impedisce di reinizializzare una variabile. Ci sono molte azioni che potrebbero essere considerate un "bug" da una prospettiva, ma sono semplicemente "inefficienti", "non necessarie", "inutili", ecc. Ho cercato di fornire quella prospettiva nella mia risposta - fare queste cose non è generalmente considerato un "bug" perché non provoca nulla di inaspettato. Probabilmente dovresti affrontare il problema in modo simile.


Non dovrebbe essere chiamato perché chiamarlo indica un bug nel chiamante.
user253751

@immibis: non necessariamente. Ad esempio, quel chiamante potrebbe essere un ascoltatore al clic di alcuni pulsanti dell'interfaccia utente. (Come utente, probabilmente non ti piacerebbe un messaggio di errore se ti capita di fare accidentalmente clic sul pulsante di arresto se il file multimediale è già stato arrestato.)
meriton

@meriton Se fosse l'ascoltatore al clic di un pulsante dell'interfaccia utente, la risposta sarebbe ovvia: "fai quello che vuoi che accada se il pulsante viene premuto". Chiaramente non lo è, è qualcosa di interno all'applicazione.
user253751

@immibis - Ho modificato la mia risposta. Spero possa essere d'aiuto.
Jim,

1

Cosa fanno esattamente MediaPlayer.play()e MediaPlayer.stop()cosa fanno? Sono listener di eventi per l'input dell'utente o sono in realtà metodi che avviano una sorta di flusso multimediale sul sistema? Se tutto ciò che sono sono ascoltatori per l'input dell'utente, è perfettamente ragionevole che non facciano nulla (anche se sarebbe una buona idea almeno registrarli da qualche parte). Tuttavia, se influenzano il modello controllato dall'interfaccia utente, potrebbero lanciare un IllegalStateExceptionperché il giocatore dovrebbe avere una sorta di stato isPlaying=trueo isPlaying=falsestato (non deve essere un vero flag booleano come scritto qui), e quindi quando chiami MediaPlayer.stop()quando isPlaying=false, il il metodo non può effettivamente "arrestare" il MediaPlayerperché l'oggetto non è nello stato appropriato per essere arrestato - vedere la descrizione dijava.lang.IllegalStateException classe:

Segnala che un metodo è stato invocato in un momento illegale o inappropriato. In altre parole, l'ambiente Java o l'applicazione Java non sono in uno stato appropriato per l'operazione richiesta.

Perché lanciare eccezioni può essere buono

Molte persone sembrano pensare che le eccezioni siano cattive in generale, in quanto non dovrebbero mai essere lanciate (cfr. Joel su Software ). Tuttavia, supponiamo che tu abbia uno MediaPlayerGUI.notifyPlayButton()che chiama MediaPlayer.play()e MediaPlayer.play()invoca un sacco di altro codice e da qualche parte lungo la linea che si interfaccia con ad esempio PulseAudio , ma io (lo sviluppatore) non so dove perché non ho scritto tutto quel codice.

Quindi, un giorno mentre lavoro sul codice, faccio clic su "Riproduci" e non succede nulla. Se MediaPlayerGUIController.notifyPlayButton()registra qualcosa, almeno posso guardare nei registri e verificare che il clic sul pulsante sia stato effettivamente registrato ... ma perché non viene riprodotto? Bene, supponiamo che ci sia qualcosa di sbagliato nei wrapper PulseAudio che MediaPlayer.play()invoca. Faccio di nuovo clic sul pulsante "Riproduci" e questa volta ottengo un 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)

Osservando quella traccia dello stack, posso ignorare tutto prima MediaPlayer.play()durante il debug e dedicare il mio tempo a capire perché, ad esempio, nessun messaggio è stato passato a PulseAudio per avviare un flusso audio.

D'altra parte, se non lanci un'eccezione, non avrò nulla con cui iniziare a lavorare se non: "Lo stupido programma non suona musica"; Sebbene non sia così grave come nascondere un errore esplicito come ingoiare un'eccezione che è stata effettivamente lanciata , stai ancora potenzialmente sprecando il tempo degli altri quando qualcosa va storto ... quindi sii gentile e lancia un'eccezione a loro.


0

Preferisco progettare l'API in un modo che renda più difficile o impossibile per il consumatore commettere errori. Ad esempio, invece di avere MediaPlayer.play()e MediaPlayer.stop(), potresti fornire MediaPlayer.playToggle()quali commuta tra "fermato" e "giocare". In questo modo, il metodo è sempre sicuro da chiamare - non c'è rischio di entrare in uno stato illegale.

Certo, questo non è sempre possibile o facile da fare. L'esempio che hai fornito è paragonabile al tentativo di rimuovere un elemento che era già stato rimosso dall'elenco. Puoi esserlo entrambi

  • pragmatico e non gettare alcun errore. Questo certamente semplifica molto codice, ad esempio, invece di scrivere if (list.contains(x)) { list.remove(x) }è necessario solo list.remove(x)). Ma può anche nascondere bug.
  • oppure puoi essere pedante e segnalare un errore che l'elenco non contiene quell'elemento. Il codice a volte può diventare più complicato, poiché è necessario verificare costantemente se le condizioni preliminari sono soddisfatte prima di chiamare il metodo, ma il lato positivo è che eventuali bug sono più facili da rintracciare.

Se chiamare MediaPlayer.stop()quando è già stato interrotto non ha alcun danno nella tua applicazione, allora lo lascerei funzionare silenziosamente perché semplifica il codice e ho una passione per i metodi idempotenti. Ma se sei assolutamente certo che MediaPlayer.stop()non sarebbe mai stato chiamato in quelle condizioni, allora lancerei un errore perché potrebbe esserci un bug da qualche altra parte nel codice e l'eccezione aiuterebbe a rintracciarlo.


3
Non sono d'accordo playToggle()sull'essere più facile da usare: per ottenere l'effetto desiderato (giocatore che gioca o non gioca), ti verrà richiesto di conoscerne lo stato attuale. Quindi, se ricevi l'input dell'utente che ti richiede di fermare il giocatore, dovresti prima chiedere se il giocatore sta giocando, quindi cambiare il suo stato o meno a seconda della risposta. È molto più ingombrante che semplicemente chiamare un stop()metodo e finirlo.
cmaster - ripristina monica

Beh, non ho mai detto che fosse più facile da usare - la mia affermazione era che era più sicuro. Inoltre, il tuo esempio presuppone che all'utente vengano assegnati pulsanti di arresto e riproduzione separati. Se così fosse, allora sì, playTogglesarebbe molto ingombrante da usare. Ma se l'utente avesse anche un pulsante di attivazione / disattivazione, playTogglesarebbe più facile da usare rispetto a play / stop.
Pedro Rodrigues,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.