Come viene introdotta una variabile?


11

Stavo leggendo gli "Standard di codifica C ++" e questa riga era lì:

Le variabili introducono lo stato e dovresti avere a che fare con il minor stato possibile, con tempi di vita il più brevi possibili.

Qualcosa che muta alla fine non modifica lo stato? Che cosa dovresti avere a che fare con il minor stato possibile ?

In un linguaggio impuro come C ++, la gestione dello stato non è davvero ciò che stai facendo? E quali altri modi per gestire il minor stato possibile oltre a limitare la durata variabile?

Risposte:


16

Qualche cosa mutevole non manipola davvero lo stato?

Sì.

E cosa significa "dovresti avere a che fare con un piccolo stato"?

Significa che meno stato è meglio di più stato. Più stato tende a introdurre più complessità.

In un linguaggio impuro come il C ++, la gestione dello stato non è davvero ciò che stai facendo?

Sì.

Quali sono altri modi per "gestire il piccolo stato" oltre a limitare la durata variabile?

Ridurre al minimo il numero di variabili. Isolare il codice che modifica uno stato in un'unità autonoma in modo che altre sezioni di codice possano ignorarlo.


9

Qualche cosa mutevole non manipola davvero lo stato?

Sì. In C ++, le uniche cose mutabili sono (non const) variabili.

E cosa significa "dovresti avere a che fare con un piccolo stato"?

Meno stato ha un programma, più facile è capire cosa fa. Quindi non dovresti introdurre lo stato che non è necessario e non dovresti tenerlo quando non ne hai più bisogno.

In un linguaggio impuro come il C ++, la gestione dello stato non è davvero ciò che stai facendo?

In un linguaggio multi-paradigma come il C ++, spesso c'è una scelta tra un approccio "puro" funzionale o guidato dallo stato, o una sorta di ibrido. Storicamente, il supporto linguistico per la programmazione funzionale è stato piuttosto debole rispetto ad alcune lingue, ma sta migliorando.

Quali sono altri modi per "gestire il piccolo stato" oltre a limitare la durata variabile?

Limitare l'ambito e la durata, per ridurre l'accoppiamento tra oggetti; favorire variabili locali anziché globali e membri di oggetti privati ​​piuttosto che pubblici.


5

stato significa che qualcosa viene archiviato da qualche parte in modo da poterlo fare riferimento in un secondo momento.

La creazione di una variabile crea uno spazio per l'archiviazione di alcuni dati. Questi dati sono lo stato del tuo programma.

Lo usi per fare le cose, modificarlo, calcolarlo, ecc.

Questo è stato , mentre le cose che fai non lo sono.

In un linguaggio funzionale, ti occupi principalmente delle funzioni e del passaggio di funzioni come se fossero oggetti. Sebbene queste funzioni non abbiano uno stato, e passando la funzione in giro, non introduce uno stato (oltre forse all'interno della funzione stessa).

In C ++ è possibile creare oggetti funzione , che sonostruct o classtipi operator()()sovraccaricati. Questi oggetti funzione possono avere uno stato locale, anche se questo non è necessariamente condiviso con altri codici nel programma. I portatori (cioè gli oggetti funzione) sono molto facili da spostare. Questo è il più vicino possibile a imitare un paradigma funzionale in C ++. (PER QUANTO NE SO)

Avere uno stato scarso o nullo significa che puoi facilmente ottimizzare il tuo programma per l'esecuzione parallela, perché non c'è nulla che possa essere condiviso tra thread o CPU, quindi nulla su cui creare contese e niente da proteggere da gare di dati, ecc.


2

Altri hanno fornito buone risposte alle prime 3 domande.

E quali altri modi per "gestire il minor stato possibile" oltre a limitare la durata variabile?

La risposta chiave alla domanda n. 1 è sì, tutto ciò che muta alla fine ha un impatto sullo stato. La chiave quindi è non mutare le cose. Tipi immutabili, che utilizzano uno stile di programmazione funzionale in cui il risultato di una funzione viene passato direttamente all'altra e non memorizzato, passando messaggi o eventi direttamente anziché memorizzare lo stato, calcolando i valori anziché memorizzarli e aggiornarli ...

Altrimenti ti resta di limitare l'impatto dello stato; tramite visibilità o durata.


1

E cosa significa "dovresti avere a che fare con un piccolo stato"?

Ciò significa che le tue classi dovrebbero essere le più piccole possibili, rappresentando in modo ottimale una singola astrazione. Se metti 10 variabili nella tua classe, molto probabilmente stai facendo qualcosa di sbagliato e dovresti vedere come riformattare la tua classe.


1

Per capire come funziona un programma, è necessario comprendere i suoi cambiamenti di stato. Meno stato hai e più locale è al codice che lo utilizza, più facile sarà.

Se hai mai lavorato con un programma che aveva un gran numero di variabili globali, lo capiresti implicitamente.


1

Lo stato è semplicemente dati memorizzati. Ogni variabile è in realtà una sorta di stato, ma di solito usiamo "stato" per fare riferimento a dati persistenti tra le operazioni. Come esempio semplice e inutile, potresti avere una classe che memorizza internamente un inte ha increment()edecrement() funzioni membro. Qui, il valore interno è stato perché persiste per la vita dell'istanza di questa classe. In altre parole, il valore è lo stato dell'oggetto.

Idealmente, lo stato definito da una classe dovrebbe essere il più piccolo possibile con ridondanza minima. Questo aiuta la tua classe a rispettare il principio della responsabilità singola , migliora l'incapsulamento e riduce la complessità. Lo stato di un oggetto deve essere interamente incapsulato dall'interfaccia verso quell'oggetto. Ciò significa che il risultato di qualsiasi operazione su quell'oggetto sarà prevedibile data la semantica dell'oggetto. È possibile migliorare ulteriormente l'incapsulamento riducendo al minimo il numero di funzioni che hanno accesso allo stato .

Questo è uno dei motivi principali per evitare lo stato globale. Lo stato globale può introdurre una dipendenza per un oggetto senza che l'interfaccia lo esprima, rendendo questo stato nascosto all'utente. Il richiamo di un'operazione su un oggetto con una dipendenza globale può avere risultati variabili e imprevedibili.


1

Qualcosa che muta alla fine non manipola lo stato?

Sì, ma se è dietro una funzione membro di una piccola classe che è l'unica entità nell'intero sistema in grado di manipolare il suo stato privato, tale stato ha un ambito molto ristretto.

Che cosa dovresti avere a che fare con il minor stato possibile?

Dal punto di vista della variabile: il minor numero di righe di codice dovrebbe essere in grado di accedervi il più possibile. Restringere al minimo l'ambito della variabile.

Dal punto di vista della linea di codice: il minor numero possibile di variabili dovrebbe essere accessibile da quella linea di codice. Restringere il numero di variabili a cui la riga di codice può eventualmente accedere (non importa nemmeno molto se vi accede, tutto ciò che conta è se può ).

Le variabili globali sono così dannose perché hanno il massimo scopo. Anche se sono accessibili da 2 righe di codice in una base di codice, dalla riga del POV del codice, una variabile globale è sempre accessibile. Dal POV della variabile, una variabile globale con collegamento esterno è accessibile a ogni singola riga di codice nell'intera base di codice (o a ogni singola riga di codice che include comunque l'intestazione). Nonostante sia effettivamente accessibile solo da 2 righe di codice, se la variabile globale è visibile a 400.000 righe di codice, il tuo elenco immediato di sospetti quando scopri che è stato impostato su uno stato non valido avrà quindi 400.000 voci (forse rapidamente ridotto a 2 voci con strumenti, ma l'elenco immediato avrà 400.000 sospetti e questo non è un punto di partenza incoraggiante).

Allo stesso modo, anche se una variabile globale inizia a essere modificata solo da 2 righe di codice nell'intera base di codice, la sfortunata tendenza delle basi di codice a evolversi all'indietro tenderà ad aumentare drasticamente quel numero, semplicemente perché può aumentare il numero di gli sviluppatori, frenetici nel rispettare le scadenze, vedono questa variabile globale e si rendono conto che possono prendere scorciatoie attraverso di essa.

In un linguaggio impuro come C ++, la gestione dello stato non è davvero ciò che stai facendo?

In gran parte sì, a meno che tu non stia usando C ++ in un modo molto esotico che ti ha a che fare con strutture di dati immutabili su misura e pura programmazione funzionale in tutto - è anche spesso la fonte della maggior parte dei bug quando la gestione dello stato diventa complessa e la complessità è spesso in funzione della visibilità / esposizione di quello stato.

E quali altri modi per gestire il minor stato possibile oltre a limitare la durata variabile?

Tutti questi sono nel regno della limitazione dell'ambito di una variabile, ma ci sono molti modi per farlo:

  • Evita le variabili globali grezze come la peste. Perfino qualche stupida funzione setter / getter globale restringe drasticamente la visibilità di quella variabile, e almeno consente un modo per mantenere invarianti (es: se alla variabile globale non dovrebbe mai essere permesso di avere un valore negativo, il setter può mantenere quell'invariante). Naturalmente, anche un design setter / getter in aggiunta a quella che altrimenti sarebbe una variabile globale è un design piuttosto scadente, il mio punto è che è ancora molto meglio.
  • Rendi le tue lezioni più piccole quando possibile. Una classe con centinaia di funzioni membro, 20 variabili membro e 30.000 righe di codice che la implementano avrebbe variabili private piuttosto "globali", poiché tutte quelle variabili sarebbero accessibili alle sue funzioni membro che consistono in 30k righe di codice. Si potrebbe dire che la "complessità dello stato" in quel caso, pur scontando le variabili locali in ciascuna funzione membro, lo è 30,000*20=600,000. Se ci fossero 10 variabili globali accessibili oltre a ciò, la complessità dello stato potrebbe essere simile 30,000*(20+10)=900,000. Una sana "complessità di stato" (il mio tipo personale di metrica inventata) dovrebbe essere nelle migliaia o sotto per le classi, non decine di migliaia, e sicuramente non centinaia di migliaia. Per le funzioni gratuite, diciamo centinaia o meno prima di iniziare a ottenere seri mal di testa nella manutenzione.
  • Allo stesso modo di cui sopra, non implementare qualcosa come una funzione membro o funzione amico che può essere altrimenti non membro, non amico usando solo l'interfaccia pubblica della classe. Tali funzioni non possono accedere alle variabili private della classe e quindi ridurre il potenziale di errore riducendo l'ambito di tali variabili private.
  • Evita di dichiarare le variabili molto prima che siano effettivamente necessarie in una funzione (ad esempio, evita lo stile C legacy che dichiara tutte le variabili all'inizio di una funzione anche se sono necessarie solo molte righe di seguito). Se usi questo stile comunque, cerca almeno funzioni più brevi.

Oltre le variabili: effetti collaterali

Molte di queste linee guida che ho elencato sopra stanno affrontando l'accesso diretto allo stato grezzo (variabili). Tuttavia, in una base di codice sufficientemente complessa, restringere l'ambito delle variabili non sarà sufficiente per ragionare facilmente sulla correttezza.

Si potrebbe avere, per esempio, una struttura di dati centrale, dietro un'interfaccia totalmente SOLIDA, astratta, pienamente in grado di mantenere perfettamente gli invarianti e finire ancora per affrontare un sacco di dolore a causa della vasta esposizione di questo stato centrale. Un esempio di stato centrale che non è necessariamente accessibile a livello globale ma semplicemente accessibile è il grafico della scena centrale di un motore di gioco o la struttura dei dati del livello centrale di Photoshop.

In tali casi, l'idea di "stato" va oltre le variabili grezze, e solo alle strutture di dati e cose del genere. Allo stesso modo aiuta a ridurne l'ambito (ridurre il numero di linee che possono chiamare funzioni che le mutano indirettamente).

inserisci qui la descrizione dell'immagine

Notate come ho deliberatamente contrassegnato anche l'interfaccia come rossa qui, poiché dal livello architettonico ampio e ingrandito, l'accesso a quell'interfaccia è ancora stato mutante, anche se indirettamente. La classe può mantenere invarianti come risultato dell'interfaccia, ma ciò va solo così lontano in termini della nostra capacità di ragionare sulla correttezza.

In questo caso, la struttura centrale dei dati si trova dietro un'interfaccia astratta che potrebbe non essere nemmeno accessibile a livello globale. Potrebbe semplicemente essere iniettato e quindi indirettamente mutato (attraverso le funzioni membro) da un carico di funzioni nella tua complessa base di codice.

In tal caso, anche se la struttura dei dati mantiene perfettamente i propri invarianti, possono accadere cose strane a un livello più ampio (es: un lettore audio può mantenere tutti i tipi di invarianti come il livello del volume che non va mai al di fuori dell'intervallo dello 0% per 100%, ma ciò non lo protegge dall'utente che preme il pulsante di riproduzione e che ha una clip audio casuale diversa da quella che ha caricato più di recente e inizia a riprodurre quando viene attivato un evento che provoca il rimpasto della playlist in modo valido ma comportamento indesiderato, ancora glitch dal punto di vista dell'utente).

Il modo per proteggersi in questi scenari complessi è quello di "strozzare" i posti nella base di codice che possono chiamare funzioni che alla fine causano effetti collaterali esterni anche da questo tipo di visione più ampia del sistema che va oltre lo stato grezzo e oltre le interfacce.

inserisci qui la descrizione dell'immagine

Per quanto strano possa sembrare, puoi vedere che nessuno "stato" (mostrato in rosso, e questo non significa "variabile grezza", significa solo un "oggetto" e forse anche dietro un'interfaccia astratta) è accessibile da numerosi luoghi . Ciascuna delle funzioni ha accesso a uno stato locale che è anche accessibile da un programma di aggiornamento centrale e lo stato centrale è accessibile solo al programma di aggiornamento centrale (rendendolo non più centrale ma piuttosto di natura locale).

Questo è solo per basi di codice davvero complesse, come un gioco che si estende su 10 milioni di righe di codice, ma può aiutare enormemente nel ragionamento sulla correttezza del tuo software e scoprire che le tue modifiche producono risultati prevedibili, quando limiti / colli di bottiglia significativamente il numero di luoghi che possono mutare stati critici su cui l'intera architettura ruota per funzionare correttamente.

Oltre alle variabili non elaborate vi sono effetti collaterali esterni e gli effetti collaterali esterni sono una fonte di errore anche se limitati a una manciata di funzioni membro. Se un carico di funzioni può richiamare direttamente quelle poche funzioni membro, allora c'è un carico di funzioni nel sistema che può indirettamente causare effetti collaterali esterni e che aumenta la complessità. Se c'è un solo posto nella base di codice che ha accesso a quelle funzioni membro e che un percorso di esecuzione non è innescato da eventi sporadici dappertutto, ma viene invece eseguito in modo molto controllato e prevedibile, allora riduce la complessità.

Complessità statale

Anche la complessità dello stato è un fattore piuttosto importante da tenere in considerazione. Una struttura semplice, ampiamente accessibile dietro un'interfaccia astratta, non è così difficile da confondere.

Una struttura dati grafica complessa che rappresenta la rappresentazione logica di base di un'architettura complessa è piuttosto semplice da confondere e in un modo che non viola nemmeno gli invarianti del grafico. Un grafico è molte volte più complesso di una struttura semplice, e quindi diventa ancora più cruciale in tal caso ridurre la complessità percepita della base di codice per ridurre il numero di posti che hanno accesso a tale struttura grafica al minimo assoluto, e dove quel tipo di strategia di "aggiornamento centrale" che si inverte in un paradigma pull per evitare sporadici, spinte dirette alla struttura dei dati del grafico da ogni parte può davvero ripagare.

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.