Cosa sono gli invarianti, come possono essere usati e l'hai mai usato nel tuo programma?


48

Sto leggendo Coders at Work , e in esso si parla molto di invarianti. Per quanto ho capito, un invariante è una condizione che detiene sia prima che dopo un'espressione. Sono, tra l'altro, utili per dimostrare che il ciclo è corretto, se ricordo correttamente il mio corso di Logica.

La mia descrizione è corretta o ho perso qualcosa? Li hai mai usati nel tuo programma? E se sì, come hanno beneficiato?



@Robert Harvey: Sì, l'ho appena letto. Ma mi sembra che gli invarianti siano utili solo quando stai provando a provare qualcosa. È corretto (nessun gioco di parole previsto)?
gablin,

Questa è la mia comprensione; quando stai cercando di ragionare sul tuo programma, per dimostrarne la correttezza.
Robert Harvey,

3
@ user9094: un'asserzione è una dichiarazione che qualcosa è vero in un determinato momento in fase di esecuzione ed è rappresentata nel codice. Un invariante è un'affermazione (si spera ben fondata) che sarà sempre vera ogni volta che si applica e non è rappresentata nel codice stesso.
David Thornley,

1
Gli invarianti sono davvero utili per dimostrare la correttezza, ma non si limitano a quel caso. Sono utili anche per la programmazione difensiva e durante il debug. Non solo aiutano a dimostrare che il tuo codice è corretto, aiutano a ragionare sul codice e a trovare la posizione dei bug vicino alle origini.
Pensando in modo strano il

Risposte:


41

In OOP, un invariante è un insieme di asserzioni che devono sempre essere vere durante la vita di un oggetto affinché il programma sia valido. Dovrebbe valere dalla fine del costruttore all'inizio del distruttore ogni volta che l'oggetto non sta eseguendo un metodo che cambia il suo stato.

Un esempio di invariante potrebbe essere che esattamente una delle due variabili membro dovrebbe essere nulla. O che se uno ha un determinato valore, allora l'insieme di valori consentiti per l'altro è questo o quello ...

Qualche volta uso una funzione membro dell'oggetto per verificare che l'invariante sia valido. In caso contrario, viene sollevata un'affermazione. E il metodo viene chiamato all'inizio e all'uscita di ogni metodo che cambia l'oggetto (in C ++, questa è solo una riga ...)


11
Il +1 per menzionare gli invarianti non deve necessariamente essere vero nel mezzo di un metodo di esecuzione.
Pensando in modo strano il

1
@Oddthinking Meglio evitarlo quando possibile però. Sarebbe facile entrare in uno stato di rottura invariata e dimenticare di ripristinare tutto correttamente prima di tornare. Le eccezioni potrebbero anche darti problemi.
Alexander

3
@Alexander: per invarianti non banali, è quasi impossibile evitarlo. Se è necessario aggiornare più di una variabile in un metodo, come descritto nella risposta, esiste un punto in cui solo una è stata aggiornata e l'invariante è falso. Ci sono abbastanza vincoli per scrivere un buon codice senza aggiungerne di nuovi.
Pensando in modo strano al

Sì, spesso è inevitabile. Ad esempio, se esiste un gruppo di variabili che logicamente appartengono insieme (ad esempio un array e l'indice di un elemento "selezionato" nell'array), probabilmente vale la pena estrarli in un tipo. Da lì, le mutazioni dell'array o del tipo possono essere espresse come una singola assegnazione di una nuova istanza di quel tipo
Alexander

13

Bene, le cose che vedo in questo thread sono tutte fantastiche, ma ho una definizione di "invariante" che è stata tremendamente utile per me al lavoro.

Un invariante è qualsiasi regola logica che deve essere rispettata durante l'esecuzione del programma che può essere comunicata a un essere umano, ma non al compilatore.

Questa definizione è utile perché suddivide le condizioni in due gruppi: quelli del compilatore di cui ci si può fidare con l'applicazione, e quelli che devono essere documentati, discussi, commentati o altrimenti comunicati ai contributori affinché possano interagire con la base di codice senza introdurre bug .

Inoltre, questa definizione è utile perché ti consente di utilizzare la generalizzazione, "Gli invarianti sono cattivi".

Ad esempio, il cambio in un'auto a cambio manuale è progettato per evitare un invariante. Se avessi voluto, avrei potuto costruire una trasmissione con una leva per ogni marcia. Questa leva può essere in avanti ("innestata") o indietro ("disinnestata"). In un tale sistema, ho creato un "invariante", che potrebbe essere documentato come tale:

"È fondamentale che l'ingranaggio attualmente innestato sia disinnestato prima di innestare un altro ingranaggio. Innestare due marce contemporaneamente provoca stress meccanici che spezzeranno la trasmissione. Disinnestare sempre l'ingranaggio attualmente innestato prima di innestarne un altro."

E così, si potrebbe incolpare trasmissioni interrotte sulla guida sciatta. Le auto moderne, tuttavia, usano un solo bastone che ruota attorno agli ingranaggi. È progettato in modo tale che, su una moderna auto con cambio a levetta, non è possibile innestare due marce contemporaneamente.

In questo modo, potremmo dire che la trasmissione è stata progettata per "rimuovere l'invariante", perché non consente di configurarsi meccanicamente in modo tale da violare la regola logica.

Ogni invariante di questo tipo che rimuovi dal tuo codice è un miglioramento, perché riduce il carico cognitivo di lavorare con esso.


1
Se un invariante è una regola logica che deve essere rispettata durante l'esecuzione del tuo programma e la tua regola logica è che non è possibile impegnare due ingranaggi contemporaneamente, non è invariante che due ingranaggi non possano essere impegnati contemporaneamente tempo? Senza questo invariante, la tua trasmissione potrebbe essere in due marce allo stesso tempo, e quindi spezzarsi. In primo luogo, un singolo shifter non sta effettivamente imponendo tale invariante? Secondo, perché un invariante dovrebbe essere intrinsecamente buono o cattivo?
Dustin Cleveland,

1
Il confronto con le marce dell'auto è molto chiaro per me. Grazie!
Marecky,

"Un invariante è qualsiasi regola logica che deve essere rispettata durante l'esecuzione del programma che può essere comunicata a un essere umano, ma non al compilatore." - Mi piace abbastanza, conciso e facile da ricordare.
ZeroKnight,

@DustinCleveland Penso che in questo esempio, i meccanismi dietro lo stick shift siano il "compilatore" che "applica" le regole, mentre il driver che potrebbe altrimenti causare un incidente è uno dei tanti client che devono consumare e ricordare le informazioni che sono stato "documentato, discusso, commentato o altrimenti comunicato".
ebernard,

Spiegazione geniale! Ora capisco davvero il ragionamento sul perché avere invarianti nel tuo codice sia una cattiva pratica.
Ben C Wang,

3

Un invariante (nel senso comune) indica alcune condizioni che devono essere vere ad un certo punto nel tempo o anche sempre mentre il programma è in esecuzione. ad es. PreCondizioni e PostCondizioni possono essere utilizzate per affermare alcune condizioni che devono essere vere quando viene chiamata una funzione e quando ritorna. Gli invarianti di oggetti possono essere usati per affermare che un oggetto deve avere uno stato valido per tutto il tempo in cui esiste. Questo è il principio secondo il progetto.
Ho usato gli invarianti in modo informale usando i controlli nel codice. Ma più recentemente sto giocando con la libreria dei contratti di codice per .Net che supporta direttamente gli invarianti.


3

Basato sulla seguente citazione di Coders At Work ...

Ma una volta che conosci l'invariante che sta mantenendo, puoi vedere, ah, se manteniamo quell'invariante, avremo tempo di ricerca del registro.

... Immagino che "invariant" = "condizione che si desidera mantenere per garantire l'effetto desiderato".

Sembra che l'invariante abbia due sensi che differiscono in modo sottile:

  1. Qualcosa che rimane lo stesso.
  2. Qualcosa che stai cercando di mantenere lo stesso, al fine di raggiungere l'obiettivo X (come un "tempo di ricerca del registro" sopra).

Quindi 1 è come un'asserzione; 2 è come uno strumento per dimostrare la correttezza, le prestazioni o altre proprietà - penso. Vedi l' articolo di Wikipedia per un esempio di 2 (dimostrando la correttezza della soluzione al puzzle MU).

In realtà un terzo senso di invariante è:

.3. Cosa dovrebbe fare il programma (o modulo o funzione); in altre parole, il suo scopo.

Dallo stesso colloquio con Coders At Work:

Ma ciò che rende gestibile il grande software è avere alcuni invarianti globali o dichiarazioni di grande quadro su ciò che dovrebbe fare e ciò che le cose dovrebbero essere vere.


1

Un invariante è come una regola o un presupposto che può essere utilizzato per dettare la logica del programma.

Ad esempio, supponiamo di disporre di un'applicazione software che tiene traccia degli account utente. Supponiamo inoltre che l'utente possa avere più account, ma per qualsiasi motivo è necessario distinguere tra account principale di un utente e account "alias".

Questo potrebbe essere un record DB o qualcos'altro, ma per ora supponiamo che ogni account utente sia rappresentato da un oggetto di classe.

class userAccount {private char * pUserName; char privato * pParentAccountUserName;

...}

Un invariante potrebbe essere il presupposto che se pParentAccountUserName è NULL o vuoto, questo oggetto è l'account principale. Puoi utilizzare questo invariante per distinguere diversi tipi di account. Esistono probabilmente metodi migliori per distinguere diversi tipi di account utente, quindi tieni presente che questo è solo un esempio per mostrare come potrebbe essere usato un invariante.


Gli invarianti controllano lo stato di un programma. Non sono decisioni di progettazione.
Xavier Nodet,

3
Gli invarianti non controllano nulla. Puoi controllare lo stato del programma per vedere se un invariante è VERO o FALSO, ma gli invarianti stessi "non fanno" nulla.
Pemdas,

2
In genere, in C ++ vedresti una sorta di invarianza di classe come il membro x deve essere inferiore a 25 e maggiore di 0. Questo è l'invariante. Qualsiasi controllo a fronte di tale invariante sono affermazioni. Nell'esempio che ho sopra, il mio invariante è se pParentAccountUserName è NULL o vuoto, quindi è un account principale. Gli invarianti sono decisioni progettate.
Pemdas,

Come si controlla che se pParentAccountUserName è NULL o vuoto, questo oggetto è l'account principale? La tua dichiarazione definisce solo ciò che dovrebbe rappresentare un valore nullo / vuoto. L'invariante è che il sistema è conforme a quello, cioè che pParentAccountUserName può essere nullo o vuoto solo se si tratta di un account principale. È una distinzione sottile.
Cameron,

1

Provenienti da un background fisico, in fisica abbiamo degli invarianti, che sono essenzialmente quantità che non variano durante un intero calcolo / simulazione. Ad esempio, in fisica, per un sistema chiuso l'energia totale è conservata. O ancora in fisica, se due particelle si scontrano, i frammenti risultanti devono contenere esattamente l'energia con cui hanno iniziato e esattamente lo stesso momento (una quantità vettoriale). Di solito non ci sono abbastanza invarianti per specificare totalmente il risultato. Ad esempio nella collisione a 2 parti, abbiamo quattro invarianti, tre componenti di quantità di moto e una componente di energia, ma il sistema ha sei gradi di libertà (sei numeri per descriverne lo stato). Gli invarianti dovrebbero essere conservati nell'errore di arrotondamento, ma la loro conservazione non dimostra che la soluzione sia corretta.

Quindi in genere queste cose sono importanti come controlli di integrità, ma da sole non possono dimostrare la correttezza.


1
-1 Gli invarianti in fisica sono diversi. Calcolare una soluzione non equivale a provare che un algoritmo è corretto. Per quest'ultimo, gli invarianti possono dimostrare la correttezza.
aaronasterling,
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.