Domanda

Mi sembra alternative di Google per le eccezioni sono

  • GO: multi-valore di ritorno "ritorno val, err;"
  • GO, C ++: zero controlli (rientro anticipato)
  • GO, C ++: "gestire l'errore maledetta" (il mio mandato)
  • C ++: assert (espressione)

  • GO: rinviare / panico / recupero sono caratteristiche del linguaggio aggiunti dopo questa domanda è stato chiesto

è multi-valore di ritorno abbastanza utile per agire come alternativa? Perché sono "afferma" considerato alternative? Fa Google pensare che O.K. se un programma si arresta se si verifica un errore che non è gestita correttamente?

effettiva: ritorno multipla valori

  

Una delle caratteristiche insolite del Go è che le funzioni ei metodi possono restituire valori multipli. Questo può essere utilizzato per migliorare un paio di linguaggi maldestri in programmi C:. In-band ritorni di errore (ad esempio -1 per EOF) e la modifica di un argomento

     

In C, un errore di scrittura viene segnalato da un   conteggio negativa con il codice di errore   secreto via in una posizione instabile.   Nel Go, scrittura può restituire un conteggio e un   errore: “Sì, hai scritto alcuni byte, ma   Non tutti loro, perché hai riempito la   dispositivo". La firma di * File.Write   nel pacchetto di sistema operativo è:

     

func (file *File) Write(b []byte) (n int, err Error)

     

e la documentazione dice,   restituisce il numero di byte scritti   e un errore non nullo quando n! = len (b).   Questo è uno stile comune; vedere la   sezione sulla gestione degli errori per maggiori   esempi.

GO Efficace: Chiamato parametri dei risultati

  

Il ritorno o risultato "parametri" di un   Vai funzione può essere dato nomi e   utilizzati come variabili normali, proprio come   i parametri di ingresso. Quando chiamato,   vengono inizializzati allo zero   I valori per i loro tipi, quando il   funzione inizia; se la funzione   esegue un'istruzione return senza   argomenti, i valori correnti della   parametri dei risultati vengono utilizzati come   valori restituiti.

     

I nomi non sono obbligatori, ma   può rendere il codice più corta e più chiara:   sono la documentazione. Se chiamiamo la   risultati di nextInt diventa evidente   che ha restituito int è che.

     

func nextInt(b []byte, pos int) (value, nextPos int) {

     

Poiché i risultati di nome vengono inizializzati e legati ad un   ritorno disadorna, possono semplificare come   oltre che chiarire. Ecco una versione di   io.ReadFull che li utilizza bene:

func ReadFull(r Reader, buf []byte) (n int, err os.Error) {
  for len(buf) > 0 && err == nil {
    var nr int;
    nr, err = r.Read(buf);
    n += nr;
    buf = buf[nr:len(buf)];
  }
  return;
}

Perché non partire Hai eccezioni ?

  

Le eccezioni sono una storia simile. Un certo numero di disegni per le eccezioni sono state proposte ma ognuno aggiunge notevole complessità al linguaggio e run-time. Per loro stessa natura, le eccezioni si estendono le funzioni e forse anche goroutines; hanno implicazioni ad ampio raggio. C'è anche preoccupazione per l'effetto che avrebbero avuto sulle librerie. Essi sono, per definizione, ma eccezionale esperienza con altri linguaggi che supportano mostrare loro che hanno effetto profondo sulla biblioteca e le specifiche di interfaccia. Sarebbe bello trovare un design che permette loro di essere veramente eccezionale, senza incoraggiare gli errori più comuni di trasformare in flusso di controllo speciale che richiede ogni programmatore per compensare.

     

Come farmaci generici, eccezioni restano un problema aperto.

Google Style Guide C ++: eccezioni

  

Decisione:

     

Sul loro volto, i benefici di ucantare   eccezioni superano i costi,   soprattutto in nuovi progetti. Però,   per codice esistente, l'introduzione di   eccezioni ha implicazioni su tutti   codice dipendente. Se eccezioni possono essere   propagato al di là di un nuovo progetto,   diventa anche problematico per integrare   il nuovo progetto in esistente   eccezioni senza codice. Poiché la maggior parte   codice C ++ esistente di Google non è   pronti ad affrontare eccezioni,   è relativamente difficile adottare   nuovo codice che genera eccezioni.

     

Dato che il codice esistente di Google è   Non eccezione-tolerant, i costi di   usare le eccezioni sono un po 'più grande   rispetto ai costi in in un nuovo progetto.   Il processo di conversione sarebbe lento   e soggetto ad errori. Noi non crediamo che    alternative disponibili a   eccezioni, come i codici di errore e   asserzioni, introducono un significativo   fardello.

     

Il nostro consiglio contro l'uso delle eccezioni è   non fondato sul filosofica o   motivi morali, ma quelli pratici.   Perché vorremmo usare il nostro   progetti open-source di Google e   è difficile farlo se quelle   progetti utilizzano eccezioni, abbiamo bisogno di   sconsigliare eccezioni in Google   progetti open-source pure. Cose   probabilmente sarebbe diverso se avessimo   a fare tutto nuovo da zero.

GO: Defer, panico e recuperare

  

dichiarazioni Posticipa ci permettono di pensare a chiudere ogni file subito dopo l'apertura di esso, a garantire che, indipendentemente dal numero di istruzioni return nella funzione, i file verranno chiusi.

     

Il comportamento delle dichiarazioni Posticipa è semplice e prevedibile. Ci sono tre semplici regole:

     

1. argomenti di una funzione differita vengono valutati quando l'istruzione viene valutata rinviare.

     

In questo esempio, l'espressione "i" è valutata quando la chiamata println è differito. La chiamata differita stamperà "0", dopo la funzione ritorna.

    func a() {
         i := 0
         defer fmt.Println(i)
         i++
         return    
    }
     

2. chiamate di funzione differite vengono eseguiti in ultimo in ordine First Out dopo la funzione ritorna circostanti Questa funzione stampa "3210".

     func b() {
        for i := 0; i < 4; i++ {
            defer fmt.Print(i)
        }   
     }
     

3. funzioni anticipate possono leggere e assegnare ai valori restituiti nome della funzione di ritorno.

     

In questo esempio, una funzione differita incrementa il valore di ritorno ho dopo la funzione ritorna circostanti. Così, questa funzione restituisce 2:

    func c() (i int) {
        defer func() { i++ }()
        return 1 
    }
     

Questo è utile per modificare il valore di ritorno di errore di una funzione; vedremo un esempio di questo breve.

     

Panic è una funzione incorporata che arresta il flusso normale di controllo e comincia panico. Quando la funzione F chiama panico, esecuzione di F si ferma, tutte le funzioni differite F vengono eseguite normalmente, e allora F ritorna al chiamante. Per il chiamante, F si comporta quindi come una chiamata a prendere dal panico. Il processo continua fino pila finché tutte le funzioni del goroutine corrente sono tornati, a questo punto il programma si blocca. Panico possono essere avviate invocando direttamente il panico. Possono anche essere causate da errori di runtime, come fuori dal campo matrice accessi.

     

Recupero è una funzione incorporata che riprende il controllo di un goroutine panico. Recupero è utile solo all'interno delle funzioni differite. Durante l'esecuzione normale, una chiamata a recuperare tornerà a zero e non hanno altro effetto. Se il goroutine corrente è in preda al panico, una chiamata a recuperare catturerà il valore dato al panico e riprendere la normale esecuzione .

     

Ecco un programma di esempio che dimostra la meccanica di panico e di rinviare:

<snip>
     

Per un esempio reale di panico e di recupero, vedere il foglio JSON dalla libreria standard Go. E 'decoDati des JSON-codificato con un insieme di funzioni ricorsive. Quando si incontra JSON valido, il parser chiama panico è rilassarsi stack alla chiamata funzione di primo livello, che recupera dal panico e restituisce un valore di errore appropriato (vedi 'errore' e funzioni 'unmarshalling' in decode.go) . C'è un simile esempio di questa tecnica nella routine compilazione del pacchetto regexp. La convenzione nelle librerie Go è che anche quando un pacchetto usa panico internamente, la sua API esterna presenta ancora valori espliciti errore di ritorno.

     

Altri usi di rinviare (oltre il File.Close () esempio dato in precedenza) comprendono il rilascio di un mutex:

mu.Lock()  
defer mu.Unlock
È stato utile?

Soluzione

rendimenti multipli non sono unici andare, e non sono un sostituto per le eccezioni. A C (o C ++) termini, sono un sostituto sintetico e di facile utilizzo per la restituzione di una struttura (oggetto) contenente più valori.

Forniscono un comodo mezzo di errori che indicano, se questo è tutto dire.

alternative Perché sono "afferma" considerati?

Asserisce sono inizialmente per il debug. Essi fermare il programma in situazioni in cui si è in uno stato "impossibile", quella che il disegno dice che non dovrebbe accadere, ma che ha comunque. Tornando un errore è improbabile che possa aiutare molto. La base di codice, ovviamente, non funziona ancora, così come sulla terra si può recuperare con successo? Perchè potreste vuole che, quando c'è un bug che ha bisogno di attenzione?

Utilizzando afferma nel codice di produzione è un po 'di una cosa diversa - ovviamente ci sono problemi di prestazioni e dimensioni del codice, in modo che il solito approccio è quello di rimuovere una volta la vostra analisi del codice e test che hanno convinto che le situazioni "impossibili" sono davvero impossibile. Ma, se si sta eseguendo il codice a questo livello di paranoia, che è il controllo stesso, allora probabilmente siete anche paranoico che se si lascia trasportare a correre in uno stato "impossibile", allora potrebbe fare qualcosa di pericolosamente rotto: corrompere dati importanti, sorpasso un'allocazione di stack e, forse, la creazione di vulnerabilità di sicurezza. Così ancora una volta, si vuole solo chiudere il più presto possibile.

La roba si utilizza afferma per la realtà non è lo stesso di quello che usate eccezioni per: durante la programmazione di linguaggi come C ++ e Java fornire eccezioni per situazioni "impossibili" (logic_error, ArrayOutOfBoundsException), hanno involontariamente incoraggiano alcuni programmatori di pensare che i loro programmi di dovrebbe tentativo di recuperare da situazioni in cui davvero sono fuori controllo. A volte è necessario, ma il consiglio di Java non prendere RuntimeExceptions è lì per una buona ragione. Molto raramente è una buona idea quella di prendere uno, che è il motivo per cui esistono. Quasi sempre non è una buona idea per la cattura di loro, il che significa che essi ammontano ad arrestare il programma (o almeno il filo) in ogni caso.

Altri suggerimenti

Si consiglia di leggere un paio di articoli sulle eccezioni per rendersi conto che i valori restituiti non sono eccezioni. Non nella C 'in-band' modo o in qualsiasi altro modo.

Senza entrare in una discussione profonda, le eccezioni sono destinate ad essere gettato in cui la condizione di errore viene trovato e catturato in cui la condizione di errore può essere gestito significato. I valori di ritorno vengono elaborati solo nella prima funzione lo stack gerarchia, che potrebbe o potrebbe non come elaborare il problema. Un semplice esempio potrebbe essere un file di configurazione che può recuperare valori come stringhe, e supporta anche la trasformazione in istruzioni return digitati:

class config {
   // throws key_not_found
   string get( string const & key );
   template <typename T> T get_as( string const & key ) {
      return boost::lexical_cast<T>( get(key) );
   }
};

Ora il problema è come si fa a gestire se non è stata trovata la chiave. Se si utilizzano i codici di ritorno (diciamo in go-way) il problema è che get_as deve gestire il codice di errore da get e agire di conseguenza. Poiché in realtà non sa cosa fare, l'unica cosa sensata è manualmente di moltiplicazione l'errore a monte:

class config2 {
   pair<string,bool> get( string const & key );
   template <typename T> pair<T,bool> get_as( string const & key ) {
      pair<string,bool> res = get(key);
      if ( !res.second ) {
          try {
             T tmp = boost::lexical_cast<T>(res.first);
          } catch ( boost::bad_lexical_cast const & ) {
             return make_pair( T(), false ); // not convertible
          }
          return make_pair( boost::lexical_cast<T>(res.first), true );
      } else {
          return make_pair( T(), false ); // error condition
      }
   }
}

L'implementatore della classe deve aggiungere il codice aggiuntivo per inoltrare gli errori, e che il codice viene mescolata con la logica effettiva del problema. In C ++ questo è probabilmente più onerosi di quelli in un linguaggio progettato per le assegnazioni multiple (a,b=4,5), ma ancora, se la logica dipende dal possibile errore (qui chiamata lexical_cast deve essere eseguita solo se abbiamo una stringa reale), allora si dovrà valori di cache in variabili in ogni caso.

Non è andare, ma in Lua, cambio multiplo è un idioma molto comune per la gestione delle eccezioni.

Se si ha una funzione come

function divide(top,bottom)
   if bottom == 0 then 
        error("cannot divide by zero")
   else
        return top/bottom
   end
end

Poi, quando bottom era 0, un'eccezione sarebbe stata sollevata e l'esecuzione del programma sarebbe fermare, a meno che non avvolto la funzione divide in un pcall (o chiamata protetta) .

pcall restituisce sempre due valori: il primo è risultato è un valore booleano che indica se la funzione è tornato con successo, e il secondo risultato è o il valore di ritorno o il messaggio di errore

.

Il seguente (forzato) Lua frammento di codice mostra questo in uso:

local top, bottom = get_numbers_from_user()
local status, retval = pcall(divide, top, bottom)
if not status then
    show_message(retval)
else
    show_message(top .. " divided by " .. bottom .. " is " .. retval)
end

Naturalmente, non c'è bisogno di usare pcall, se la funzione che stai chiamando già ritorna sotto forma di status, value_or_error.

ritorno multipla è stato abbastanza buono per Lua per diversi anni, così, mentre questo non assicurare che è abbastanza buono per andare, è di supporto di questa idea.

Sì, i valori di ritorno di errore sono belle, ma non colgono il vero significato di gestione delle eccezioni ... che è la capacità e la gestione dei casi eccezionali in cui uno normalmente non intende.

Il Java (cioè) la progettazione considera eccezioni IMO di essere flusso di lavoro valido scenari e hanno un punto circa la complessità delle interfacce e librerie dover dichiarare e la versione questi eccezione generata, ma ahimè eccezioni servono un ruolo importante nel domino stack.

Si pensi al caso alternativo in cui i codici di ritorno eccezionali sono condizionalmente gestite in diversi metodo dozzina di chiamate in profondità. Che cosa sarebbe impilare tracce assomigliano in termini di dove il numero di riga incriminata è?

Questa domanda è un po 'difficile da rispondere oggettivamente e pareri su eccezioni possono differire molto.

Ma se dovessi speculare, credo che il motivo principale eccezioni non è incluso nel Go è perché complica il compilatore e può portare a conseguenze non banali quando si scrivono le librerie. Eccezioni è difficile da ottenere, e loro priorità ottenere qualcosa di lavoro.

La differenza principale tra la gestione degli errori attraverso i valori di ritorno e le eccezioni è che le eccezioni costringe il programmatore a che fare con condizioni insolite. Non si può mai avere un "errore di silenzio" a meno che non si cattura esplicitamente un'eccezione e non fare nulla nel blocco catch. D'altra parte, si ottengono punti rendimento implicito in tutto il mondo all'interno di funzioni che possono portare ad altri tipi di insetti. Questo è particolarmente diffuso con C ++ in cui gestire la memoria in modo esplicito e necessario assicurarsi di non perdere mai un puntatore a qualcosa che avete assegnato.

Esempio di situazione di pericolo in C ++:

struct Foo {
    // If B's constructor throws, you leak the A object.
    Foo() : a(new A()), b(new B()) {}
    ~Foo() { delete a; delete b; }

    A *a;
    B *b;
};

valori di ritorno multipli rende più facile implementare la gestione degli errori valore di ritorno base, senza dover fare affidamento su fuori argomenti alle funzioni, ma non cambia nulla fondamentalmente.

Alcune lingue hanno entrambi più valori di ritorno e le eccezioni (o meccanismi simili). Un esempio è Lua .

Ecco un esempio di come più valori di ritorno potrebbero funzionare in C ++. Non vorrei scrivere questo codice me, ma io non credo che sia del tutto fuori questione di utilizzare un approccio di questo tipo.

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

// return value type
template <typename T> 
struct RV {
    int mStatus;
    T mValue;

    RV( int status, const T & rv ) 
        : mStatus( status ), mValue( rv ) {}
    int Status() const { return mStatus; }
    const T & Value() const {return mValue; }
};

// example of possible use
RV <string> ReadFirstLine( const string & fname ) {
    ifstream ifs( fname.c_str() );
    string line;
    if ( ! ifs ) {
        return RV <string>( -1, "" );
    }
    else if ( getline( ifs, line ) ) {
        return RV <string>( 0, line );
    }
    else {
        return RV <string>( -2, "" );
    }
}

// in use
int main() {
    RV <string> r = ReadFirstLine( "stuff.txt" );
    if ( r.Status() == 0 ) {
        cout << "Read: " << r.Value() << endl;
    }
    else {
        cout << "Error: " << r.Status() << endl;
    }
}

Se avete bisogno del senso C ++ di fare un "annullabile" l'uso oggetto boost :: opzionale . Si prova come un valore booleano e se si valuta vero allora si dereferenziarlo ad un T valido.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top