Στον ιδανικό κόσμο, όλα λειτουργούν τέλεια. Αλλά στον πραγματικό κόσμο, τα πράγματα μπορεί να πάνε στραβά, και ο τρόπος που χειριζόμαστε αυτές τις καταστάσεις έχει σημασία, ώστε το λογισμικό μας να είναι ανθεκτικό και αξιόπιστο. Οι εξαιρέσεις είναι μια κρίσιμη έννοια στον προγραμματισμό που μας επιτρέπει να χειριζόμαστε σφάλματα και απρόβλεπτες καταστάσεις με κομψό τρόπο.
Το πέταγμα ενός σφάλματος, αν δεν αντιμετωπιστεί, σταματάει το πρόγραμμα και εμφανίζει ένα μήνυμα σφάλματος. Στις περισσότερες περιπτώσεις, δεν θέλεις το πρόγραμμά σου να σταματήσει όταν προκύψει ένα σφάλμα, αλλά θέλεις να χειριστείς το σφάλμα και να συνεχίσεις την εκτέλεση του προγράμματος.
Στην Crystal, πετάς εξαιρέσεις με τη λέξη-κλειδί raise, και μπορείς να της δώσεις είτε μια String είτε ένα αντικείμενο Exception.
Αν δεν αντιμετωπιστεί, το πρόγραμμα θα σταματήσει και θα εκτυπώσει το μήνυμα σφάλματος.
raise "This is an error"
Υπάρχουν αρκετές ενσωματωμένες εξαιρέσεις στην Crystal, όπως οι ArgumentError, IndexError, KeyError, IOError, SystemCallError, TypeError, ZeroDivisionError και πολλές άλλες.
Αυτές απαιτούν να περάσεις ένα μήνυμα στην εξαίρεση.
raise ArgumentError.new("This is an argument error")
Δεν θα θέλαμε το πρόγραμμά μας να καταρρεύσει όταν πετάγεται μια εξαίρεση.
Επομένως, όταν γνωρίζουμε ότι ένα κομμάτι κώδικα είναι επιρρεπές σε σφάλματα, μπορούμε να το τυλίξουμε σε ένα μπλοκ begin και να πιάσουμε την εξαίρεση με ένα μπλοκ rescue.
Το μπλοκ begin σηματοδοτεί την αρχή του κώδικα που μπορεί να πετάξει μια εξαίρεση, και το μπλοκ rescue χειρίζεται την εξαίρεση.
begin
raise "This is an error"
rescue
puts "An error occurred!"
end
Το μπλοκ rescue μπορεί επίσης να καθοριστεί με μια μεταβλητή, για να πάρεις το αντικείμενο της εξαίρεσης.
begin
raise "This is an error"
rescue ex
puts "An error occurred: #{ex.message}"
end
Το μπλοκ rescue μπορεί επίσης να καθοριστεί με έναν συγκεκριμένο τύπο εξαίρεσης, ώστε να πιάνει μόνο αυτή την εξαίρεση.
begin
raise ArgumentError.new("This is an argument error")
rescue ArgumentError
puts "An argument error occurred!"
end
# or
begin
raise ArgumentError.new("This is an argument error")
rescue ex : ArgumentError
puts "An argument error occurred: #{ex.message}"
end
Μπορείς να χρησιμοποιήσεις πολλά μπλοκ rescue για να χειριστείς διαφορετικούς τύπους εξαιρέσεων.
Στο παρακάτω παράδειγμα, το πρώτο μπλοκ rescue θα πιάσει ένα ArgumentError, και το δεύτερο μπλοκ rescue θα πιάσει οποιαδήποτε άλλη εξαίρεση.
begin
raise ArgumentError.new("This is an argument error")
rescue ArgumentError
puts "An argument error occurred!"
rescue
puts "An error occurred!"
end
Το μπλοκ begin μπορεί επίσης να έχει ένα μπλοκ else, το οποίο εκτελείται αν δεν πεταχτεί καμία εξαίρεση.
begin
puts "No error occurred"
rescue
puts "An error occurred!"
else
puts "No error occurred"
end
Τέλος, υπάρχει ένα μπλοκ ensure που εκτελείται πάντα, ανεξάρτητα από το αν πετάχτηκε μια εξαίρεση.
begin
raise "This is an error"
rescue
puts "An error occurred!"
ensure
puts "This is always executed"
end
Κάποιες μέθοδοι έχουν δύο εκδοχές, μία με ! και μία χωρίς.
Αυτό μπορεί να σημαίνει δύο διαφορετικά πράγματα.
Το ένα είναι ότι η μέθοδος μεταλλάσσει το αντικείμενο, και το άλλο είναι ότι η μέθοδος μπορεί να πετάξει μια εξαίρεση.
Αλλά υπάρχει και μια άλλη σύμβαση σχετικά με το να τελειώνει μια μέθοδος με ?, που αναφέρεται στην έννοια Boolean.
Κάποιες μέθοδοι πετούν μια εξαίρεση από προεπιλογή, αλλά έχουν και μια εκδοχή που τελειώνει με ?, η οποία επιστρέφει nil αντί να πετάξει μια εξαίρεση.
Αυτό είναι ιδανικό όταν θέλεις να αποφύγεις το πέταγμα ενός σφάλματος. Αυτό μπορεί να ωφελήσει την απόδοση, αφού δεν χρειάζεται να δημιουργήσει ένα ίχνος στοίβας, και, αν στηθεί σωστά, θα μπορούσε να κάνει τον κώδικα πιο ασφαλή.
Μπορείς επίσης να δημιουργήσεις τις δικές σου εξαιρέσεις, κληρονομώντας από την κλάση Exception.
Κάνοντάς το, μπορείς προαιρετικά να παρακάμψεις τη μέθοδο initialize για να ορίσεις το μήνυμα της εξαίρεσης.
Αυτό μπορεί να γίνει ορίζοντας σε μια μεταβλητή στιγμιοτύπου με το όνομα @message το μήνυμα.
class MyException < Exception
def initialize
@message = "This is my exception"
end
end
raise MyException.new
Η μέρα που περίμενες τόσο καιρό ήρθε επιτέλους και τώρα είσαι ο περήφανος ιδιοκτήτης μιας όμορφης φάρμας στις Άλπεις.
Ακόμα δε σου αρέσει να ξυπνάς πολύ νωρίς το πρωί για να ταΐσεις τις αγελάδες σου.
Επειδή είσαι εξαιρετικός μηχανικός, κατασκευάζεις έναν ταϊστή, τον FEED-M-ALL.
Το τελευταίο πράγμα που απαιτείται για να ολοκληρώσεις το έργο σου είναι ένα κομμάτι κώδικα που υπολογίζει την ποσότητα ζωοτροφής που πρέπει να πάρει κάθε αγελάδα. Είναι σημαντικό κάθε αγελάδα να παίρνει την ίδια ποσότητα, πρέπει να αποφύγεις τις συγκρούσεις. Οι αγελάδες είναι πολύ ευαίσθητες.
Ευτυχώς, δε χρειάζεται να βγάλεις μόνος σου όλους τους τύπους για τον υπολογισμό των ποσοτήτων ζωοτροφής. Χρησιμοποιείς κάποια μυστηριώδη εξωτερική βιβλιοθήκη που βρήκες στο διαδίκτυο. Υποτίθεται ότι θα έχει ως αποτέλεσμα τις πιο ευτυχισμένες αγελάδες. Η βιβλιοθήκη εκθέτει μια κλάση που υλοποιεί την παρακάτω διεπαφή. Θα στηριχτείς σε αυτήν στον κώδικα που γράφεις ο ίδιος.
class FodderCalculator
def fodder_amount! : Number
end
def fattening_factor! : Number
end
end
Καθώς δουλεύεις τον κώδικά σου, θα βελτιώσεις τον χειρισμό σφαλμάτων ώστε να γίνει πιο ανθεκτικός και πιο εύκολος στο debug αργότερα, όταν τον χρησιμοποιείς στην καθημερινή ζωή της φάρμας σου.
Πρώτα απ' όλα, επικεντρώνεσαι στη συγγραφή του κώδικα που χρειάζεται για να υπολογίσεις την ποσότητα ζωοτροφής ανά αγελάδα.
Υλοποίησε μια μέθοδο TheFarm.divide_food που δέχεται ως ορίσματα ένα αντικείμενο FodderCalculator και έναν αριθμό αγελάδων ως ακέραιο.
Για αυτή την εργασία, υποθέτεις ότι ο αριθμός των αγελάδων που περνάει είναι πάντα μεγαλύτερος από το μηδέν.
Η μέθοδος πρέπει να επιστρέφει την ποσότητα τροφής ανά αγελάδα.
Για να κάνεις τον υπολογισμό, πρώτα πρέπει να ανακτήσεις τη συνολική ποσότητα ζωοτροφής για όλες τις αγελάδες.
Αυτό γίνεται καλώντας τη μέθοδο fodder_amount! και περνώντας τον αριθμό των αγελάδων.
Επιπλέον, χρειάζεσαι έναν συντελεστή με τον οποίο πρέπει να πολλαπλασιαστεί αυτή η ποσότητα.
Αυτόν τον συντελεστή τον παίρνεις καλώντας τη μέθοδο fattening_factor!.
Με αυτές τις δύο τιμές και τον αριθμό των αγελάδων, μπορείς τώρα να υπολογίσεις την ποσότητα τροφής ανά αγελάδα (ως float64).
Αν μία από τις μεθόδους που καλείς επιστρέφει σφάλμα, η εκτέλεση πρέπει να σταματάει και να επιστρέφει 0.
fodder_calculator = FodderCalculator.new(50, 1.5)
TheFarm.divide_food(fodder_calculator, 5)
# => 15.0
fodder_calculator = FodderCalculator.new(50, nil)
TheFarm.divide_food(fodder_calculator, 5)
# => 0
Ενώ δούλευες στην πρώτη εργασία παραπάνω, συνειδητοποίησες ότι η εξωτερική βιβλιοθήκη που χρησιμοποιείς δεν είναι τόσο υψηλής ποιότητας όσο νόμιζες. Για παράδειγμα, δε μπορεί να χειριστεί σωστά μη έγκυρες εισόδους. Θέλεις να παρακάμψεις αυτόν τον περιορισμό προσθέτοντας έναν έλεγχο για την τιμή εισόδου στον κώδικά σου.
Γράψε μια συνάρτηση TheFarm.validate_input_and_divide_food που έχει την ίδια υπογραφή με τη TheFarm.divide_food παραπάνω.
TheFarm.divide_food και να επιστρέφει τα αποτελέσματα αυτής της κλήσης."Number of cows must be greater than 0".TheFarm.validate_input_and_divide_food(fodderCalculator, 5)
# => 15
TheFarm.validate_input_and_divide_food(fodderCalculator, -2)
# Error: Number of cows must be greater than 0
Ο έλεγχος του αριθμού των αγελάδων πριν τον περάσεις παρακάτω ήταν καλή κίνηση, αλλά δεν είσαι και τόσο ευχαριστημένος με το ασαφές μήνυμα σφάλματος.
Αποφασίζεις να τα κάνεις καλύτερα δημιουργώντας μια προσαρμοσμένη κλάση σφάλματος με το όνομα InvalidCowsError.
Το προσαρμοσμένο σφάλμα πρέπει να κρατάει τον αριθμό των αγελάδων (Int32) και ένα προσαρμοσμένο μήνυμα (String) και η μέθοδος Expection πρέπει να σειριοποιεί τα δεδομένα στην παρακάτω μορφή:
{number of cows} cows are invalid: {custom message}
Οπλισμένος με το προσαρμοσμένο σφάλμα σου, υλοποίησε μια συνάρτηση TheFarm.validate_number_of_cows που δέχεται τον αριθμό των αγελάδων ως ακέραιο και επιστρέφει ένα σφάλμα (ή nil).
InvalidCowsError με το προσαρμοσμένο μήνυμα ορισμένο σε "there are no negative cows".InvalidCowsError με το προσαρμοσμένο μήνυμα ορισμένο σε "no cows don't need food".nil για να δείξει ότι ο έλεγχος ήταν επιτυχής.TheFarm.validate_number_of_cows(5)
# => nil
TheFarm.validate_number_of_cows(-5)
# => InvalidCowsError: -5 cows are invalid: there are no negative cows
Μετά τη σκληρή δουλειά της δημιουργίας αυτής της συνάρτησης ελέγχου, παρατηρείς ότι έχει ήδη βραδιάσει και αφήνεις το γραφείο σου για να απολαύσεις το ηλιοβασίλεμα πάνω από τα βουνά. Την προσθήκη της νέας συνάρτησης ελέγχου στον κώδικά σου την αφήνεις για άλλη μέρα.
Γράψου στο Exercism για να μάθεις και να κατακτήσεις Crystal με 26 έννοιες142 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.