„Code-Smell“ ist ein informeller Begriff, der von Programmierern verwendet wird, um Software-Entwurfsmuster zu beschreiben, die bei schlechtem Code häufig vorkommen. Code-Smells sind am besten nicht als Bugs oder Fehler an sich zu verstehen, sondern als Warnzeichen für schlechte Codequalität.
Der Begriff „Code-Smell“ wurde von Kent Beck geprägt und in dem wegweisenden Buch „Refactoring: Improving the Design of Existing Code“, das er 1999 zusammen mit Martin Fowler verfasste, populär gemacht – insbesondere in dem Kapitel mit dem Titel „Bad Smells in Code“. Fowler selbst formuliert einen Code-Smell prägnant als „eine Oberflächenindikation, die meist einem tieferen Problem im System entspricht.“1
Im Gegensatz zu einem echten Softwarefehler verhindert ein Code-Smell nicht, dass Quellcode kompiliert, ausgeführt und seine gewünschte Funktion erfüllt – und sein Vorhandensein weist auch nicht immer auf ein tatsächliches Problem hin. Stellen Sie sich vor, Sie betreten ein Haus und riechen etwas Seltsames oder Schimmeliges. Der Geruch kann von abgelaufenen Lebensmitteln in Ihrem Kühlschrank oder von Müll stammen, der nach draußen gebracht werden muss; oder genauer gesagt, könnte es sich um Schimmel oder etwas Verwesendes in Ihren Wänden handeln – es könnte aber auch von einem sehr scharfen Käse sein, der absolut sicher zu essen ist. Der Geruch rechtfertigt dennoch eine genauere Untersuchung.
Für einen erfahrenen Software Engineer „riechen“ bestimmte Muster im Code einfach nicht richtig. Mit der Zeit könnte man ihre Anwesenheit instinktiv mit bestimmten Komplikationen, Ineffizienzen oder zukünftigen Problemen in Verbindung bringen. Das Benennen und Popularisieren bestimmter Code-Smells erleichtert die Vertrautheit mit diesen kontraproduktiven Mustern (oder „Anti-Mustern“) und den Designprinzipien, die sie verletzen. Diese Vertrautheit wiederum führt dazu, dass diese Anti-Muster bei der Code-Überprüfung eher erkannt und behoben werden. Wie Fowler es ausdrückt: „Ein Smell ist per Definition etwas, das schnell wahrnehmbar – oder riechbar – ist.“
Code-Smells, wenn sie nicht angegangen werden, tragen wesentlich zu technischen Schulden bei. Wie eine Studie behauptet, beeinflussen 75 % der Code-Überprüfungen nicht die Programmausführung, beeinträchtigen aber „die Evolvierbarkeit der Software“.2 Selbst wenn ein bestimmtes Anti-Muster oder eine schlechte Angewohnheit heute keinen Fehler verursacht, erhöht es das Risiko erheblich, dass später Bugs, Abstürze oder Sicherheitslücken in deiner Codebasis auftauchen. Zumindest reduziert das Vorhandensein von bekannten Code-Smells die Lesbarkeit und Wartbarkeit Ihres Codes, was es schwieriger macht, ihn zu verstehen, zu aktualisieren und zu verbessern.
Bleiben Sie mit dem Think-Newsletter über die wichtigsten – und faszinierendsten – Branchentrends in den Bereichen KI, Automatisierung, Daten und mehr auf dem Laufenden. Weitere Informationen finden Sie in der IBM Datenschutzerklärung.
Obwohl viele gängige Code-Smells weiterhin unter den von Beck und Fowler in Refactoring geprägten Namen bekannt sind, existiert keine einzige kanonische, allgemein anerkannte Liste von Code-Smells. Ob ein bestimmter Code-Smell mit dem einen oder anderen Namen bezeichnet werden sollte – oder ob er als eigenes Anti-Muster und nicht als Untertyp eines anderen kategorisiert werden sollte – ist weitgehend irrelevant. Entscheidend ist, ob Name und Beschreibung eines Code-Smells klar und intuitiv genug sind, um Softwareentwicklungsteams dabei zu helfen, produktive Designprinzipien zu teilen und anzuwenden.
Ebenso gibt es keine allgemein anerkannte Taxonomie für verschiedene Kategorien von Code-Smells. Dieser Artikel stützt sich hauptsächlich auf die von Jerzyk und Madeyski im Jahr 2023 vorgeschlagene Taxonomie. 3 Ihre Ergebnisse wurden im hilfreichen Katalog veröffentlicht, der bei codesmells.org geführt wird und zusätzlichen Kontext, Codebeispiele und geeignete Refaktorisierungstechniken für jede bietet.
Jerzyk und Madeyski weisen darauf hin, dass die am häufigsten zitierte Taxonomie von Code-Smells den fünf von Mäntylä und Lassenius im Jahr 2006 vorgeschlagenen Gruppierungen folgt. Diese kategorisierten die 22 von Fowler und Beck eingeführten Code-Smells (sowie einen weiteren eigenen) in fünf verschiedene Gruppen: Bloaters, Object-Orientation Abusers, Change Preventers, Dispensables und Couplers.4 Nach ihrem eigenen Versuch einer umfassenden Überprüfung sowohl formal veröffentlichter Literatur als auch „grauer Quellen“ (wie Blogs, Foren und Wikis) katalogisierten Jerzyk und Madeyski 56 verschiedene Code-Smells in 9 Gruppen (darunter die 5, die bereits in diesem Absatz genannt wurden).
Obwohl dieser Abschnitt größtenteils der von ihnen vorgeschlagenen 9-Kategorien-Struktur folgt, ist es wichtig zu beachten, dass solche Gruppierungen informell und subjektiv sind: Die nützlichste Taxonomie ist die, die Sie am intuitivsten finden. Das Verständnis der verschiedenen Arten von Komplikationen oder Ineffizienzen, die Code-Smells mit sich bringen können, kann eine aussagekräftigere Vorstellung von Code-Smells liefern als das Auswendiglernen einzelner Smells. Wie Mäntylä formuliert: „Bei einer langen, flachen Liste von Code-Smells ist es leicht, das Gesamtbild zu verlieren.“5
Bloater sind Code-Smells, die oft dazu führen, dass Methoden, Klassen oder Codeblöcke so groß werden, dass sie unhandlich werden. Dies verringert die Lesbarkeit und erschwert die Wartung und Änderung des Codes.
Zu den Beispielen für Bloater gehören:
Datenklumpen: Gruppen von Variablen, die häufig und überall zusammen auftreten.
Große Klasse: Eine Klasse, die versucht, zu viel zu tun, zu viele Variablen enthält und daher keinen Zusammenhalt aufweist.
Lange Funktion (lange Methode): Eine Methode mit zu vielen Codezeilen.
Lange Parameterliste: Funktionen, die zu viele Argumente erfordern, um korrekt zu funktionieren.
Primitive Obsession: Verwendung primitiver Datentypen anstelle spezialisierter kleiner Objekte.
Change Preventer oder Änderungsverhinderer sind, wie der Name schon sagt, Code-Smells, die die Möglichkeit behindern, Software zu modifizieren, zu erweitern oder anderweitig weiterzuentwickeln. Ein typisches Beispiel für einen Change Preventer ist eine Codestruktur, die Sie zwingt, eine Reihe von Änderungen an mehreren Stellen vorzunehmen, nur um eine einzige, einfache Änderung zu implementieren.
Solche Code-Smells verstoßen gegen Robert C. Martins Prinzip der Einzelverantwortung (SRP), das besagt, dass Änderungen an einem Codemodul nur von einem Ort ausgehen dürfen. Wie Martin in einem Blogbeitrag über SRP vorschlägt: „Sammeln Sie die Dinge, die sich aus den gleichen Gründen ändern. Trennen Sie die Dinge, die sich aus unterschiedlichen Gründen ändern.“6
Beispiele für Änderungsverhinderer sind:
Shotgun Surgery: Die Implementierung einer einzelnen Änderung erfordert Modifikationen über viele verstreute Module gleichzeitig (was im Wesentlichen das Gegenteil von Large Classes ist).
Divergent Change: Ähnlich wie bei der umgekehrten Shotgun Surgery erfordert die Umsetzung einer einzigen Änderung viele Modifikationen innerhalb einer einzigen Klasse.
Callback Hell: Eine Codestruktur, in der viele Methoden tief ineinander verschachtelt sind, durch zahlreiche Einrückungen und geschweifte Klammern, wodurch Ursache und Wirkung verschleiert werden und der Code schwer lesbar und wartbar wird.
Kopplungen sind Code-Smells, die gegen das Kerndesignprinzip der niedrigen Kopplung verstoßen, indem sie übermäßige Abhängigkeiten zwischen verschiedenen Klassen schaffen. Dies verringert die Wiederverwendbarkeit des Codes und erschwert (oder verhindert sogar) unabhängiges Unit-Testing.
Beispiele für Kopplungen sind:
Feature Envy: Eine Methode greift übermäßig auf die Daten eines anderen Objekts zu.
Insiderhandel (auch bekannt als unangemessene Intimität): Eine Klasse verwendet die internen Felder oder Methoden einer anderen Klasse.
Nachrichtenketten: Eine lange Abfolge von verketteten Methodenaufrufen zwischen Objekten.
Datenhändler leiten Daten durch weit mehr Klassen oder Funktionen, als notwendig wären. Dadurch entstehen unnötige Abhängigkeiten und Komplexität, was oft zu Codeelementen führt, die lediglich Daten speichern oder verteilen, ohne ein sinnvolles Verhalten zu liefern.
Beispiele für Datenhändler sind:
Middle Man: Eine Klasse, deren einzige Aufgabe darin besteht, Aufgaben an andere zu delegieren.
Tramp Data: Wenn Daten „per Anhalter“ durch eine Kette von Methoden wandern, die sie nicht nutzen.
Globale Daten: Eine Struktur, in der Variablen von überall im Code modifiziert werden können, sodass jede Funktion im Code verdächtig wird, wenn etwas kaputtgeht.
Dispensables sind intuitiv betrachtet entbehrliche Codeelemente: Ihre Entfernung würde die Codebasis übersichtlicher und leichter lesbar machen, ohne dass dies nennenswerte Auswirkungen auf die Gesamtfunktionalität hätte.
Beispiele für entbehrliche Güter sind:
Kommentare: Kommentare sind zwar im Allgemeinen eine gute Sache, werden aber manchmal als eine Art „Deodorant“ für Code-Smell verwendet, indem sie ihn eher erklären als verbessern. Wenn zum Beispiel ein Kommentar beschreibt, was in einem bestimmten Codeabschnitt passiert, wird er verwendet, um Code zu verbergen, der für sich allein nicht intuitiv oder lesbar genug ist. Einzelne Kommentare können mit der Zeit auch redundant oder veraltet werden. Spezifischeres Verhalten bei Kommentaren kommt in anderen Code-Smells vor.
Datenklasse: Klassen, die nur Felder und Accessoren enthalten und denen ein sinnvolles Verhalten fehlt.
Toter Code: Codeelemente, die nicht mehr ausgeführt werden, weil sie im Laufe der Zeit durch Refactoring und andere Änderungen überflüssig geworden sind.
Doppelter Code: Identische oder sehr ähnliche Codestrukturen an mehreren Stellen.
Faule Klasse (auch faule Elemente genannt): Klassen oder Funktionen, die zu wenig tun, um zu existieren.
Spekulative Allgemeinheit: Unnötiger Code wurde hinzugefügt, um hypothetische zukünftige Funktionen zu unterstützen.
Functional Abusers sind Code-Smells, die objektorientierte Programmierprinzipien meiden, indem sie funktionale Muster in eine objektorientierte Codebasis zwingen.
Zu den Beispielen für Functional Abusers gehören:
Schleifen: Verwendung traditioneller Schleifen anstelle moderner Pipeline-Operationen. Obwohl Fowler fast alle Schleifen für veraltet hielt, stellte Jerzyk vor, dass insbesondere imperative Schleifen das Hauptproblem sind.
Veränderliche Daten: Variablen, deren Zustand sich unerwartet ändert und dadurch unvorhersehbare Nebenwirkungen verursacht.
Nebenwirkungen (auch unreine Funktionen genannt): Methoden, die mehr leisten, als ihr Name und ihr primärer Zweck vermuten lassen.
Lexical Abusers sind Code-Smells, die aus schlechten Namenskonventionen, inkonsistenter Formatierung oder kryptischer Syntax stammen. Einfach ausgedrückt handelt es sich um Fälle, in denen die Formulierung des Codes nicht intuitiv mit dem entsprechenden Codeverhalten übereinstimmt, was die Lesbarkeit beeinträchtigt.
Zu den Beispielen für Lexical Abusers gehören:
Magische Zahl: Unbenannte Zahlen, die ohne angemessene Erklärung oder Kontext in den Code eingefügt werden.
Fallacious Comment: Kommentare, die nicht mehr zutreffend sind, weil sich der umgebende Code geändert hat. Weil Kommentare nicht wirklich ausgeführt werden, entgehen sie oft Linters und anderen automatisierten Prüfungen.
Mysteriöser Name: Funktionen oder Variablen sind schlecht benannt und verbergen ihre eigentliche Absicht.
Falscher Methodenname: Funktionen, deren Namen aktiv irreführend sind, basierend auf allgemeinen Konventionen und Erwartungen. Zum Beispiel eine Funktion namens getSomething, die tatsächlich nichts anzeigt.
Obfuscators sind Codeelemente, die unnötig kompliziert, komplex oder „clever“ geschrieben sind und die eigentliche Absicht des Codes hinter unnötiger Abstraktion verbergen. Diese Code-Smells machen den Quellcode schwer lesbar und erschweren es Programmierern in der Zukunft, ihn zu verstehen und bei Bedarf zu modifizieren.
Zu den Obfuscators gehören beispielsweise:
Vertikale Trennung: Unnötig große Abstände zwischen verwandten, relevanten Codeelementen, wie etwa einer Variable, die am Anfang einer Methode deklariert wird und erst 50 Zeilen später verwendet wird.
Verschleierte Absicht: Die breitere Kategorie von Funktionen, Variablen, Namen und Zahlen, deren Zweck weder intuitiv noch im Kontext klar gemacht wird.
Komplizierter boolescher Ausdruck: Verworrene logische Flüsse, z. B. solche, die doppelte Negative oder komplexe Ketten von
Cleverer Code: Code, der funktioniert, aber schwer verständliche, maßgeschneiderte Sprache in Situationen ersetzt, für die es bereits weit verbreitete Eingebaute und andere konventionelle Lösungen gibt.
Object-oriented Abusers (oder Object-orientation Abusers) sind Code-Smells, die objektorientierte Designprinzipien nicht vollständig oder korrekt anwenden. Zum Beispiel sind Switch-Anweisungen in der prozeduralen Programmierung nützlich, sollten aber in objektorientierter Programmierung vermieden werden.
Zu den Beispielen Object-oriented Abusers gehören:
Alternative Klasse mit verschiedenen Schnittstellen: Klassen, die ähnliche Funktionen mit völlig unterschiedlichen Methodennamen ausführen.
Refused Bequest: Unterklassen, die keine geerbten Methoden benötigen oder verwenden.
Switch-Anweisungen (auch bekannt als Conditional Complexity aka Repeated Switches): Genau dieselbe Switch-Anweisung, die in der Codebasis dupliziert wurde.
Temporäres Feld: Eine Variable, die dort erstellt wird, wo sie oft nicht benötigt wird und typischerweise nur in bestimmten Fällen verwendet wird.
Der Hauptvorteil des Verstehens von Code-Smells besteht darin, dass das Erkennen davon eine effektivere Refaktorisierung ermöglicht: die Praxis, den Quellcode zu aktualisieren, ohne dessen externes Verhalten oder seine Funktionalität zu verändern. Regelmäßiges Aufräumen des Codes durch Refactoring ist unerlässlich, um technische Schulden zu reduzieren und im Laufe der Zeit schnelle, effektive Verbesserungen und Ergänzungen Ihrer Codebasis zu ermöglichen.
Refactoring kann im Wesentlichen als der Prozess der Identifizierung und Adresse von Code-Smells verstanden werden. Ziel ist es, Probleme zu beheben, bevor sie die Funktionalität negativ beeinflussen – an diesem Punkt ähnelt der Prozess eher dem Debuggen als dem Refactoring.
Moderne agentische Engineering-Plattformen wie IBM Bob bieten oft automatisierte Refactoring-Vorschläge in Echtzeit. Durch umfangreiche Schulungen zu gängigen Code-Smells im Kontext der tatsächlichen Codebasen – und nicht durch Auswendiglernen von Code-Smells-Definitionen – ermöglichen die KI-Code-Refactoring-Funktionen solcher Plattformen Entwicklern, ihre Ausgabe zu skalieren, ohne dass es zu einem entsprechenden Anstieg des unhandlichen KI-generierten Codes kommt, der später schwer zu verstehen und zu ändern sein wird.
Beschleunigen Sie die Softwarebereitstellung mit Bob, Ihrem KI-Partner für sichere, absichtsorientierte Entwicklung.
Entwickeln, bereitstellen und verwalten Sie KI-Anwendungen schneller mit unternehmensgerechten Tools.
Gestalten Sie Altsysteme durch intelligente KI-Modernisierung neu.