2009-12-12 13 views
6

Per favore, qualcuno mi può spiegare, cosa può sollevare un'eccezione in questo codice?Perché utilizzare la gestione delle eccezioni in codice apparentemente "sicuro"?

function CreateBibleNames: TStrings; 
begin 
    Result := TStringList.Create; 
    try 
    Result.Add('Adam'); 
    Result.Add('Eva'); 
    Result.Add('Kain'); 
    Result.Add('Abel'); 
    except 
    Result.Free; 
    raise; 
    end;  
end; 

Dal momento che utilizzo delphi, ho utilizzato una gestione delle eccezioni forse una volta. Considero il codice sopra scritto da un abile programmatore e non penso che le eccezioni siano ridondanti. Ma ancora, usare la gestione delle eccezioni in questo concetto rimane un mistero per me. Sembra essere un codice sicuro (senza provare tranne alla fine). Ho visto molte volte frammenti di codice simili come questo, ecco perché c'è probabilmente una buona ragione per scriverlo in questo modo nonostante la mia esperienza, che non ha dimostrato la sua necessità.

Inoltre quando qualcosa non funziona, ottengo descrizione eccezione ....

Thanx

+0

Quale bit del codice ti preoccupa? –

+0

tra prova e tranne ... Ho un atteggiamento simile a "simsong" ... giusto? – lyborko

+0

Anche se NON si dovrebbe implementare la gestione delle eccezioni solo per il gusto di farlo; nel codice precedente, è possibile un'eccezione. Se si verifica uno, allora c'è ** NO ** modo per il codice chiamante per eseguire la pulizia necessaria (per evitare perdite di memoria); pertanto, è imperativo che il codice sopra riportato prenda le precauzioni come illustrato. –

risposta

12

Ok, quel codice è strano, sono d'accordo, e capisco perfettamente perché è stato scritto in quel modo. Ma è stato scritto in quel modo perché la premessa alla base del codice è sbagliata.Il fatto che il costrutto sembri strano dovrebbe essere un "odore di codice" e dovrebbe dirti che qualcosa potrebbe non essere fatto nel modo migliore possibile.

In primo luogo, ecco perché l'insolito costrutto nel tentativo ... eccetto il blocco. La funzione crea una TStringList, ma se qualcosa va storto durante il riempimento, la TStringList che è stata creata verrà "persa" nell'heap e si verificherà una perdita di memoria. Quindi il programmatore originale era difensivo e si assicurava che se si fosse verificata un'eccezione, la TStringList sarebbe stata liberata e l'eccezione sarebbe stata nuovamente sollevata.

Ora ecco la parte "cattiva premessa". La funzione sta restituendo un'istanza di TStrings. Questo non è sempre il modo migliore per farlo. Restituire un'istanza di un oggetto come questo solleva la domanda "Chi ha intenzione di disporre di questa cosa che ho creato?". Crea una situazione in cui potrebbe essere facile, dal lato della chiamata, dimenticare che un'istanza di TStrings è stata allocata.

Una "migliore pratica" consiste nel fare in modo che la funzione assuma come parametro un TStrings e quindi inserire l'istanza esistente. In questo modo, non c'è dubbio su chi possiede l'istanza (il chiamante) e quindi chi dovrebbe gestirne la durata.

Quindi, la funzione diventa una procedura e potrebbe assomigliare a questo:

procedure CreateBibleNames(aStrings: TStrings); 
begin 
    if aStrings <> nil then 
    begin 
    aStrings .Add('Adam'); 
    aStrings .Add('Eva'); 
    aStrings .Add('Kain'); 
    aStrings .Add('Abel'); 
    end;  
end; 

Ora, questo non vuol dire che il ritorno di un'istanza di oggetto è una brutta cosa ogni volta - che è l'unica funzione del modello di fabbrica molto utile, per esempio. Ma per più funzioni "generali" come questa, quanto sopra è un modo "migliore" di fare le cose.

+11

Ogni volta che qualcuno crea un'istanza dell'oggetto, chiama una funzione per restituire la nuova istanza. Questa funzione (solitamente chiamata Create o CreateXXX) è un tipo speciale di funzione (un costruttore); ma una funzione comunque. Quindi, ogni volta che qualcuno chiama tale funzione Crea, è loro responsabilità gestire la risorsa (distruggere l'oggetto quando necessario). SUGGERIMENTO: non chiamare la procedura 'procedura ** Crea ** BibleNames (aStrings: TStrings); '... perché quello è ** NON ** quello che sta facendo. –

2

L'eccezione più ovvia sarebbe qualunque Delphi usa per OutOfMemoryException. Se non c'è abbastanza memoria per aggiungere un paio di stringhe a un elenco, liberare l'intero elenco e ripetere l'eccezione.

Sembra una programmazione eccessivamente difensiva per creare un elenco di nomi predefiniti - in genere, se si ottiene un'eccezione a causa della mancanza di memoria, viene interrotta l'intera applicazione.

+0

ovviamente, questo è un esempio molto primitivo, ma rivela il pensiero avanzato ... dovrei fare lo stesso? Vedo solo uno svantaggio: il codice è meno leggibile ... – lyborko

+2

Lyborko, meno il codice diventa primitivo, più è importante usare l'idioma di gestione delle eccezioni dimostrato nella domanda. –

4

L'unica ragione potenziale per un'eccezione che posso vedere è un'eccezione OutOfMemory e se questo è il caso, tutte le scommesse sono comunque disattivate. Secondo me questo è un esempio di uso abusivo di un concetto valido.

uso eccessivo e ingiustificato di prova, tranne ingombra il codice e lo rende più difficile leggere

+5

È solo più difficile da leggere finché non si acquisisce familiarità con l'idioma try-except per le funzioni di fabbrica come quella mostrata. Quindi, a malapena lo si nota. Se c'è un costruttore che * non è * immediatamente seguito da un'istruzione "try", il codice sembra strano. –

-2

Come nota a margine interessante, alcuni di Giava autori ormai considerano la decisione di richiedere la cattura di eccezioni in quasi tutti casi per essere un errore Questo perché la maggior parte delle eccezioni ha come risultato la morte del programma, quindi puoi anche lasciare che il sistema runtime uccida il programma e stampi una traccia di stack, piuttosto che forzare il programmatore a catturare l'eccezione e ... beh, basta stampare qualcosa e uccidere il programma.

+0

Non è necessario catturare tutte le eccezioni nel linguaggio Java. Le eccezioni controllate possono essere gestite o dichiarate nella dichiarazione del metodo. Se un metodo non gestisce un'eccezione controllata, il metodo deve dichiararlo utilizzando la parola chiave throws. Le eccezioni non controllate non devono essere gestite affatto. – mjn

+2

Non voglio che tu stia lavorando a qualsiasi software che si occupi di garantire che qualcuno viva! Uscire dal programma in caso di errore in caso di errore non dovrebbe mai essere un'opzione, anche per Hello, World! – TofuBeer

+1

L'eccezione del codice utente Delphi causa molto raramente l'uccisione dell'intera applicazione. –

2

Senza la sorgente per TStringList o la documentazione autorevole, non si può davvero sapere. Solo a scopo illustrativo, supponiamo che TStringList sia scritto in modo tale che quando la memoria si stringe, inizia a scambiare porzioni della lista da e verso il disco, permettendoti di gestire un elenco grande quanto il tuo disco. Ora il chiamante è anche suscettibile alla gamma di errori I/O: spazio su disco, blocco errato, timeout di rete, ecc.

La gestione di tali eccezioni è eccessiva? Chiamata di giudizio basata sullo scenario. Potremmo chiedere ai programmatori della NASA cosa pensano.

9

È una buona abitudine essere presente quando si restituiscono oggetti di nuova costruzione dalle funzioni. In uno scenario più complesso di una lista di stringhe, alcune invarianti possono essere interrotte o si verificano altri errori e quindi un'eccezione, e in tal caso si desidera liberare il valore restituito. Piuttosto che dover esaminare tutte le possibili situazioni in cui ciò può accadere, è generalmente una buona pratica avere un singolo modello per restituire oggetti di nuova costruzione e seguirlo ovunque.

In questo modo, quando l'implementazione degli oggetti che si stanno costruendo cambia in futuro, si è ancora sicuri se si verifica un'eccezione.

+2

Ma chiamerei ancora FreeAndNil (Risultato). È vero che l'eccezione è passata, ma è in questo caso che la funzione restituisce nil in questo caso. Non sai mai cosa succede dopo e nulla ti segnala che il risultato non è valido. – Runner

+3

@ Runner: il fatto che ci sia un'eccezione significa che il risultato è ** NON ** restituito. E non vi è alcun motivo per impostare un valore che non sarà mai visto a nulla (incluso nil). –

+0

Hm Ho appena controllato e tu sei corretto. Interessante :) Non l'ho mai notato, ma probabilmente è perché non scrivo mai il codice in questo modo. – Runner

4

vorrei scrivere il codice in questo modo, per diversi motivi:

1) attraverso la standardizzazione come le allocazioni di memoria guardano, è facile per ispezionare il codice sorgente per vedere se manca qualcosa, senza dover leggere tutte le linee . Qui, puoi vedere il .Create e notare che la gestione delle eccezioni è buona, quindi non devi leggere la parte tra try ... tranne.

2) Standardizzando il modo in cui si codificano le allocazioni di memoria, non si dimentica mai di farlo, anche se necessario.

3) Se in un secondo momento dovresti cambiare il codice sorgente, inserire del codice, cambiare la modalità .Add, sostituire TStringList con qualcos'altro, ecc., Quindi non infrangerebbe questo codice.

4) Significa che non devi pensare se TStringList.Add() genererà eccezioni, o meno, alleviando il tuo cervello per altri lavori che forniscono più valore.

2

Questo tipo di codice è un classico modello "factory". Questo esempio è banale e potrebbe non richiedere la gestione delle eccezioni perché potrebbe generare eccezioni in casi estremi. Tuttavia, se il codice factory è molto più complesso, è corretto liberare l'istanza creata prima di rilanciare qualsiasi eccezione rilevata perché il chiamante non può sapere se l'istanza è stata creata o meno prima che l'eccezione fosse sollevata. È molto meglio presumere che l'istanza non sia stata creata o liberata se viene restituita un'eccezione. Le fabbriche sono utili quando l'istanza restituita potrebbe non essere sempre la stessa (vale a dire qualsiasi classe in una determinata gerarchia) e quando i parametri per creare l'istanza sono "sconosciuti" al chiamante ma non alla fabbrica. In questi casi il chiamante non può passare un'istanza già creata solo per essere "riempita" con i parametri corretti, ma deve solo richiedere un'istanza - ovviamente deve essere chiaro che il chiamante diventa il "proprietario" dell'istanza creata .