Πράγματι, δεν επιτρέπεται να βγεις από ένα Finally με Return ή GoTo. Αν το σκεφτείς, το finally υπάρχει μόνο και μόνο για να καθαρίσεις τυχόν αλλαγές που έχεις κάνει ακόμα και αν έχει εμφανιστεί exception. Αν σου επιτρεπόταν να βγεις με return ή GoTo, ακόμα και σε περίπτωση που έχει ήδη εμφανιστεί exception, θα μπορούσες να γράψεις εξαιρετικά επικίνδυνο κώδικα. Όπως σου λέει και ο Σωτήρης, μπορείς πολύ απλά να εκτελέσεις τη Return μετά το End Try και να ξεφορτωθείς την Finally.
Ακόμα και έτσι όμως, αυτό που προσπαθείς να κάνεις είναι εξαιρετικά επικίνδυνο. Είναι πολύ κακή ιδέα να κουκουλώνεις ένα exception και να επιστρέφεις true/false. Πολύ απλά, αυτός που καλεί το function σου μπορεί να ξεχάσει να ελέγξει το αποτέλεσμα και να συνεχίσει να δουλεύει χρησιμοποιώντας π.χ. λανθασμένα δεδομένα, ή να προσπαθήσει να χρησιμοποιήσει κάποιο πίνακα ο οποίος είναι άδειος.
Είναι σαν να "διορθώνεις" μία καμένη ασφάλεια τυλίγοντας την με ... αλουμινόχαρτο. Έχω δει χρηματιστηριακή εφαρμογή που χρησιμοποιούσε τέτοια κολπάκια. Κάποια στιγμή, άρχισαν να εμφανίζονται μηδενικά υπόλοιπα σε κάποιους λογαριασμούς. Ψάχνοντας, βρήκα ότι η συνάρτηση που επέστρεφε τις ισοτιμίες νομισμάτων σε κάποιες ημερομηνίες επέστρεφε 0. Βλέπεις, όποιος έφτιαξε τη συνάρτηση ξέχασε να ελέγξει τί του επέστρεφε η αντίστοιχη συνάρτηση του data layer η οποία ... επέστρεφε false σε περίπτωση λάθους.
Ο σωστός τρόπος να χειρίζεσαι σφάλματα και exceptions είναι να τα χειρίζεσαι αν μπορείς, αλλιώς να τα αφήσεις να προχωρήσουν στις παραπάνω συναρτήσεις, μέχρι να βρεθεί κάποια συνάρτηση που θα μπορέσει να τα χειριστεί. Φαντάσου, για παράδειγμα, ότι έχεις τις παρακάτω συναρτήσεις:
void TransferMoney(Account a, Account b,decimal amount)
{
Widthraw(a,amount);
Deposit(b,amount);
}
void Withdraw(Account a, decimal amount)
{
}
void Deposit(Account a, decimal amount)
{
}
Για λόγους ευκολίας, ας υποθέσουμε ότι μόνο η Deposit μπορεί να εμφανίσει exception. Φαντάσου ότι κάποιο exception εμφανίζεται μέσα στην Deposit. Μπορεί η Deposit να αντιμετωπίσει την κατάσταση? Μάλλον όχι. Μπορεί μεν να ακυρώσει την κατάθεση των χρημάτων στο λογαριασμό Β αλλά δεν μπορεί να ακυρώσει την ανάληψη από το λογαριασμό Α. Συνεπώς, θα πρέπει το exception να πιαστεί μέσα στην TransferMoney για να ακυρωθεί η ανάληψη από τον Α. Και επειδή η ακύρωση μίας μεταφοράς χρημάτων είναι σημαντικό πρόβλημα, θα πρέπει να ξαναριχτεί το exception για να ειδοποιηθεί όποιος ξεκίνησε τη συναλλαγή ότι κάτι πήγε στραβά. Θα πρέπει δηλαδή να γραφτεί κάτι σαν αυτό:
void TransferMoney(Account a, Account b,decimal amount)
{
try
{
Widthraw(a,amount);
Deposit(b,amount);
catch(Exception ex)
{
Deposit(a,amount);
throw;
}
}
void Withdraw(Account a, decimal amount)
{
}
void Deposit(Account a, decimal amount)
{
try
{
....
}
catch(Exception ex)
{
//Cancel Deposit
throw;
}
}
Παναγιώτης Καναβός, Freelancer
Twitter: http://www.twitter.com/pkanavos