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.
Die folgende Methode soll eine Pizza-Bestellung anhand eines Parameter-Objekts verarbeiten. Natürlich wird die Bestellung nur verarbeitet, wenn die Parameter plausibel sind.
1public class BestellungParams
2{
3 List<string> PizzaSorten;
4 string Adresse;
5 string Name;
6 double Preis;
7}
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.
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.
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.
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.
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.
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.
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.