Tracks
/
Crystal
Crystal
/
Übungen
/
Der Bauernhof
Der Bauernhof

Der Bauernhof

Lernübung

Einführung

Im Idealfall funktioniert alles perfekt. Aber in der realen Welt kann etwas schiefgehen, und wie wir mit diesen Situationen umgehen, ist entscheidend, damit unsere Software robust und zuverlässig ist. Ausnahmen sind ein wichtiges Konzept in der Programmierung, das es uns ermöglicht, Fehler und unerwartete Situationen elegant zu behandeln.

Wenn ein Fehler ausgelöst und nicht behandelt wird, hält das Programm an und gibt eine Fehlermeldung aus. In den meisten Fällen möchtest du nicht, dass dein Programm anhält, wenn ein Fehler auftritt. Stattdessen möchtest du den Fehler behandeln und das Programm weiterlaufen lassen.

Eine Ausnahme auslösen

In Crystal werden Ausnahmen mit dem Schlüsselwort raise ausgelöst und ihnen kann entweder ein String oder ein Exception-Objekt übergeben werden. Wenn sie nicht behandelt werden, hält das Programm an und gibt die Fehlermeldung aus.

raise "This is an error"

Es gibt mehrere eingebaute Ausnahmen in Crystal, wie ArgumentError, IndexError, KeyError, IOError, SystemCallError, TypeError, ZeroDivisionError und viele mehr. Bei diesen musst du eine Nachricht an die Ausnahme übergeben.

raise ArgumentError.new("This is an argument error")

Ausnahmen behandeln

Wir wollen nicht, dass unser Programm abstürzt, wenn eine Ausnahme ausgelöst wird. Wenn wir also wissen, dass ein Stück Code fehleranfällig ist, können wir es in einen begin-Block packen und die Ausnahme mit einem rescue-Block abfangen. Der begin-Block markiert den Anfang des Codes, der eine Ausnahme auslösen könnte, und der rescue-Block fängt die Ausnahme ab.

begin
 raise "This is an error"
rescue
 puts "An error occurred!"
end

Der rescue-Block kann auch mit einer Variablen angegeben werden, um das Ausnahmeobjekt zu erhalten.

begin
 raise "This is an error"
rescue ex
 puts "An error occurred: #{ex.message}"
end

Der rescue-Block kann auch mit einem bestimmten Ausnahmetyp angegeben werden, um nur diese Ausnahme abzufangen.

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

Es können mehrere rescue-Blöcke verwendet werden, um verschiedene Arten von Ausnahmen zu behandeln. Im folgenden Beispiel fängt der erste rescue-Block einen ArgumentError ab, und der zweite rescue-Block fängt jede andere Ausnahme ab.

begin
 raise ArgumentError.new("This is an argument error")
rescue ArgumentError
 puts "An argument error occurred!"
rescue
 puts "An error occurred!"
end

Der begin-Block kann auch einen else-Block haben, der ausgeführt wird, wenn keine Ausnahme ausgelöst wird.

begin
 puts "No error occurred"
rescue
 puts "An error occurred!"
else
 puts "No error occurred"
end

Schließlich gibt es einen ensure-Block, der immer ausgeführt wird, unabhängig davon, ob eine Ausnahme ausgelöst wurde.

begin
 raise "This is an error"
rescue
 puts "An error occurred!"
ensure
 puts "This is always executed"
end

Methodenkonvention

Manche Methoden haben zwei Versionen, eine mit ! und eine ohne. Das kann zwei verschiedene Dinge bedeuten: Entweder verändert die Methode das Objekt, oder sie kann eine Ausnahme auslösen.

Aber es gibt noch eine weitere Konvention, eine Methode mit ? enden zu lassen, die im Konzept zu booleschen Werten erwähnt wird. Manche Methoden lösen standardmäßig eine Ausnahme aus, haben aber auch eine Version, die mit ? endet und nil zurückgibt, statt eine Ausnahme auszulösen.

Das ist ideal, wenn du vermeiden willst, dass ein Fehler ausgelöst wird. Das kann die Leistung verbessern, da kein Stacktrace erstellt werden muss, und bei richtiger Einrichtung den Code sicherer machen.

Eigene Ausnahmen

du kannst auch eigene Ausnahmen erstellen, indem du von der Klasse Exception erbst. Dabei kannst du optional die Methode initialize überschreiben, um die Ausnahmenachricht festzulegen. Das kannst du tun, indem du einer Instanzvariable namens @message die Nachricht zuweist.

class MyException < Exception
 def initialize
   @message = "This is my exception"
 end
end

raise MyException.new

Anleitung

Der Tag, auf den du so lange gewartet hast, ist endlich gekommen: Du bist jetzt der stolze Besitzer einer wunderschönen Farm in den Alpen.

Es gefällt dir immer noch nicht, morgens zu früh aufzustehen, um deine Kühe zu füttern. Weil du ein ausgezeichneter Ingenieur bist, baust du einen Futterspender, den FEED-M-ALL.

Das Letzte, was noch fehlt, um dein Projekt abzuschließen, ist ein Stück Code, das die Futtermenge berechnet, die jede Kuh bekommen soll. Es ist wichtig, dass jede Kuh die gleiche Menge bekommt, du musst Konflikte vermeiden. Kühe sind sehr sensibel.

Zum Glück musst du nicht alle Formeln zur Berechnung der Futtermenge selbst herleiten. Du verwendest eine geheimnisvolle externe Bibliothek, die du im Internet gefunden hast. Sie soll für die glücklichsten Kühe sorgen. Die Bibliothek stellt eine Klasse bereit, die die folgende Schnittstelle erfüllt. Darauf wirst du dich in dem Code verlassen, den du selbst schreibst.

class FodderCalculator
  def fodder_amount! : Number 
  end

  def fattening_factor! : Number
  end
end

Während du an deinem Code arbeitest, wirst du die Fehlerbehandlung verbessern, damit sie robuster und später leichter zu debuggen ist, wenn du sie in deinem täglichen Farmleben einsetzt.

1. Verteile das Futter gleichmäßig

Zuerst konzentrierst du dich darauf, den Code zu schreiben, der benötigt wird, um die Futtermenge pro Kuh zu berechnen.

Implementiere eine Methode TheFarm.divide_food, die ein Objekt vom Typ FodderCalculator und eine Anzahl Kühe als Ganzzahl als Argumente entgegennimmt. Für diese Aufgabe gehst du davon aus, dass die übergebene Anzahl Kühe immer größer als null ist. Die Methode sollte die Futtermenge pro Kuh zurückgeben.

Um die Berechnung durchzuführen, musst du zuerst die Gesamtfuttermenge für alle Kühe abrufen. Das machst du, indem du die Methode fodder_amount! aufrufst und die Anzahl der Kühe übergibst. Außerdem brauchst du einen Faktor, mit dem diese Menge multipliziert werden muss. Diesen Faktor erhältst du, indem du die Methode fattening_factor! aufrufst. Mit diesen beiden Werten und der Anzahl der Kühe kannst du nun die Futtermenge pro Kuh berechnen (als float64).

Wenn eine der Methoden, die du aufrufst, einen Fehler zurückgibt, sollte die Ausführung stoppen und 0 zurückgeben.

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

2. Überprüfe die Anzahl der Kühe

Während du an der ersten Aufgabe oben gearbeitet hast, hast du gemerkt, dass die externe Bibliothek, die du verwendest, nicht so hochwertig ist, wie du dachtest. Zum Beispiel kann sie ungültige Eingaben nicht richtig verarbeiten. Du möchtest diese Einschränkung umgehen, indem du in deinem eigenen Code eine Prüfung des Eingabewerts hinzufügst.

Schreibe eine Funktion TheFarm.validate_input_and_divide_food, die dieselbe Signatur hat wie TheFarm.divide_food oben.

  • Wenn die übergebene Anzahl der Kühe größer als 0 ist, sollte die Funktion TheFarm.divide_food aufrufen und die Ergebnisse dieses Aufrufs zurückgeben.
  • Wenn die Anzahl der Kühe 0 oder kleiner ist, sollte die Funktion einen Fehler mit der Meldung "Number of cows must be greater than 0" auslösen.
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

3. Verbessere die Fehlerbehandlung

Die Anzahl der Kühe zu prüfen, bevor du sie weitergibst, war ein guter Schachzug, aber mit der unspezifischen Fehlermeldung bist du nicht ganz zufrieden. Du beschließt, es besser zu machen, indem du eine eigene Fehlerklasse namens InvalidCowsError erstellst.

Der eigene Fehler sollte die Anzahl der Kühe (Int32) und eine eigene Meldung (String) enthalten, und die Methode Expection sollte die Daten im folgenden Format serialisieren:

{number of cows} cows are invalid: {custom message}

Ausgestattet mit deinem eigenen Fehler, implementiere eine Funktion TheFarm.validate_number_of_cows, die die Anzahl der Kühe als Ganzzahl entgegennimmt und einen Fehler (oder nil) zurückgibt.

  • Wenn die Anzahl der Kühe kleiner als 0 ist, gibt die Funktion einen InvalidCowsError zurück, bei dem die eigene Meldung auf "there are no negative cows" gesetzt ist.
  • Wenn die Anzahl der Kühe 0 ist, gibt die Funktion einen InvalidCowsError zurück, bei dem die eigene Meldung auf "no cows don't need food" gesetzt ist.
  • Andernfalls gibt die Funktion nil zurück, um anzuzeigen, dass die Validierung erfolgreich war.
TheFarm.validate_number_of_cows(5)
# => nil

TheFarm.validate_number_of_cows(-5)
# => InvalidCowsError: -5 cows are invalid: there are no negative cows

Nach der harten Arbeit, diese Validierungsfunktion aufzusetzen, fällt dir auf, dass es bereits Abend ist, und du verlässt deinen Schreibtisch, um den Sonnenuntergang über den Bergen zu genießen. Die Aufgabe, die neue Validierungsfunktion tatsächlich in deinen Code einzubauen, verschiebst du auf einen anderen Tag.

Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab
Crystal Exercism

Bereit, mit Der Bauernhof zu starten?

Melde dich bei Exercism an, um Crystal mit 26 Konzepte142 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.