Cleaner Code – Guard Clauses

Morris M. | Softwareentwickler
10/2024

Best Practices: Guard Clauses für sauberen und lesbaren Code

Bei vielen Gelegenheiten im täglichen Programmieren läuft man Gefahr, die Lesbarkeit und Wartbarkeit des Codes deutlich zu verschlechtern, indem man unachtsam mit Verschachtelungen durch If-Anweisungen umgeht.

Schauen wir uns dazu ein Beispiel an:

Die folgende Methode soll eine Pizza-Bestellung anhand eines Parameter-Objekts verarbeiten. Natürlich wird die Bestellung nur verarbeitet, wenn die Parameter plausibel sind.

Parameter-Objekt:

1public class BestellungParams
2{
3    List<string> PizzaSorten;
4    string Adresse;
5    string Name;
6    double Preis;
7}

Grund-Bestellmethode:

1void BestellePizza(BestellungParams bestellung)
2{
3    foreach(var sorte in bestellung.PizzaSorten)
4    {
5        backePizza(sorte);
6    }
7
8    informiereFahrer(bestellung.Adresse, bestellung.Name);
9
10    bezahle(bestellung.Preis);
11}

Soweit so gut. Aber wir wollen natürlich nur solche Eingaben verarbeiten, bei denen auch tatsächlich etwas bestellt wird.

Überprüfen wir dies also, bevor wir mit dem Backen beginnen:

1void BestellePizza(BestellungParams bestellung)
2{
3    if (bestellung.PizzaSorten.Count > 0)
4    {
5        foreach (var sorte in bestellung.PizzaSorten)
6        {
7            backePizza(sorte);
8        }
9    }
10
11    informiereFahrer(bestellung.Adresse, bestellung.Name);
12
13    bezahle(bestellung.Preis);
14}

Nun fällt uns jedoch auf, dass wir auch die Fahrer nicht unnötig alarmieren sollten, wenn es keine Lieferung gibt. Ebenso sollten wir kein Geld abbuchen, wenn keine Bestellung erfolgt.

Erweitern wir also unsere Abfragen für die weiteren Schritte:

1void BestellePizza(BestellungParams bestellung)
2{
3    if (bestellung.PizzaSorten.Count > 0)
4    {
5        foreach (var sorte in bestellung.PizzaSorten)
6        {
7            backePizza(sorte);
8        }
9
10        informiereFahrer(bestellung.Adresse, bestellung.Name);
11
12        bezahle(bestellung.Preis);
13    }
14}

Besser. Aber wenn keine Adresse vorliegt, können wir nicht liefern, und die Bestellung sollte nicht weiterverarbeitet werden.

Ebenso sollte kein Geld für Bestellungen abgebucht werden, denen kein Preis zugewiesen wurde:

1void BestellePizza(BestellungParams bestellung)
2{
3    if (bestellung.PizzaSorten.Count > 0)
4    {
5        if (!string.IsNullOrEmpty(bestellung.Name) && !string.IsNullOrEmpty(bestellung.Adresse))
6        {
7            if (bestellung.Preis > 0)
8            {
9                foreach (var sorte in bestellung.PizzaSorten)
10                {
11                    backePizza(sorte);
12                }
13
14                informiereFahrer(bestellung.Adresse, bestellung.Name);
15
16                bezahle(bestellung.Preis);
17            }
18        }
19    }
20}

Wie Sie sehen, hat die Vielzahl an Verschachtelungen unseren Code erheblich schwerer lesbar gemacht. Wir könnten dies beheben, indem wir alle Bedingungen in eine einzige Abfrage zusammenfassen, aber das würde die individuelle Behandlung von Fehlern verhindern.

Wenn wir für jeden ungültigen Parameter eine eigene Exception auslösen möchten, müssen wir die Funktion noch weiter verschachteln:

1void BestellePizza(BestellungParams bestellung)
2{
3    if (bestellung.PizzaSorten.Count > 0)
4    {
5        if (!string.IsNullOrEmpty(bestellung.Name) && !string.IsNullOrEmpty(bestellung.Adresse))
6        {
7            if (bestellung.Preis > 0)
8            {
9                foreach (var sorte in bestellung.PizzaSorten)
10                {
11                    backePizza(sorte);
12                }
13
14                informiereFahrer(bestellung.Adresse, bestellung.Name);
15
16                bezahle(bestellung.Preis);
17            }
18            else
19            {
20                throw new Exception("Kein Preis angegeben");
21            }
22        }
23        else
24        {
25            throw new Exception("Kein Name oder Adresse angegeben");
26        }
27    }
28    else
29    {
30        throw new Exception("Keine Pizza Sorten angegeben");
31    }
32}

Zum Glück gibt es eine einfache Möglichkeit, die Komplexität dieser Methode drastisch zu reduzieren. Indem wir die If-Bedingungen invertieren und die Methode bei Nichterfüllen der Bedingung sofort verlassen, können wir den produktiven Code ausführen, ohne ihn zu verschachteln.

Anstatt also die Funktion bei Vorhandensein von Pizzasorten auszuführen, verlassen wir die Methode, wenn keine Pizzasorten vorhanden sind:

1void BestellePizza(BestellungParams bestellung)
2{
3    if (bestellung.PizzaSorten.Count == 0)
4    {
5        throw new Exception("Keine Pizza Sorten angegeben");
6    }
7
8    if (!string.IsNullOrEmpty(bestellung.Name) && !string.IsNullOrEmpty(bestellung.Adresse))
9    {
10        if (bestellung.Preis > 0)
11        {
12            foreach (var sorte in bestellung.PizzaSorten)
13            {
14                backePizza(sorte);
15            }
16
17            informiereFahrer(bestellung.Adresse, bestellung.Name);
18
19            bezahle(bestellung.Preis);
20        }
21        else
22        {
23            throw new Exception("Kein Preis angegeben");
24        }
25    }
26    else
27    {
28        throw new Exception("Kein Name oder Adresse angegeben");
29    }
30}

Schon haben wir eine Ebene der Verschachtelung vermieden.

Diese Technik können wir nun auf alle Validierungen anwenden:

1void BestellePizza(BestellungParams bestellung)
2{
3    if (bestellung.PizzaSorten.Count == 0)
4        throw new Exception("Keine Pizza Sorten angegeben");
5
6    if (string.IsNullOrEmpty(bestellung.Name) || string.IsNullOrEmpty(bestellung.Adresse))
7        throw new Exception("Kein Name oder Adresse angegeben");
8
9    if (bestellung.Preis == 0)
10        throw new Exception("Kein Preis angegeben");
11
12    foreach (var sorte in bestellung.PizzaSorten)
13    {
14        backePizza(sorte);
15    }
16
17    informiereFahrer(bestellung.Adresse, bestellung.Name);
18
19    bezahle(bestellung.Preis);
20}

Viel besser! Es wird deutlich, dass dieses Verhalten die Funktion auf natürliche Weise in zwei Hälften geteilt hat.

Die Validierung könnte nun in eine eigene Funktion ausgelagert werden, aber dies ist das Thema für einen weiteren Artikel.

Morris M. | Softwareentwickler
Zurück zur Übersicht

Gemeinsam Großes schaffen

Wir freuen uns auf ein kostenloses Erstgespräch mit Ihnen!
Unser Geschäftsführer Tibor Csizmadia und unser Kundenbetreuer Jens Walter stehen Ihnen persönlich zur Verfügung. Profitieren Sie von unserer langjährigen Erfahrung und erhalten Sie eine kompetente Erstberatung in einem unverbindlichen Austausch.
Foto von Tibor

Tibor Csizmadia

Geschäftsführer
Foto von Jens

Jens Walter

Projektmanager
Devware GmbH verpflichtet sich, Ihre Privatsphäre zu schützen. Wir benötigen Ihre Kontaktinformationen, um Sie bezüglich unserer Produkte und Dienstleistungen zu kontaktieren. Mit Klick auf Absenden geben Sie sich damit einverstanden. Weitere Informationen finden Sie unter Datenschutz.
Vielen Dank für Ihre Nachricht!

Wir haben Ihre Anfrage erhalten und melden uns in Kürze bei Ihnen.

Falls Sie in der Zwischenzeit Fragen haben, können Sie uns jederzeit unter Kontaktanfrage@devware.de erreichen.

Wir freuen uns auf die Zusammenarbeit!
Oops! Something went wrong while submitting the form.
KontaktImpressumDatenschutz