Domanda

Domanda :

Quello che è considerato come "best practice" - e perché - di errori di manipolazione in un costruttore ?.

"Best Practice" può essere una citazione da Schwartz, o il 50% dei moduli CPAN usarlo, ecc ...; ma io sono contento di parere ben motivato da chiunque, anche se spiega perché la migliore pratica comune non è in realtà l'approccio migliore.

Per quanto riguarda il mio punto di vista del tema (informato da sviluppo di software in Perl per molti anni), ho visto tre approcci principali alla gestione degli errori in un modulo Perl (elencati dal migliore al peggiore, a mio parere):

  1. costruire un oggetto, impostare una bandiera non valida (metodo di solito "is_valid"). Spesso accoppiato con l'impostazione messaggio di errore tramite la gestione degli errori della vostra classe.

    Pro :

    • Consente di standard (rispetto ad altre chiamate ai metodi) di gestione degli errori in quanto consente di utilizzare le chiamate di tipo $obj->errors() dopo un cattivo costruttore come dopo qualsiasi altro metodo di chiamata.

    • Permette Per ulteriori informazioni da passare (ad esempio> 1 errore, avvisi, ecc ...)

    • Permette la funzionalità leggero "rifare" / "FIXME", in altre parole, se l'oggetto che è costruito è molto pesante, con molti attributi complessi che sono al 100% sempre OK, e l'unico motivo per cui non è valido è perché qualcuno è entrato una data non corretta, si può semplicemente fare "$obj->setDate()" al posto della testa di ri-esecuzione di tutto il costruttore di nuovo. Questo modello non è sempre necessario, ma può essere enormemente utile nella progettazione destra.

    Contro :. Nessuno che io sappia

  2. Return "undef".

    Contro : Non può raggiungere nessuno dei pro di prima soluzione (i messaggi di errore per-oggetto al di fuori delle variabili globali e leggero capacità "FIXME" per gli oggetti pesanti)

  3. Die all'interno del costruttore. Al di fuori di alcuni casi limite molto strette, io personalmente considero questa una scelta terribile per troppi motivi per elencare ai margini di questa domanda.

  4. UPDATE: Giusto per essere chiari, ritengo che la (altrimenti molto degno e un grande disegno) la soluzione di avere costruttore molto semplice che non può mancare a tutti e un metodo inizializzatore pesante dove tutto il controllo degli errori viene in mente essere solo un sottoinsieme di entrambi i casi # 1 (se inizializzazione imposta flag di errore) o caso # 3 (se inizializzatore muore) ai fini di questa domanda. Ovviamente, la scelta di un tale progetto, si rifiuta automaticamente l'opzione # 2.

È stato utile?

Soluzione

dipende da come si desidera che i costruttori si comportino.

Il resto di questa risposta va nelle mie osservazioni personali, ma come molte altre cose in Perl, Best Practices davvero riduce a "Ecco un modo per farlo, che si può prendere o lasciare a seconda delle esigenze." Le tue preferenze come hai descritto loro sono del tutto validi e coerenti, e nessuno dovrebbe dire il contrario.

Io in realtà preferire di morire se la costruzione non riesce, perché abbiamo impostato in modo che gli unici tipi di errori che possono verificarsi durante la costruzione oggetto sono davvero grandi, errori evidenti che dovrebbe fermare l'esecuzione.

D'altra parte, se si preferisce che ciò non accada, penso che preferirei 2 su 1, perché è altrettanto facile per verificare la presenza di un oggetto indefinito come è quello di verificare la presenza di qualche variabile bandiera. Questo non è C, quindi non abbiamo un forte vincolo di battitura che ci dice che il nostro costruttore deve restituire un oggetto di questo tipo. Quindi tornando undef, e la prova di che, per stabilire il successo o il fallimento, è una grande scelta.

La 'testa' di mancanza di costruzione è una considerazione in alcuni casi limite (in cui non si può non rapidamente prima di incorrere in testa), quindi per coloro si potrebbe preferire il metodo 1. Così ancora una volta, dipende da quello che hai semantica definito per la costruzione dell'oggetto. Ad esempio, è preferibile fare inizializzazione pesante fuori di costruzione. Per quanto riguarda la normalizzazione, penso che verificare se un costruttore restituisce un oggetto definito è un buon standard, come il controllo di una variabile di flag.

EDIT: In risposta alla tua modifica su initializers respingendo caso # 2, non vedo il motivo per cui un inizializzatore non può semplicemente restituire un valore che indica il successo o il fallimento piuttosto che impostare una variabile di bandiera . In realtà, si consiglia di utilizzare entrambi, a seconda di quanto si vuole dettagli sull'errore che si è verificato. Ma sarebbe perfettamente valido per un inizializzatore per tornare vero in caso di successo e undef in caso di fallimento.

Altri suggerimenti

Io preferisco:

  1. fare il meno possibile l'inizializzazione come nel costruttore.
  2. croak con un messaggio informativo quando qualcosa va storto.
  3. effettuato con metodi di inizializzazione per fornire per i messaggi di errore degli oggetti etc

Inoltre, tornando undef (invece di gracidare) va bene nel caso in cui gli utenti della classe non possono preoccuparsi perché esattamente si è verificato il guasto, solo se hanno ottenuto un oggetto valido o meno.

Io disprezzo facile dimenticare metodi is_valid o l'aggiunta di controlli supplementari per garantire metodi non vengono chiamati quando lo stato interno dell'oggetto non è ben definito.

Lo dico questi da un punto di vista molto soggettivo, senza fare dichiarazioni sulle migliori pratiche.

Vorrei raccomandare contro 1 # semplicemente perché porta a una maggiore errore codice di gestione che non sarà scritto. Ad esempio, se hai appena return false allora questo funziona bene.

my $obj = Class->new or die "Construction failed...";

Ma se si torna un oggetto che non è valido ...

my $obj = Class->new;
die "Construction failed @{[ $obj->error_message ]}" if $obj->is_valid;

E come la quantità di codice di gestione degli errori aumenta la probabilità di esso che è diminuisce scritte. E la sua non è lineare. Aumentando la complessità del sistema di gestione degli errori che in realtà diminuire la quantità di errori si cattura in uso pratico.

Bisogna anche stare attenti che il vostro oggetto non valido in questione muore quando un metodo viene chiamato (a parte is_valid e error_message) che porta ad ancora più codice e le opportunità per gli errori.

Ma sono d'accordo c'è valore di essere in grado di ottenere informazioni relative al fallimento, il che rende il ritorno false (non solo return return undef) inferiore. Tradizionalmente questo viene fatto chiamando un metodo di classe o una variabile globale come in DBI.

my $ dbh = DBI-> connect ($ data_source, $ username, $ password)               o morire $ DBI :: errstr;

Ma soffre di una) si devono ancora scrivere il codice di gestione degli errori e B) la sua valida solo per l'ultima operazione.

La cosa migliore da fare, in generale, è un'eccezione con croak. Ora, nel caso normale l'utente scrive nessun codice speciale, l'errore si verifica in corrispondenza del punto del problema, e ottengono un buon messaggio di errore per impostazione predefinita.

my $obj = Class->new;

raccomandazioni tradizionali del Perl contro generazione di eccezioni nel codice della libreria come maleducazione non è aggiornato. i programmatori Perl sono (finalmente) abbracciando eccezioni. Piuttosto che scrivere errore di codice di gestione mai e più volte, male e spesso dimenticando, eccezioni DWIM. Se non siete convinti solo iniziare a utilizzare Autodie ( guarda il video del PJF su di esso ) e non andrete mai indietro.

Eccezioni allineano codifica di Huffman con l'uso effettivo. Il caso comune di aspettarsi il costruttore di lavorare solo e che vogliono un errore se non è ora il codice minimo. Il caso raro di voler gestire tale errore richiede scrittura di codice speciale. E il codice speciale è piuttosto piccolo.

my $obj = eval { Class->new } or do { something else };

Se vi trovate avvolgere ogni chiamata in un eval si sta facendo male. Le eccezioni sono chiamati così perché sono eccezionali. Se, come nel tuo commento di cui sopra, si desidera la gestione degli errori grazioso per il bene dell'utente, poi approfittare del fatto che gli errori bolla lo stack. Per esempio, se si desidera fornire una bella pagina di errore utente e accedere anche l'errore si può fare questo:

eval {
    run_the_main_web_code();
} or do {
    log_the_error($@);
    print_the_pretty_error_page;
};

Hai solo bisogno in un unico luogo, in cima alla vostra stack di chiamate, piuttosto che sparsi ovunque. È possibile usufruire di questo a incrementi più piccoli, per esempio ...

my $users = eval { Users->search({ name => $name }) } or do {
    ...handle an error while finding a user...
};

Ci sono due cose che accadono. 1) Users->search restituisce sempre un valore vero, in questo caso un riferimento ad array. Questo rende il lavoro semplice my $obj = eval { Class->method } or do. Questo è opzionale. Ma ancora più importante 2) avete solo bisogno di mettere errore speciale gestione intorno Users->search. Tutti i metodi chiamati all'interno Users->search e tutti i metodi che chiamano ... semplicemente buttare eccezioni. E sono tutti catturati in un punto e gestiti allo stesso. Gestione eccezione nel punto in cui si prende cura su di esso rende molto più ordinato, compatto e il codice di gestione degli errori flessibile.

Potete imballare ulteriori informazioni in salvo da croaking con un oggetto stringa di sovraccarico, piuttosto che solo una stringa.

my $obj = eval { Class->new }
  or die "Construction failed: $@ and there were @{[ $@->num_frobnitz ]} frobnitzes";

Eccezioni:

  • Fare la cosa giusta senza alcun pensiero dal chiamante
  • Richiede il codice almeno per il caso più comune
  • Fornire la massima flessibilità e le informazioni relative al fallimento to il chiamante

I moduli, come Prova :: Piccolo risolvere la maggior parte delle questioni sospese che circondano utilizzando eval come un gestore di eccezioni.

Per quanto riguarda il tuo caso d'uso in cui si potrebbe avere un oggetto molto costoso e vuole cercare di continuare con esso parzialmente costruire ... puzza YAGNI a me. Avete veramente bisogno? O avete un oggetto di design gonfio che sta facendo troppo lavoro troppo presto. Se avete bisogno di esso, è possibile inserire le informazioni necessarie per continuare la costruzione in oggetto eccezione.

In primo luogo le osservazioni generali pomposi:

  1. Il compito di un costruttore dovrebbe essere: Attribuite parametri costruttivi validi, restituisce un oggetto valido
  2. .
  3. Un costruttore che non costruire un oggetto valido non può eseguire il suo lavoro ed è quindi un candidato ideale per la generazione eccezione.
  4. Fare che l'oggetto costruito è valido è parte del lavoro del costruttore. Distribuendo una nota-to-be-oggetto cattivo e contando sul client per verificare che l'oggetto sia valido è un modo sicuro per finire con gli oggetti non validi che esplodono in luoghi remoti per motivi non ovvie.
  5. verificare che tutti gli argomenti corretti sono in atto prima della chiamata al costruttore è di lavoro del cliente.
  6. Eccezioni forniscono un modo a grana fine di propagare l'errore che si è verificato particolare senza la necessità di avere un oggetto spezzato in mano.
  7. return undef; è sempre male [1]
  8. bIlujDI 'yIchegh () Qo'; yIHegh ()!

Ora alla domanda reale, che mi interpretare il significato di "che cosa, Darch, considera la migliore pratica e perché". In primo luogo, io notare che restituisce un valore falso in caso di fallimento ha una lunga storia Perl (la maggior parte del nucleo funziona in questo modo, per esempio), e un sacco di moduli seguono questa convenzione. Tuttavia, si scopre questa convenzione produce codice client inferiore e nuove unità si allontanano da esso. [2]

[I campioni sostegno argomento e codice per questo risultano essere il caso più generale per le eccezioni che hanno indotto la creazione di Autodie , e così io resistere alla tentazione di fare quel caso qui. Invece:]

dover controllare per la creazione di successo è in realtà più onerose di verifica un'eccezione a livello di gestione delle eccezioni appropriata. Le altre soluzioni richiedono il client immediato di fare più lavoro di quello che dovrebbe avere per solo per ottenere un oggetto, il lavoro che non è necessaria quando il costruttore fallisce di un'eccezione. [3] Un'eccezione è enormemente più espressivo undef e altrettanto espressiva passare indietro un oggetto rotto per scopi di errori documentano e l'annotazione loro a diversi livelli nella pila chiamata.

È possibile anche ottenere l'oggetto parzialmente costruito se si passa di nuovo nel l'eccezione. Penso che questa sia una cattiva pratica per la mia convinzione su ciò che il contratto di un costruttore con i propri clienti dovrebbe essere, ma il comportamento è supportato. Goffamente.

Quindi: un costruttore che non può creare un oggetto valido dovrebbe generare un'eccezione il più presto possibile. Le eccezioni un costruttore può buttare devono essere documentate le parti della sua interfaccia. Solo i livelli di chiamata che possono significato dar seguito l'eccezione dovrebbe nemmeno cercarlo; molto spesso, il comportamento di "se questa costruzione non riesce, non fare nulla" è esattamente corretto.

[1]: e con questo intendo, io non sono a conoscenza di casi d'uso in cui return; non è strettamente superiore. Se qualcuno mi chiama su questo avrei potuto aprire in realtà una domanda. Quindi, per favore non lo fanno. ;)
[2]:. Per il mio ricordo molto poco scientifico delle interfacce dei moduli che ho letto negli ultimi due anni, oggetto di entrambi i bias di selezione e di conferma
[3]: Si noti che un'eccezione richiede ancora di gestione degli errori, come farebbe le altre soluzioni proposte. Questo non significa avvolgere ogni esemplificazione in una eval a meno che non si vuole realmente fare complesso di gestione degli errori in giro ogni costruzione (e se si pensa di fare, siete probabilmente sbagliato). Significa avvolgendo la chiamata che è in grado di agire sul significato l'eccezione in un eval.

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