Clean Code

Das Kind braucht einen Namen

esskar über Programmierung

Klarer Code – Clean Code – ist ein fast schon ein neuer Modebegriff in der IT und Entwicklerwelt. Leider kommt die neuste Mode nicht immer und überall gleich gut an, dabei sollte es gar keine Modeerscheinung sein beim Entwickeln von Software auf klare Formen und Strukturen Wert zu legen.

Als Softwareentwickler verbringt man mehr Zeit damit, existierenden Code zu lesen – seien es Anwedungsbeispiele, Referenz Implementierungen von ähnlichen Problemen, den Code des Teamkollegen oder eben der alte eigene Code von gestern – als neuen Code zu schreiben. Das Lesen dient den Zwecken, die Funktion des Codes zu verstehen, um Fehler zu finden und zuverbessern, die Funktionalität des Codes zu erweitern (manchmal auch nur zu bestätigen) oder um neue Kenntnisse zu erlernen. Dadurch ist es extrem von Vorteil, wenn der Code so angelegt wurde, dass man ohne große Anstrengungen versteht, was da – meist schwarz auf weiß – geschrieben steht.

Dies ist der erste – von hoffentlich vielen – Artikel auf uswusf, der sich mit dem Schreiben von klarem Code befasst.

Namensgebung und -findung

Benamung ist ein wichtiger Bestandteil in der Programmierung. Ständig benamt man neue Variablen, Klassen, Strukturen, Funktionen, Methoden, uswusf. Dabei ist zu beachten, dass sinnvolle und eindeutige Namen gewählt werden. Ein Methoden- oder Funktionsname sollte so gewählt werden, dass es klar wird – ohne den Code kennen zu müssen – was beim Aufruf der Prozedur passieren wird. So erreicht man, dass ein Teil des Codes verständlich ist und bleibt ohne das man zwischen verschiedenen Teilen hin und her springen muss.

Hier einmal einige Beispiele für schlecht gewählte Namen, und wie es besser gehen könnte:

public class Info
{
   private string str;
   private List list_;

   public Info(string str)
   {
      this.str = str;
      list_ = new List();
   }

   public string Name
   {
      get { return str; }
   }

   public void add(Info info)
   {
      if(info == null)
         throw new ArgumentNullException("info");
      list_.Add(info);
   }

   public int Depth()
   {
      int d = 0;
      foreach (Info info in list_)
      {
        d = Math.Max(d, info.Depth());
      }
      return 1 + d;
   }
}

Auf den ersten Blick fallen schon einige Dinge auf: unterschiedliche Groß- und Kleinschreibung, sinnfreie Bezeichner und nichtsagende Namen.

Los geht es mit der Klasse: Klassennamen wie Info oder Data sind nicht aussagekräftig. Wenn man die Klasse nicht selbst geschrieben hat, tappt man völlig im Dunkeln, da man nicht weiß, welche Information da nun hinterlegt werden soll. Da wir aber im Moment noch nichts über die Klasse wissen, warten wir erstmal noch ab, bevor wir sie umbenennen.

Die privaten Member-Variablen str und list_ sind in ihrer Schreibweise weder konsistent noch aussagekräftig. Auch empfiehlt es sich, Member-Variablen besonders zu markieren, damit sie sich von „normalen“ Variablen abheben, und es zu weniger Namenskonflikten kommt (dazu gleich mehr). Verschiedene Quellen nutzen entweder den vorran- bzw. nachgestellten Unterstrich. Ich persönlich bevorzuge ein vorangestelltes m_ (um sie selber von statatischen Klassen-Variablen, die ich mit einem vorangestellten s_ kennzeichne, zu unterscheiden). Für welches Konzept man sich im Endeffekt entscheidet, bleibt einem selber überlassen, aber man sollte seine Entscheidung konsistent im kompletten Quellcode durchziehen und sich bei größeren Projekten im Team einigen.

Schaut man sich den Rest des Codes an, stellt man fest, dass str eigentlich den Namen des Objektes und list_ Kinder Objekte des gleichen Types speichert. Die add Methode fügt so ein Kind hinzu, gibt den Vorgang aber nicht über den Namen nach außen Preis. Außerdem passt die Kleinschreibung nicht zur restlichen Schreibweise. Wir ändern also wie folgt:

public class Info
{
   private string m_name;
   private List m_children;

   public Info(string name)
   {
      m_name = name;
      m_children = new List();
   }

   public string Name
   {
      get { return m_name; }
   }

   public void AddChild(Info child)
   {
      if(child == null)
         throw new ArgumentNullException("child");
      m_children.Add(child);
   }

   public int Depth()
   {
      int d = 0;
      foreach (Info child in m_children)
      {
        d = Math.Max(d, child.Depth());
      }
      return 1 + d;
   }
}

Durch das präfixen der Member-Variablen umgehen wir auch den Namenskonflikt im Konstruktor der Klasse und können nun das für Variablen untypische this wegfallen lassen — Schön — so langsam wird nun auch klar, dass die Klasse Info eher soetwas wie ein Knoten einer Baumstruktur darstellen könnte. Deshalb benennen wir sie auch im weiteren nach TreeNode um. Nun nehmen wir uns noch die Depth vor. Der Funktionscode bestätigt, dass Depth die Tiefe des Knotens berechnet. Diese Berechnung sollte sich auch im Namen widerspiegeln, ComputeDepth ist daher besser gewählt. Die Variable d ist nur ein Platzhalter. Natürlich, der Code der Depth bzw. ComputeDepth Funktion ist überschaubar, dennoch sollten immer aussagekräftige Namen gewählt werden. d steht in diesem Fall für die Tiefe der Kinder, sollte das auch klar und deutlich widergeben. Fassen wir also die Änderungen im folgenden Codesegment zusammen.

public class TreeNode
{
   private string m_name;
   private List m_children;

   public TreeNode(string name)
   {
      m_name = name;
      m_children = new List();
   }

   public string Name
   {
      get { return m_name; }
   }

   public void AddChild(TreeNode child)
   {
      if(child == null)
         throw new ArgumentNullException("child");
      m_children.Add(child);
   }

   public int ComputeDepth()
   {
      int depthOfChildren = 0;
      foreach (TreeNode child in m_children)
      {
        depthOfChildren = Math.Max(depthOfChildren, child.ComputeDepth());
      }
      return 1 + depthOfChildren;
   }
}

Respektiere die Sprache

Im Allgemeinen gilt: Klassen, Strukturen, Enums und Variablen stehen oft für Dinge (Person, Rectangle, Width) und sollten deswegen auch so bezeichnet werden, also class Person, struct Rectangle bzw. int depth. Dabei kann man auch zusammengesetzte Ausdrücke verwenden, wie z.B. class TeamMember, enum ProgrammingStyle oder int depthOfChildren.
Es ist wichtig die Programmiersprache der Wahl respektiert. So ist es in C++ oder Java gängige Praxis, dass man Funktions- bzw. Methodennamen klein schreibt: computeTotalWidth, createUserFactory. In C# würde man Methodennamen als ComputeTotalWidth und CreateUserFactory und in Perl etwa als compute_total_width beziehungsweise create_user_factory deklariert werden. So unterschiedlich wie der Jargon der Sprache auch sein mag, gmeinsam gilt für alle: Funktionen und Methoden bezeichnen einen Vorgang und sollten immer mit einem Verb beginnen (compute, create, get, …) und beschreiben, was sie tun. (computeDepth, createFactory, getResult, …).

Auch sind nahzu alle Programmiersprachen – zumindest die Relevanten – so konzipiert, dass ihnen die englische Sprache zu Grunde liegt, die Befehle der Programmierprache also Begriffe aus dem englischen Wortschatz sind. Eigener Code darf diesen Grundsatz nicht brechen. Ein Funktionsname KindHinzufügen ist also in keinem Fall akzeptabel, hat in professionalem Code nicht zu suchen und sollte aller höchstens (wenn überhaupt) im Unterricht in der Unterstufe in Schulen akzeptiert werden. Außerdem bricht in diesem Falle die deutsche Sprache die Regel, dass Prodcedurennamen mit einem Verb beginnen sollten.

Konzepte sind wichtig und sollten eingehalten werden

Hat man sich dann auch mal für ein Namenskonzept entschieden, sollte man dies auch immer Anwenden. Schlechter Code verwendet z.B. an verschiedenen Stellen andere Bezeichnungen für die selbe Funktionalität – zum Löschen von etwas, findet man dann clear, delete, destroy, remove oder Ähnliches.

Abkürzungen bleiben erlaubt, wenn sie sinnvoll gewählt sind

Ein Relikt aus alten Zeiten ist auch, den Typ der Variable mit dem Namen zu verheiraten. Vorallem in älteren Win32 Anwedungen fand und findet man immer noch Ausdrücke wie LPSTR lpstrName, int iLength oder PBYTE pbData. Man nahm fälchlicherweise an, dass solche Präfixe zum besseren Verständnis des Codes und dessen Ablauf beitragen. Heute wissen wir, dass solcher Code Smell redundant ist, das Verstehen des Codes nicht verbessert, sondern oft sogar erschwert.
Es sei auch abzuraten, Namen zu viel und zu stark abzukürzen. Das Bekannte i als Zählervariable in for-Schleifen wird sich wohl nie abschaffen lassen, aber man sollte sich immer überlegen, ob man vielleicht doch einen geschickteren Namen findet.

for(int i = 0; i < rows.Length; ++i)
{
  for(int j = 0; j < columns.Length; ++j)
  {
     // ...
  }
}

// Eindeutiger wird es so:

for(int rowIndex = 0; rowIndex < rows.Length; ++rowIndex)
{
  for(int colIndex = 0; colIndex < columns.Length; ++colIndex)
  {
     // ...
  }
}

(Ich hab hier absichtlich colIndex anstatt columnIndex gewählt um zu zeigen, dass Abkürzungen kein absolutes no-go sind, sondern durchaus mit Bedacht eingesetzt werden dürfen. Anmerkung: Das foreach anstatt for in diesem Fall die bessere Lösung ist, ist bekannt, für dieses Beispiel aber nicht relevant.)

Erlauben wir uns ein Fazit…

Beim Programmieren neigt man oft dazu, ein Stück Code schnell und kurz "runterzuschreiben". Dabei ist im ersten Moment die Benamung für einen selber nicht so wichtig, da man sich direkt mit dem Code auseinandersetzt und ja selber weiß, was man gerade genau meint. Wir tendieren gerne dazu - vorallem Variablen - schlampig und uneindeutig zu bezeichnen - dass das nicht nur uns, sondern auch unseren Nachfolgern, anderen Teammitgliedern und oft auch dem Erfolg des Projektes zum Verhängnis werden kann, weil man zuviel Zeit und Geld beim Lesen und Verstehen des Codes verschwendet. Man sollte sich also selbst einen gefallen tun, und von Anfang an Klassen, Variablen, Funktion, ... so benennen, dass ohne große Recherche klar erkannnt werden kann, was hinter der Bezeichnung verborgen steht.

So restriktiv diese Regeln auf den ersten Blick auch sein mögen, erlauben sie dennoch genug Spielraum für den eigenen Stil, den sich jeder Entwickler meiner Meinung nach erhalten sollte. Es wäre doch auch langweilig, wenn wir alle mit den gleichen Klamotten rumlaufen würden.

Informationen über den Autor

esskar

ist das, was man einen Fullstack Developer bezeichnet. Vom Design bis in die Tiefen der C-Programmierung verfügt er nationale und internationale Projekterfahrung, und alles unter dem Motto — Code ist Poesie. Follow @esskar