Basics – Funktionen

Hallo Spaß-Coder,

im Artikel Basics? Kenn’ ich doch schon! haben wir begründet, warum grundlegende Programmierpraktiken sinnvoll und hilfreich sind.

Einige dieser Basics betrifft insbesondere Funktionen in unserer Programmierung. Darüber möchten wir im folgenden ein wenig sprechen.

 

Regeln für Funktionen

Frei nach Uncle Bob (Robert C. Martin) – es gibt zwei Regeln zu Funktionen:

  1. Funktionen sind klein!
  2. Funktionen sind noch kleiner!!

Eine Funktion – oder in der Objektorientierung eine Methode – sind sehr kleine und überschaubare Code-Fragmente. Habt ihr schonmal eine Methode (von euch selbst oder anderen) gelesen, die über mehrere Bildschimseiten geht? Methoden dieser Größe lassen sich schlecht erfassen, man braucht lange um sie zu verstehen. Gute Methoden hingegen sind schnell zu lesen und leicht zu verstehen. Ein typischer Programmierer verbringt in der Regel 80% seiner Zeit damit, Code zu lesen. Dieser sollte also leicht verständlich und schnell erfassbar sein.

 

Benennung

Ein weiterer Punkt, der gute Funktionen ausmachen ist, sie erfüllen eine und nur genau eine Aufgabe. Wird dieses Prinzip beachtet, lässt sich eine Funktion viel leichter in einem Unit Test prüfen. Sie wird dadurch auch besser wiederverwendbar.

Was eine Funktion oder Methode genau macht, kann man am aussagekräftigen Namen ablesen. Das Thema haben wir im entsprechenden Artikel bereits ausführlich besprochen.

 

Einfachheit

Ein weiteres Problem von großen Methoden ist, dass diese oft eine hohe Verschachtelungstiefe haben. Viele if, for, switch oder ähnliche Anweisungen erschweren die  Lesbarkeit (in der statischen Code-Analyse wird dies durch die Kennzahl der „Cyclomatic Complexity“ ausgedrückt).  An jedem Entscheidungspunkt im Code halten wir kurz inne und versuchen ihn zu verstehen. Je weniger wir davon ineinander verschachtelt lesen und verstehen müssen, desto leichter fällt es uns und desto schneller verstehen wir den Code. Ein Code-Smell zur Erkennung ist die Einrückungstiefe bei sauberer Formatierung des Codes. Diese sollte bei guten Funktionen höchstens 2 sein, besser wäre nur eine Ebene oder gar ganz ohne Verschachtelungen auszukommen.

Damit Funktionen auch aus Sicht des Aufrufers einfach bleiben, sollten sie möglichst wenige Parameter haben. Denkbar sind hier 0, 1 oder maximal 2. Funktionen mit 3 Parametern oder mehr sollten wohlüberlegt sein und ihr solltet möglichst alles dafür tun, dies zu vermeiden.

Mit jedem Parameter einer Funktion fügen wir ihr eine Komplexitätsstufe hinzu, die der Aufrufer unter Umständen gar nicht zu wissen braucht. Wenn wir in Klassen dem Prinzip der einen Verantwortung folgen (Single Responsibility Principle), so ist es in manchen Fällen sinnvoller, der Klasse eine globale Klassenvariable zu geben, statt diese per Parameter an jede einzelne Methode übergeben zu lassen.

Es sollte niemals ein Parameter vom Typ bool oder boolean verwendet werden. Warum? Erinnert ihr euch noch an den Punkt „Eine Funktion hat genau eine Aufgabe“? Was werden wir mit einem boolchen Parameter wohl anstellen? Wahrscheinlich folgendes:

Methoden mit boolchen Parametern erfüllen also in aller Regel mehr als eine Aufgabe.

Das gleiche Prinzip gilt auch für die Bedeutung eines Parameters mit dem Wert null. Wenn wir folgenden Methodencode haben, erfüllt die Funktion ebenfalls mehr als eine Aufgabe:

Eine Prüfung der eingehenden Parameter auf null ist demgegenüber allerdings gültig:

Streng betrachtet erfüllt diese Methode zwar zwei Aufgaben (1. Prüfen der Parameter, 2. „was machen“), halten wir uns aber an diese Struktur – erst prüfen, dann den eigentlichen Code ausführen – ist die Methode weiterhin gut lesbar.

In .Net sollten wir zudem darauf achten, keine out-Parameter zu verwenden. Ein Beispiel:

Der Aufrufer der Methode muss nun zwei Werte verarbeiten: 1. den Rückgabewert (bool) und den eigentlichen, von der Methode ermittelten Wert (customer). Besser wäre es hier, direkt den Customer zurückzugeben und den Aufrufer auf null prüfen zu lassen (wie es etwa die Methode GetCustomerById(int customerId) macht, die hier aufgerufen wird). Alternativ können wir den Umstand des nicht gefundenen Werts als Ausnahme betrachten und eine entsprechende Exception werfen:

 

 Überraschung!

Was fällt euch beim folgenden Code auf?

Denkt mal kurz darüber nach, was die Methode macht……….

Sie setzt einen „aktiven Kunden“ in einer Klasse. Das sagt zumindest der Name der Methode aus. Tatsächlich wird aber nicht nur der aktive Kunde auf den übergebenen Wert festgelegt, sondern dieser übergebene Wert auch in eine Kundenliste eingefügt.

Wollten wir das, wenn wir die Methode SetActiveCustomer aufrufen? Wahrscheinlich nicht. Dieses Verhalten ist für uns eine Überraschung…und Überraschungen können wir nicht leiden (wenigstens nicht im Code). Wenn wir später auf diese customerList zugreifen, sind dort plötzlich Werte enthalten, die wir dort gar nicht erwarten, weil wir sie nicht hinzugefügt haben. Zumindest nicht wissentlich.

Methoden sollten immer nur genau das tun, was ihr Name sagt. Sie sollten keine Nebeneffekte haben. Wenn der Aufrufer diesen Wert auch in diese Liste einfügen will, dann soll er das explizit tun.

Zusammenfassung

Der Artikel ist ganz schön lang geworden. Bei Funktionen gibt es für uns so einiges zu beachten. Zunächst sollen sie klein sein. Sehr klein. Noch kleiner. Funktionen können gar nicht klein genug sein. Dann sollten sie einen sprechenden Namen haben und genau die Aufgabe ausführen, die in diesem Namen angegeben ist. Und nur genau diese eine Aufgabe. Keine weiteren Aufgaben, keine überraschenden Aktivitäten.

Wenn wir dies beherzigen, werden unsere Methoden schon übersichtlich, unser Code sprechend und viel leichter zu lesen und zu debuggen.

Viel Spaß beim Anwenden,

Eure Spaß-Coder

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.