Sto lavorando su alcuni codici incorporati usando C. Varie funzionalità richiedono funzioni di stato non bloccanti, che sono per lo più implementate usando un interruttore su vari stati. Ad esempio, un gestore di connessione modem (pseudo-codice):
void manage_connection(void) {
static states state = IDLE;
switch (state) {
case IDLE:
if (connection_requested()) state = BACKOFF;
break;
case BACKOFF:
if (random_delay_timeout()) {
do_connect();
state = CONNECTED;
}
break;
case CONNECTED:
result = send_data();
if (result == DATA_SENT) {
state = IDLE;
} else if (result == ERROR) {
// Go straight to backoff state and connect ASAP
state = BACKOFF;
}
break;
}
}
Le macchine di stato sono cattive nei momenti migliori, quindi ho iniziato a passare a un paradigma diverso usando le funzioni in stile coroutine, usando una dichiarazione di "rendimento". C, C non fornisce una dichiarazione di "rendimento", ma sto usando protothreads per raggiungere questo obiettivo. Protothreads è fantastico e mi dà esattamente quello che voglio implementare la macchina a stati non bloccanti di cui sopra in uno stile di programmazione strutturata. La funzione sopra riportata si traduce approssimativamente in (pseudo-codice):
void manage_connection(void) {
WAIT_UNTIL(connection_requested()); // or, while(!condition) yield;
WAIT_UNTIL(random_delay_timeout());
do_connect();
WAIT_UNTIL((result = send_data()));
if (result == DATA_SENT) {
// RESTART starts from top. It is part of the protothreads API.
RESTART();
} else if (result == ERROR) {
// This is the part which doesn't translate so well.
// Could use an ugly goto:
goto backoff;
// Or similar alternatives, none of which scale well:
// - Set a flag to skip the states I don't want, and RESTART();
// - Use while loops or other control logic to branch appropriately.
}
}
Come puoi vedere, la versione dei protothread è, nel complesso, molto più chiara e amp; più semplice in termini di flusso di logica (una volta che si impara come funzionano i protothread e si guarda oltre un numero di piastre molto minore che non ho incluso nell'esempio).
Ma saltare a un particolare stato in una macchina a stati è un modello comune che mi viene in mente, che la versione della macchina a stati gestisce in modo elegante, ma la versione procedurale no. Naturalmente, è per questo che la versione procedurale è meno incline ai bug e più chiara - perché non puoi saltare per tutto il luogo per un capriccio (eccetto per goto, che vorrei evitare per le solite ragioni).
Tuttavia, quando hai una buona ragione per voler saltare, aggiunge una significativa mancanza di chiarezza soggetta a bug in quella che altrimenti è una funzione molto semplice.
Ovviamente ci sono molti modi per ottenere le funzionalità necessarie, ma nessuno di essi è in grado di adattarsi a più di pochi stati. Tieni presente che questo è l'esempio più semplice possibile: le mie funzioni del mondo reale riempiono metà schermo.
Quali sono alcuni modi per risolvere questo problema in modo da non confondere il flusso della logica altrimenti lucido?