Come recuperare da guasti a macchine a stati finiti?

13

La mia domanda potrebbe sembrare molto scientifica, ma penso che sia un problema comune e gli sviluppatori e i programmatori esperti spero abbiano qualche consiglio per evitare il problema che cito nel titolo. Btw., Quello che descrivo qui sotto è un vero problema che sto cercando di risolvere proattivamente nel mio progetto iOS, voglio evitarlo a tutti i costi.

Per macchina a stati finiti intendo questo > Ho un'interfaccia utente con alcuni pulsanti, diversi stati di sessione rilevanti per quell'interfaccia utente e ciò che rappresenta questa UI, ho alcuni dati che i valori sono parzialmente visualizzati nell'interfaccia utente, ricevo e gestisco alcuni trigger esterni (rappresentati da callback dai sensori). Ho creato diagrammi di stato per mappare meglio gli scenari rilevanti che sono desiderabili e ineludibili in quella UI e applicazione. Mentre implemento lentamente il codice, l'app inizia a comportarsi sempre più come dovrebbe. Tuttavia, non sono molto fiducioso che sia abbastanza robusto. I miei dubbi derivano dall'osservare il mio modo di pensare e di implementare il processo. Ero sicuro che avessi tutto coperto, ma era sufficiente fare alcuni test brutali nell'interfaccia utente e ho subito capito che ci sono ancora lacune nel comportamento .. li ho patchati. Tuttavia, dal momento che ogni componente dipende e si comporta in base all'input di qualche altro componente, un determinato input da parte dell'utente o di una fonte esterna attiva una catena di eventi, cambiamenti di stato ... ecc. Ho diversi componenti e ognuno si comporta in questo modo Trigger ricevuto su input - > trigger e il suo mittente analizzati - > emettere qualcosa (un messaggio, un cambiamento di stato) basato sull'analisi

Il problema è che questo non è completamente autonomo e che i miei componenti (un elemento del database, uno stato di sessione, lo stato di un pulsante) ... potrebbero essere modificati, influenzati, cancellati o modificati in altro modo, al di fuori dell'ambito dell'evento -chain o scenario desiderabile. (il telefono si blocca, la batteria si spegne improvvisamente) Ciò introdurrà una situazione non valida nel sistema, da cui il sistema potenzialmente POTREBBE NON ESSERE IN GRADO di recuperare. Vedo questo (anche se la gente non si rende conto di questo è il problema) in molte delle mie app concorrenti che sono su Apple Store, i clienti scrivono cose come questa > "Ho aggiunto tre documenti e, dopo esserci andati li e là, non posso aprirli, anche se li vedo." oppure "Ho registrato video tutti i giorni, ma dopo aver registrato un video troppo log, non riesco a capovolgerli .., e il pulsante per i sottotitoli non funziona" ..

Questi sono solo esempi abbreviati, i clienti spesso lo descrivono in modo più dettagliato ... dalle descrizioni e dal comportamento descritto in essi, presumo che l'app in questione abbia una scomposizione FSM.

Quindi la domanda finale è: come posso evitare questo e come proteggere il sistema dal blocco stesso?

EDIT > Sto parlando nel contesto di una vista di viewcontroller sul telefono, intendo una parte dell'applicazione. Comprendo il pattern MVC, ho moduli separati per una funzionalità distinta. Tutto ciò che descrivo è pertinente per una tela sull'interfaccia utente.

    
posta Earl Grey 05.04.2012 - 13:53
fonte

6 risposte

7

Sono sicuro che lo sai già, ma nel caso in cui:

  1. Assicurati che ogni nodo nel diagramma di stato ha un arco in uscita per OGNI tipo di input legale (o dividere gli input in classi, con un arco in uscita per ogni classe di input).

    Ogni esempio che ho visto di una macchina a stati utilizza solo una uscita arco per QUALSIASI ingresso errato.

    Se non c'è una risposta definitiva su cosa l'input farà ogni volta, è l'uno o l'altro una condizione di errore, o ce n'è un'altra input che manca (che dovrebbe risultato coerentemente un arco per gli input in corso un nuovo nodo).

    Se un nodo non ha un arco per uno tipo di input, quindi questa è un'ipotesi l'input non si verificherà mai nella vita reale (questa è una potenziale condizione di errore che non sarebbe gestito dalla macchina di stato).

  2. Assicurati che la macchina a stati possa solo prendere o seguire solo un arco in risposta all'input ricevuto (non più di un arco).

    Se ci sono diversi tipi di scenari di errore, o tipi di input che non possono essere identificati al momento della progettazione della macchina di stato, l'errore scenari e input sconosciuti dovrebbero essere a uno stato con un completamente percorso separato separato dagli "archi normali".

    vale a dire. se si riceve un errore o una sconosciuta in qualsiasi stato "conosciuto", quindi gli archi seguiti come risultato della gestione degli errori / input sconosciuto non dovrebbe tornare in nessuno stato la macchina sarebbe entrata se solo avesse ricevuto input conosciuti.

  3. Una volta raggiunto un terminale (fine) ti dichiari non dovrebbe essere in grado di tornare a un non-terminale solo allo stato iniziale (iniziale).

  4. Per una macchina a stati non dovrebbe esserci più di un inizio o stato iniziale (basato sugli esempi che ho visto).

  5. In base a ciò che ho visto, una macchina a stati può rappresenta solo lo stato di un problema o di uno scenario.
    Non dovrebbero mai esserci più stati possibili a una volta in un diagramma di stato.
    Se vedo il potenziale per più concorrenti afferma questo mi dice che ho bisogno di dividere il diagramma di stato fino a 2 o più macchine a stati separati che hanno il potenziale che ogni stato sia modificato in modo indipendente.

risposta data 05.04.2012 - 20:27
fonte
9

Il punto di una macchina a stati finiti è che ha regole esplicite per tutto ciò che può accadere in uno stato. Ecco perché è finito .

Ad esempio:

if a:
  print a
elif b:
  print b

È non finito, perché potremmo ottenere input c . Questo:

if a:
  print a
elif b:
  print b
else:
  print error

è finito, perché tutti gli input possibili sono considerati. Questo spiega i possibili input in uno stato , che può essere separato dal controllo degli errori. Immagina una macchina a stati con i seguenti stati:

No money state. 
Not enough money state.
Pick soda state.

Negli stati definiti, tutti gli input possibili vengono gestiti per il denaro inserito e la soda selezionata. L'interruzione di corrente è all'esterno della macchina a stati e "non esiste". Una macchina a stati può gestire solo gli input per gli stati che ha, quindi hai due scelte.

  1. Garantire che ogni azione sia atomica. La macchina può avere una perdita di potenza totale e lasciare tutto in uno stato stabile e corretto.
  2. Estendi i tuoi stati per includere problemi sconosciuti e fai in modo che gli errori ti portino a questo stato, in cui vengono gestiti i problemi.

Per riferimento, l' articolo wiki sulle macchine di stato è completo. Suggerisco anche codice completo , per i capitoli sulla creazione di software stabile e affidabile.

    
risposta data 05.04.2012 - 16:34
fonte
5

Le espressioni regolari sono implementate come macchine a stati finiti. La tabella di transizione generata dal codice della libreria avrà uno stato di errore integrato, per gestire ciò che accade se l'input non corrisponde al modello. C'è almeno una transizione implicita allo stato di errore da quasi tutti gli altri stati.

Le grammatiche linguistiche di programmazione non sono FSM, ma i generatori di parser (come Yacc o bison) generalmente hanno un modo per inserire uno o più stati di errore, in modo che input inattesi possano causare il codice generato finire in uno stato di errore.

Sembra che il tuo FSM abbia bisogno di uno stato di errore o di errore o di un equivalente morale, insieme a transizioni esplicite (per i casi che anticipi) e implicite (per casi che non prevedi) a uno degli stati di errore o errore .

    
risposta data 05.04.2012 - 14:42
fonte
3

Il modo migliore per evitare ciò è Test automatico .

L'unico modo per avere una vera sicurezza su ciò che fa il tuo codice sulla base di determinati input è testarli. Puoi fare clic in giro nella tua applicazione e provare a fare le cose in modo errato, ma questo non si adatta bene per assicurarti di non avere regressioni. Invece, puoi creare un test che trasmetta input non validi in un componente del tuo codice e assicura che lo gestisca in modo sano.

Questo non sarà in grado di dimostrare che la macchina a stati non può mai essere rotta, ma ti dirà che molti dei casi più comuni sono gestiti correttamente e non rompono altre cose.

    
risposta data 05.04.2012 - 14:06
fonte
2

quello che stai cercando è la gestione delle eccezioni. Una filosofia di progettazione per evitare di rimanere in uno stato inconsistente è documentata come the way of the samurai : restituire vittorioso o non tornare. In altre parole: un componente dovrebbe controllare tutti i suoi input e assicurarsi che sia in grado di elaborarli normalmente. Non è il caso, dovrebbe sollevare un'eccezione contenente informazioni utili.

Una volta sollevata un'eccezione, bolle in cima allo stack. Dovresti definire un livello di gestione degli errori che saprà cosa fare. Se un file utente è danneggiato, spiega al tuo cliente che i dati vengono persi e ricrea un file vuoto pulito.

La parte importante qui è tornare a uno stato operativo ed evitare la propagazione degli errori. Quando hai finito, puoi lavorare sui singoli componenti per renderli più robusti.

Non sono un esperto di obiettivi, ma questa pagina dovrebbe essere un buon punto di partenza:

link

    
risposta data 05.04.2012 - 17:05
fonte
1

Dimentica la tua macchina a stati finiti. Quello che hai qui è una situazione multi-threading seria. Qualsiasi pulsante può essere premuto in qualsiasi momento e i trigger esterni potrebbero spegnersi in qualsiasi momento. I pulsanti sono probabilmente tutti su un thread, ma un trigger può sparire letteralmente nello stesso istante di uno dei pulsanti o di uno, molti o tutti gli altri trigger.

Quello che devi fare è determinare il tuo stato nel momento in cui decidi di agire. Ottieni tutti i pulsanti e gli stati di attivazione. Salvali in variabili locali . I valori originali possono cambiare ogni volta che li guardi. Quindi agisci in base alla situazione come hai fatto tu. È un'istantanea di come il sistema ha osservato un punto. Un millesimo di secondo dopo potrebbe sembrare molto diverso, ma con il multithreading non esiste una "corrente" attuale a cui aggrapparsi, solo un'immagine che è stata salvata in variabili locali.

Quindi devi rispondere allo stato storico salvato. Tutto è fisso e dovresti avere un'azione per tutti gli stati possibili. Non terrà conto delle modifiche apportate tra il momento in cui hai scattato l'istantanea e il tempo di visualizzazione dei risultati, ma questa è la vita nel mondo multi-threading. E potrebbe essere necessario utilizzare la sincronizzazione per mantenere l'istantanea troppo sfocata. (Non puoi essere aggiornato, ma puoi chiudere per ottenere l'intero stato da un particolare istante di tempo.)

Leggi su multi-threading. Hai molto da imparare. E a causa di questi trigger, non penso che tu possa usare molti trucchi spesso forniti per semplificare l'elaborazione parallela ("Thread di lavoro" e simili). Non stai facendo "elaborazione parallela"; non stai cercando di usare il 75% di 8 core. Stai usando l'1% dell'intera CPU, ma hai thread altamente indipendenti e altamente interattivi, e ci vorrà un sacco di pensiero per sincronizzarli e mantenere la sincronizzazione dal blocco del sistema.

Test su macchine single core e multi-core; Ho scoperto che si comportano in modo piuttosto diverso con il multi-threading. Le macchine single-core presentano meno bug multi-threading, ma questi bug sono molto più strani. (Anche se le macchine multi-core ti faranno impazzire finché non ti abituerai a loro.)

Un ultimo pensiero spiacevole: non è roba facile da provare. Avrai bisogno di generare trigger casuali e premere un pulsante e lasciare che il sistema funzioni per un po 'per vedere cosa succede. Il codice multi-thread è non deterministico. Qualcosa può fallire una volta su un miliardo di corse, solo perché il tempo era scaduto per un nanosecondo. Inserire le dichiarazioni di debug (con attenti if-statement per evitare 999.999.999 messaggi non necessari) ed è necessario effettuare un miliardo di run solo per ottenere un messaggio utile. Fortunatamente, le macchine sono veramente veloci in questi giorni.

Mi dispiace di aver scaricato tutto questo su di te all'inizio della tua carriera. Spero che a qualcuno venga in mente un'altra risposta con un modo per aggirare tutto questo (penso che ci siano cose là fuori che potrebbero domare i trigger, ma hai ancora il conflitto trigger / pulsante). Se è così, questa risposta ti farà almeno sapere cosa ti stai perdendo. Buona fortuna.

    
risposta data 05.04.2012 - 16:01
fonte

Leggi altre domande sui tag