Code Smell

Ein Code Smell ist ein Indiz für mangelnde Code-Qualität. Liegen Code Smells vor, hat man beim Sichten des Codes das Gefühl, dass etwas faul ist. Wir zeigen bekannte Code Smells und erklären, wie man diese los wird.

Was ist ein Code Smell?

Ein Code Smell ist ein Merkmal von Code, das bei erfahrenen Programmiererinnen und Programmierern das Gefühl hervorruft, dass der Code nicht sauber ist. Eine gute „Nase“ für Code Smells zu haben, ist in etwa vergleichbar mit der Intuition eines Handwerksmeisters. Blickt dieser auf Kabelsalat oder schlecht verlegte Leitungen, ist ihm sofort klar, dass etwas faul ist. Nur, im Quellcode sehen diese vielsagenden Muster anders aus.

Der bekannte britische Programmierer Martin Fowler verweist auf seinen Kollegen Kent Beck als Schöpfer des Begriffs. Auf Fowlers Blog liefert selbiger die folgende Definition:

Zitat

„A Code Smell is a surface indication that usually corresponds to a deeper problem in the system.“ – Quelle: https://martinfowler.com/bliki/CodeSmell.html

Übersetzung: „Ein Code Smell ist ein oberflächlicher Hinweis, der für gewöhnlich mit einem tieferliegenden Problem im System einhergeht.“ (übersetzt von IONOS)

Ein Code Smell weist also auf ein systemisches Problem hin. Entweder wurde der Code von einem Programmierer bzw. einer Programmiererin mit mangelnden Kompetenzen oder von immer wieder wechselnden Programmierenden geschrieben. Im letzteren Fall führt der schwankende Grad von Kompetenz, Kenntnis der Codebase und Vertrautheit mit Richtlinien und Standards zu mangelnder Code-Qualität. Der Code fängt an zu stinken.

Ein Code Smell ist nicht zu verwechseln mit punktuellen Defiziten, die in jeder Codebase zu erwarten sind. Zunächst ist ein Code Smell lediglich ein Indiz. Nimmt ein Programmierer bzw. eine Programmiererin einen Code Smell wahr, gilt es, durch weitere Überprüfung festzustellen, ob tatsächlich ein systemisches Problem vorliegt.

Zeigt sich, dass der Code riecht, weil er faul ist, gibt es unterschiedliche Vorgehensweisen, den Code zu bereinigen. Oft handelt es sich dabei um sogenannte Refactoring-Ansätze, die dazu dienen, die Struktur des Codes unter Beibehaltung der Funktionalität zu verbessern. Je nach Umfang und Komplexität des Codes kann es jedoch unmöglich sein, Code Smells zu entfernen. Dann hilft nur noch ein „Re-write“, d. h. ein Neuschreiben von Grund auf.

Welche Arten von Code Smell gibt es?

Code ist ein Medium, das auf verschiedenen Abstraktionsebenen existiert. Denken wir an eine Anwendung. Diese besteht aus einer Vielzahl von Einzelstücken, die allesamt in Code definiert sind: Variablen, Funktionen, Klassen, Module. Zur Betrachtung der verschiedenen Code Smells unterscheiden wir zwischen den Abstraktionsebenen:

  1. Generelle Code Smells
  2. Code Smells auf Funktionsebene
  3. Code Smells auf Klassenebene
  4. Code Smells auf Anwendungsebene

Generelle Code Smells

Die generellen Code Smells sind auf jeder Ebene einer Anwendung anzutreffen. Meist handelt es sich nicht um klare Fehler, sondern um sich negativ auswirkende Muster. Eine Faustregel der Programmierung besagt, dass Code primär für menschliche Betrachtende geschrieben werden sollte. Gerade noch unerfahrene Programmierende schreiben oft Code, der zwar das gewünschte Ergebnis liefert, jedoch unverständlich ist.

Schlechte Benennung von Code-Konstrukten

Die einzelnen Code-Konstrukte wie Variablen und Funktionen gut zu benennen, ist eine hohe Kunst. Leider finden sich oft nichtssagende, unsinnige oder sogar widersprüchlich Namen im Code. Das Paradebeispiel sind Variablennamen, die nur aus einem Buchstaben bestehen: x, i, n etc. Da diese Namen keinerlei Kontext geben, sind Sinn und Zweck nicht ersichtlich. Besser ist es, ausdrucksstarke Namen zu verwenden. Dann liest sich der Code wie natürliche Sprache und es ist einfacher, dem Programmfluss zu folgen und logische Inkonsistenzen zu erkennen.

Kein einheitlicher Code-Stil

Sauberer Code wirkt wie aus einem Guss; man sieht sofort, dass der Code auf Grundlage gewisser Regeln entstanden ist. Fehlt diese Regelmäßigkeit, handelt es sich um einen Code Smell. Variabilität in der Benennung deutet darauf hin, dass mehrere Personen unabhängig voneinander Teile des Codes geschrieben haben. Handelt es sich um ein Team, kann eine einheitliche Vorgabe fehlen.

Tipp

Erfahren Sie mehr zum Thema "Clean Code".

Undefinierte Variablen

Manche Sprachen wie C++, Java und JavaScript erlauben, eine Variable zu deklarieren, ohne einen Anfangswert festzulegen. Die Variable existiert ab der Deklaration, ist jedoch nicht definiert. Daraus ergeben sich ggf. subtile Bugs. Besonders fatal ist dieser Code Smell in lose typisierten Sprachen. Denn ohne anfängliche Definition ist nicht erkennbar, welcher Typ für die Variable erwartet wird.

Hartkodierte Werte im Code

Gerade unerfahrene Programmierende machen diesen Fehler oft: Um eine Variable mit einem spezifischen Wert zu vergleichen, tragen sie den Wert direkt ein. Man spricht dabei auch von „hartkodierten Werten“. Hartkodierte Werte sind problematisch, wenn sie an mehreren Stellen im Programm auftauchen. Denn die einzelnen Kopien tendieren dazu, über die Zeit unabhängig voneinander zu mutieren. Besser ist es, für den Wert eine Konstante zu definieren. Dadurch legt man eine „Single Source of Truth“ (SSOT) fest: Jeglicher Code, der den Wert benötigt, greift auf dieselbe, an einer einzigen Stelle definierte Konstante zu.

Magische Zahlen im Code

Bei einer magischen Zahl handelt es sich um einen Spezialfall hartkodierter Werte. Stellen wir uns vor, unser Code arbeitet mit einem auf 24 Bits limitierten Wert. 24 Bits entsprechen 16.777.216 möglichen Werten bzw. den Zahlen von 0 bis 16.777.215. Leider fehlt ein Kommentar, und so ist zu einem späteren Zeitpunkt nicht klar, wie der Wert zustande kam – eine magische Zahl ist entstanden. Das fehlende Wissen über den Ursprung des Werts erhöht das Risiko, dass dieser versehentlich verändert wird. Diese Fehlerquellen werden verhindert, wenn man die Definition des Wertes als Ausdruck in die Zuweisung der Konstante aufnimmt.

Tief verschachtelte Kontrollanweisungen

In den meisten Sprachen ist es möglich, Code-Konstrukte beliebig tief zu schalten. Leider wächst dadurch auch die Komplexität, was es schwieriger macht, den Code beim Lesen zu durchblicken. Ein besonders häufig anzutreffender Code Smell sind tief verschachtelte Kontrollanweisungen wie Schleifen und Verzweigungen. Zum Auflösen der Verschachtelung gibt es mehrere Ansätze (z. B. boolescher AND-Operator, um die zwei Bedingungen innerhalb einer if-Anweisung unterzubringen, ein anderer Ansatz, der mit beliebig vielen Bedingungen funktioniert, nutzt „Guard Clauses“).

Hinweis

Die Bewertung „tief verschachtelt“ folgt einem subjektiven Maß. Als Faustregel gilt: Kontrollanweisungen sollten maximal drei Ebenen tief geschachtelt werden, in Ausnahmen vier Ebenen. Tiefer ist fast nie ratsam oder notwendig. Fühlt man sich versucht, tiefer zu schachteln, sollte man ein Refactoring in Erwägung ziehen.

Code Smells auf Funktionsebene

In den meisten Sprachen sind Funktionen die grundlegende Ausführungseinheit von Code. Es gibt kleinere Stücke Code, z. B. Variablen und Ausdrücke, jedoch stehen diese für sich. Das Schreiben sauberer Funktionen erfordert einiges an Erfahrung. Wir zeigen ein paar der häufigsten Code Smells auf Funktionsebene.

Fehlende Behandlung von Ausnahmen

Funktionen erhalten Eingabewerte als Argumente. In vielen Fällen sind nur bestimmte Werte bzw. Wertebereiche gültig. Es liegt in der Verantwortung der Programmierenden, Eingabewerte auf Gültigkeit zu prüfen und Ausnahmen entsprechend zu behandeln.

Zugriff auf Variable aus übergeordnetem Gültigkeitsbereich in Funktion

Gültigkeitsbereiche sind grundlegendes Merkmal der meisten Programmiersprachen. Sie bestimmen darüber, welche Namen an welchen Stellen im Code definiert sind. Dabei erben untergeordnete Gültigkeitsbereiche von den darüberliegenden. Wird innerhalb einer Funktion auf eine außerhalb definierte Variable zugegriffen, handelt es sich um einen Code Smell. Denn zwischen der Definition der Variable und dem Funktionsaufruf kann sich der Wert geändert haben. Idealerweise greift man innerhalb von Funktionen nur auf Werte zu, die als Argumente übergeben wurden. Um nicht immer wieder denselben Wert übergeben zu müssen, setzt man Default-Parameter ein.

Hinweis

Der Zugriff auf eine Variable aus dem Gültigkeitsbereich einer umschließenden Funktion erzeugt eine „Closure“. Dabei handelt es sich nicht um Code Smell. Closures sind ein wichtiges Konzept in der JavaScript-Programmierung.

Code Smells auf Klassen-Ebene

Die objektorientierte Programmierung (OOP) kann helfen, Code-Reuse zu steigern und Komplexität zu verringern. Leider bieten sich vielfältige Möglichkeiten, beim Klassen-Design Fehler zu machen, die sich später als Code Smell bemerkbar machen.

Einer der häufigsten Code Smells auf Klassen-Ebene ist das „God Object“ bzw. die dazugehörige „Gott-Klasse“. Ein God-Object fasst allerlei eigentlich nicht zusammengehörende Funktionalität zusammen und verletzt damit den Grundsatz der „Trennung der Belange“.

Häufig anzutreffen sind Code Smells im Zusammenhang mit der Vererbungshierarchie. Manchmal wird Code unnötig über mehrere Vererbungsebenen hinweg verteilt. Oft wird auch der Fehler gemacht, Vererbung anstelle von Komposition als primären Ansatz für das Zusammensetzen von Objekten zu nutzen.

Auch der unbeabsichtigte Einsatz von „Data Classes“ gilt als Code Smell. Dabei handelt es sich um Klassen, die kein eigenes Verhalten implementieren. Sind außer generischen Gettern und Settern keinerlei Methoden definiert, sollte man überlegen, eine simplere Datenstruktur wie ein Dict oder eine Struct zu verwenden.

Code Smells auf Anwendungsebene

Es ist der Albtraum eines jeden Programmierers und einer jeden Programmiererin: Man wird beauftragt, an einer bestehenden Anwendung zu arbeiten, und der erste Blick auf den Code zeigt, dass die gesamte Anwendung in einer einzelnen, riesigen Datei steckt. Es ist unklar, wie die einzelnen Code-Bestandteile in Zusammenhang stehen. Wie auf einem Teller Nudeln geht im „Spaghetti-Code“ alles drunter und drüber.

Jegliche Abstraktionen fehlen, es gibt keine Unterteilung in Klassen und bestenfalls wenige Funktionen. Dafür finden sich allerlei Code-Duplizierungen an, aus denen sich subtile Bugs ergeben. Ferner kommt es zum haufenweisen Einsatz globaler Variablen.

Der Einsatz globaler Variablen ist ein besonders penetranter Code Smell. Da sich globale Variablen potenziell von allen Orten im Code ändern lassen, öffnet man schwer zu behebenden Fehlern Tür und Tor. Beim Debuggen sieht man sich gezwungen, Fehler überall zu suchen.

Ein weiterer anwendungsspezifischer Code Smell ist die mangelnde Trennung der Belange. Besonders bei PHP-Projekten kommt es leicht dazu, Markup, Funktionalität und Darstellung in einer Datei zu vermischen. Dies erschwert Code Reuse und Refactoring. Besser ist es, eine saubere Trennung der Belange anzustreben, z. B. durch den Einsatz des Musters „Model-View-Controller“ (MVC).

Wie lässt sich ein Code Smell entfernen?

An sich ist es vorzuziehen, Code kontinuierlich sauber zu halten. Am besten folgt man dazu dem Muster:

  1. Prototype: Entwurf erstellen
  2. Test: Funktionalität testen
  3. Refactor: Code aufräumen
  4. Ship: Code in Produktionsumgebung ausliefern

Leider wird der dritte Punkt oft übersprungen – insbesondere dann, wenn einem Manager oder einer Managerin ohne Coding-Erfahrung die Entscheidung obliegt. Die Argumentation ist: Der Code funktioniert doch, also wozu weiterhin Arbeit darauf verwenden? Über einen längeren Zeitraum fängt der Code an zu stinken; technische Schulden sammeln sich an.

Wenn eine bestehende Codebasis Code Smells enthält, ist es nicht möglich, von vorne anzufangen. Dann gilt es, den Code aufzuräumen. Am besten geht man inkrementell vor und grenzt zunächst Bereiche innerhalb der Codebase ab, die sich relativ leicht bereinigen lassen. Man trennt die funktionierenden oder verbesserbaren Bestandteile von den „dunklen Ecken“, die man besser in Ruhe lässt.

Das Abgrenzen der weniger von den stärker stinkenden Code-Bereichen verringert die Komplexität. So wird es einfacher, Code zu testen und Debugging-Schritte vorzunehmen. Helfen können automatisierte Code-Review-Tools, die Code Smells aufspüren und Vorschläge bzw. Hilfen zum Bereinigen anbieten.

Effektiv zum Entfernen von Code-Smells sind Refactoring-Ansätze. Man kapselt Funktionalität in Funktionen bzw. spaltet bestehende Funktion auf. Das Entfernen nicht genutzter Code-Abschnitte und Verbessern der Namensgebung lichtet die Code-Basis. Dabei helfen die goldenen Regeln „Don’t Repeat Yourself“ (DRY) und „You ain’t gonna need it“ (YAGNI) als Richtlinien.