|
|
|
![]() |
|
Strumenti |
![]() |
#1 |
Member
Iscritto dal: Jun 2006
Messaggi: 117
|
[ALL] Curiosita' sui flusso di controllo di un programma
Scusate, spesso mi imbatto in situazioni in cui devo eseguire una serie di operazioni all'interno di un "task" piu' generale e spesso l'operazione i-esima deve essere eseguita solo se la precedente e' andata a buon fine.
Per esempio ho un task generico che per coerenza logica e' suddiviso in 4 operazioni A, B, C e D. Ogni operazione e' ovviamente una funzione. Ad esempio: - A = lettura di un file con i dati di connessione - B = connessione ad un db - C = esecuzione select - D = lettura recordset ottenuto Ognuna di queste 4 operazioni dipende dal successo della precedente (vabbe' a parte la prima che va sempre eseguita...). Sinceramente a me fa abbastanza schifo scrivere codice del tipo: Codice:
if(A) if(B) if(C) if(D) ... else errore D else errore C else errore B else errore A Vorrei sapere se voi seguite degli schemi precisi o meno. Questa e' una questione piuttosto sottovalutata in programmazione ma alla fine la "ciccia" dei nostri programmi e' proprio costituita da tutti quei brutti IF, SWITCH, WHILE, FOR... di cui anche la programmazione ad oggetti prima o poi necessita. |
![]() |
![]() |
![]() |
#2 |
Senior Member
Iscritto dal: Nov 2005
Messaggi: 2774
|
Con il meccanismo delle eccezioni (se disponibile) puoi scrivere codice più elegante e leggibile:
Codice:
try{ A B C D }catch(Errore){ Gestione Errore } |
![]() |
![]() |
![]() |
#3 |
Member
Iscritto dal: Jun 2006
Messaggi: 117
|
Beh non e' necessariamente corretto e produttivo l'uso delle eccezioni per la gestione degli errori. Le eccezioni generalmente vanno lanciate e catturate per gestire situazioni "eccezionali" appunto, cioe' imprevisti.
Nelle situazioni a cui faccio riferimento, come nell'esempio, io vorrei che B venisse eseguita solo se A va a buon fine, ma A potrebbe benissimo ritornare errore per un motivo piu' che lecito e per nulla "eccezionale". Cioe' non sempre le operazioni falliscono per motivi inconsueti. Per esempio l'operazione C dell'esempio (select su db) potrebbe semplicemente ritornare null perche' non trova alcun record, ma questo non significa che si debba lanciare un'eccezione! Per chiarire meglio, il mio codice nel caso presentato e' spesso cosi: Codice:
try { if(A) if(B) if(C) if(D) ... else errore D else errore C else errore B else errore A }catch(Errore){ Gestione Errore } |
![]() |
![]() |
![]() |
#4 |
Senior Member
Iscritto dal: May 2001
Messaggi: 12815
|
Mah non saprei... è prevedibile che un sistema di risoluzione dei nomi (tipo DNS) possa non trovare alcun record relativo ad un URL?
A mio parere si, eppure nei moderni linguaggi di programmazione questa viene considerata una eccezione. La regola è che tutto funzioni come dovrebbe, l'eccezione è proprio un caso che non ti consente di andare avanti nel programma di per se. Nel tuo caso: - A = lettura di un file con i dati di connessione - B = connessione ad un db - C = esecuzione select - D = lettura recordset ottenuto A e B possono essere tranquillamente gestite tramite eccezioni. In C l'esecuzione della select per quale motivo dovrebbe fallire, supponendo che la query sia pre-confezionata e ben formata? Stessa cosa per D, NULL non è un fallimento. Qui il piano si sposta su cosa consideri come fallimento... supponendo che per C e D valgano le mie considerazioni, non ci sarebbe alcun motivo di fare un controllo su queste due cose. Chiaramente in fase di presentazione dei risultati il discorso è diverso, in quanto eventuali record NULL potresti volerli evitare (adesso non ricordo se i ResultSet scartano in automatico i record NULL, in quel caso non dovresti fare controlli, in genere le routine di presentazione ciclano sulla base della dimensione del ResultSet, che in questo caso sarebbe nulla). E' chiaro che questo è un esempio, come dici te ci si può trovare spesso nella situazione di dover effettuare più controlli concatenati, e se non vuoi ricorrere alle eccezioni credo che quello sia l'unico modo. Una cosa che ho trovato comoda per levare un po' di if di mezzo è usare ove possibile il polimorfismo, chiaramente non si può usare sempre questa via (ad esempio in questo caso poco c'entrerebbe), però in alcuni casi può essere la soluzione. Ultima modifica di WarDuck : 17-03-2010 alle 18:58. |
![]() |
![]() |
![]() |
#5 |
Senior Member
Iscritto dal: Nov 2004
Città: Tra Verona e Mantova
Messaggi: 4553
|
Se usi un linguaggio OO la questione non si pone. Ogni linguaggio orientato agli oggetti è un meta-linguaggio. Significa tra l'altro che sei sempre libero di definire un approccio alla soluzione di un problema che esula dalle operazioni predefinite.
Bisogna avere un'idea alternativa, un "sarebbe bello se potessi scrivere così". Come si potrebbe esprimere una catena di if in modo non annidato? Si potrebbe usare un concatenatore di espressioni, qualcosa cioè che data una lista di espressioni esegua la successiva solo se la precedente abbia dato un certo risultato. Diremmo allora: a = if x qualcosa else qualcos'altro b = if x qualcosa else qualcos'altro ... z = if x qualcosa else qualcos'altro concatenatore esegui a + b + ... + z Non è difficile pensare a come si possa realizzare. Abbiamo semplicemente un'espressione composta di una condizione e due alternative che restituisce un valore di controllo, diciamo un booleano. L'espressione restituisce true se la condizione è true altrimenti esegue l'alternativa e restituisce false. Il concatenatore piglia queste espressioni e dice: per ogni espressione x se x.eval restituisce true esegui la successiva altrimenti termina. Tutto sta nello scegliere una soluzione: il resto lo fa l'orientamento agli oggetti.
__________________
Uilliam Scecspir ti fa un baffo? Gioffri Cioser era uno straccione? E allora blogga anche tu, in inglese come me! |
![]() |
![]() |
![]() |
#6 |
Junior Member
Iscritto dal: Mar 2010
Messaggi: 8
|
Non basta definire un metodo per A che ritorna un valore booleano per indicarne l'esito?
Così puoi tranquillamente scrivere Codice:
if(A) B Ultima modifica di sadino90 : 17-03-2010 alle 19:56. |
![]() |
![]() |
![]() |
#7 | |
Senior Member
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
|
Quote:
Le situazioni "eccezionali" sono invece quelle non previste e quindi non gestite se non, solitamente, da un try catch in cui è contenuto il ciclo principale del programma. Java differenzia proprio bene questa situazione con le checked e le unchecked exception. |
|
![]() |
![]() |
![]() |
#8 |
Member
Iscritto dal: Jun 2006
Messaggi: 117
|
Beh veramente tutto quello che ho letto sulle eccezioni generalmente diceva proprio di evitare di utilizzare le eccezioni per la gestione degli errori comuni e prevedibili.
In ogni caso non volevo focalizzarmi su un esempio preciso, ma piuttosto su una "best practice" comune. E' innegabile infatti che generalmente un sistema software complesso presenta innumerevoli operazioni composte da altre operazioni e cosi' via, creando un annidamento non banale. Proprio per questo generalmente le best practice per l'exception handling dicono di gestire le eccezioni il piu' vicino possibile al punto in cui vengono generate. Questo implica chiaramente che non si puo' utilizzare un unico try-catch a livello del main, ma invece occorre avere un blocco try-catch quasi in ogni metodo-operazione sufficientemente complessa da poter generare molte situazioni anomale e impreviste. E questo e' un altro punto fondametale. Infatti gran parte delle funzioni-metodi corposi a mio parere devono avere una gestione moltro stretta degli errori, cioe' gestire tutto il piu' localmente possibile e soprattutto bisogna sempre supporre che per quanto siamo stati bravi, potrebbe essere generato un errore imprevisto anche dopo mesi che il software e' in stato di release. Nell'esempio che ho fatto, volutamente semplice, vorrei farvi notare che, sì, e' vero che una semplice select difficilmente fallisce, ma ripeto, volevo cercare di rendere l'idea con un piccolo esempio, non focalizzarmi su quello. In una situazione reale qulle operazioni A-B-C-D magari al loro interno sono molto piu' complesse e a loro volta sono suddivise in sotto-operazioni, anch'esse da eseguirsi in sequenza una dopo l'altra. E' innegabile quindi che non e' possibile fare in modo che qualunque metodo in caso di errore lanci un'eccezione piuttosto che gestire localmente l'errore. Questo puo' essere corretto solo per pochi casi isolati, ma in genere, questo sistema di "scarica barile" sul chiamante prima o poi deve terminare con un try-catch piu' generale, e se tutti i metodi fanno lo scarica barile... l'unico try-catch che gestisce tutto e' sul main... e quello poverino non ne sa piu' nulla dell'errore e non fa altro che dire "HEY.. ERRORE!". E tanti saluti. |
![]() |
![]() |
![]() |
#9 | |
Senior Member
Iscritto dal: Oct 2006
Città: Roma
Messaggi: 1383
|
scusate, non leggo il thread ma do ugualmente la mia risposta.
Quote:
Codice:
if (inizializzazione di A riuscita con successo) { if (inizializzazione di B riuscita con successo) { if (inizializzazione di C riuscita con successo) { esegui operazione che usa A, B e C; cleanup di C; } else { errore relativo a C; } cleanup di B; } else { errore relativo a B; } cleanup di A; } else { errore relativo ad A; } Codice:
class A { public: A() { if (inizializzazione di A fallisce) { throw false; } } ~A() { cleanup di A; } }; class B { public: B() { if (inizializzazione di B fallisce) { throw false; } } ~B() { cleanup di B; } }; class C { public: C() { if (inizializzazione di C fallisce) { throw false; } } ~C() { cleanup di C; } }; { A a; B b; C c; esegui operazione che usa a, b e c; } |
|
![]() |
![]() |
![]() |
#10 | |
Senior Member
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
|
Quote:
Quello è uno dei casi da te proposti ![]() Poi dipende sempre cosa si intende per "situazione eccezionale". Secondo me le eccezioni vanno usate in tutti i casi in cui la mancata esecuzione di una operazione abbia implicazioni nel proseguimento di una sequenza di operazioni che si suppone il chiamante esegua. L'esempio della connessione al database è proprio una di queste. Altrimenti uso la normale gestione degli errori con il ritorno di boolean. Comunque le possibilità possono essere molteplici, dipende molto anche da chi programma le varie operazioni. Se lo facessi io penserei ad una classe con le varie operazioni di ripulitura... Che so, una cosa del genere: Codice:
UndoInfo undo; bool doSomething(...., UndoInfo &undo) { if(!x.a(..., undo)) { return false; } if(!x.b(..., undo)) { return false; } if(!x.c(..., undo)) { return false; } return x.d(..., undo)); } UndoInfo undo; if(!doSomething(...., undo)) { undo.cleanUp(); } Codice:
bool doSomething(...., UndoInfo &undo) { return x.a(..., undo) && (x.b(..., undo) && x.c(..., undo) && x.d(..., undo)); } - per linguaggi interpretati si aggiungeranno stringhe che poi verranno interpretate con la relativa funzione eval - per altri linguaggi si dovrà implementare un'interfaccia ed inserire il method object all'interno di UndoInfo. UndoInfo::cleanUp non dovrà fare altro che eseguire in ordine inverso rispetto all'inserimento le varie operazioni di clean up Ultima modifica di cionci : 18-03-2010 alle 14:37. |
|
![]() |
![]() |
![]() |
#11 | |
Senior Member
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
|
Quote:
- i costruttori si debuggano peggio - mi piace vedere invocato un metodo che descriva l'esatta operazione che sto facendo |
|
![]() |
![]() |
![]() |
#12 | |
Senior Member
Iscritto dal: Oct 2006
Città: Roma
Messaggi: 1383
|
perché? io non ho mai incontrato simili difficoltá, se faccio step into il debugger mi entra regolarmente nel costruttore prima di proseguire e se metto un breakpoint nel costruttore il debugger mi ci si ferma regolarmente.
edit - parlo del debugger di Visual C++ ![]() Quote:
anzi, di un linguaggio mi piace sfruttare tutti i meccanismi. |
|
![]() |
![]() |
![]() |
#13 | ||
Senior Member
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
|
Quote:
Succede a volte, ma non voglio rischiare... Quote:
Di esempi ce ne sono a bizzeffe, ma anche se si va a vedere il pattern Method Object (cioè un oggetto con un solo metodo), l'implementazione non è mai nel costruttore, ma anche per ovvi motivi: non potrei passare l'oggetto per valore perché eseguirei due volte l'operazione, oppure eseguirei l'operazione quando non mi serve. Ultima modifica di cionci : 18-03-2010 alle 17:56. |
||
![]() |
![]() |
![]() |
#14 | |
Senior Member
Iscritto dal: Oct 2005
Messaggi: 3306
|
Quote:
Ovviamente non sono io a dirlo ma un certo Herb Sutter http://www.gotw.ca/gotw/066.htm di cui consiglio: Exceptional C++ e More Exceptional C++. Il primo risultato è che non verrà mai chiamato il distruttore in quanto l'oggetto ha vita solo alla fine del costruttore ma se questo solleva un'eccezione non termina correttamente. Un costruttore che solleva eccezioni ti porta ad usare i function try block in altre classi. I function try block sono obbligati a risollevare un'eccezione quindi ciò comporta che rischi di non portare a termine la costruzione di un'intera collezione di oggetti, nell'esempio tutte le classi che abbiano come variabile d'istanza un oggetto di tipo A o B o C e così ricorsivamente. Ovvero rischi di ritrovarti al catch nel main: Codice:
class ThrowChild { public: ThrowChild() { throw exception("faccio terminare il programma"); } }; class NoThrowChild { public: NoThrowChild() try :tc(){}catch(...){throw exception("Houston abbiamo un problema"); } private: ThrowChild tc; }; class MainNoThrow { public: MainNoThrow() {} private: NoThrowChild ntc; }; int main(int argc, char *argv[]) { try { MainNoThrow nt; } catch(const exception & ex) { cout << ex.what() << endl; exit(-1); } catch(...) { exit(-1); } return 0; } |
|
![]() |
![]() |
![]() |
#15 |
Senior Member
Iscritto dal: Nov 2004
Città: Tra Verona e Mantova
Messaggi: 4553
|
Caspita, 'sto tizio è proprio stupefatto da C++. Il terzo volume come l'ha chiamato? C++ ecceziunale veramente?
__________________
Uilliam Scecspir ti fa un baffo? Gioffri Cioser era uno straccione? E allora blogga anche tu, in inglese come me! |
![]() |
![]() |
![]() |
#16 |
Senior Member
Iscritto dal: May 2008
Messaggi: 533
|
■
Ultima modifica di rеpne scasb : 18-06-2012 alle 15:59. |
![]() |
![]() |
![]() |
#17 |
Senior Member
Iscritto dal: Oct 2005
Messaggi: 3306
|
|
![]() |
![]() |
![]() |
#18 |
Senior Member
Iscritto dal: May 2001
Messaggi: 12815
|
Io ripeto secondo me che il problema è "a monte", ovvero verificare effettivamente cosa si possa definire "eccezione", e soprattutto se è il caso di fare un certo tipo di controlli per quelle parti di codice il cui comportamento è ben determinato.
|
![]() |
![]() |
![]() |
#19 |
Senior Member
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
|
|
![]() |
![]() |
![]() |
#20 |
Member
Iscritto dal: Jun 2006
Messaggi: 117
|
In effetti le soluzioni sono molte per il semplice fatto ci sono infinte possibilità di complicare le cose. Quello che mi sembra strano è che non ci sia fondamentalmente una best practice per lo meno ufficiosa (se non ufficiale).
La gestione degli errori tramite eccezioni a mio parere e' utile solo in funzioni di libreria abbastanza generiche. Secondo me infatti il modello delle eccezioni e' sostanzialmente uno "scarica barile" sul chiamante. E prima o poi qualcuno il barile deve per forza sobbarcarselo, cioe' qualche metodo-funzione deve prima o poi fare una catch e gestire l'errore. E quello a quel punto manda a farsi friggere il modello stesso della gestione degli errori tramite eccezioni. Infatti quel metodo in caso di errore, invece di lanciare un'eccezione al chiamante a sua volta (come tutti gli altri metodi) deve gestire l'errore localmente nella calusola catch, sia che si tratti di errori generati da eccezioni lanciate da funzioni all'interno del corpo del metodo in questione, sia che si tratti di errori propri del metodo. A quel punto al questione si sposta su "scegliere il metodo che gestisce l'errore". E' questo e' difficile, tanto difficile che alla fine ci si riduce a mettere il try-catch nel main. E alla fine nel main dell'errore non se ne sa una mazza ormai, perche' magari generato 10 livelli piu' sotto. L'unica cosa che si puo' fare e' mostrare un messaggio di errore con tutta lo stack trace! |
![]() |
![]() |
![]() |
Strumenti | |
|
|
Tutti gli orari sono GMT +1. Ora sono le: 00:06.