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 er­fah­re­nen Pro­gram­mie­re­rin­nen und Pro­gram­mie­rern das Gefühl her­vor­ruft, dass der Code nicht sauber ist. Eine gute „Nase“ für Code Smells zu haben, ist in etwa ver­gleich­bar mit der Intuition eines Hand­werks­meis­ters. Blickt dieser auf Ka­bel­sa­lat oder schlecht verlegte Leitungen, ist ihm sofort klar, dass etwas faul ist. Nur, im Quellcode sehen diese viel­sa­gen­den Muster anders aus.

Der bekannte britische Pro­gram­mie­rer Martin Fowler verweist auf seinen Kollegen Kent Beck als Schöpfer des Begriffs. Auf Fowlers Blog liefert selbiger die folgende De­fi­ni­ti­on:

Zitat

„A Code Smell is a surface in­di­ca­ti­on that usually cor­re­sponds to a deeper problem in the system.“ – Quelle: https://mart­in­fow­ler.com/bliki/CodeSmell.html

Über­set­zung: „Ein Code Smell ist ein ober­fläch­li­cher Hinweis, der für ge­wöhn­lich mit einem tie­fer­lie­gen­den Problem im System ein­her­geht.“ (übersetzt von IONOS)

Ein Code Smell weist also auf ein sys­te­mi­sches Problem hin. Entweder wurde der Code von einem Pro­gram­mie­rer bzw. einer Pro­gram­mie­re­rin mit man­geln­den Kom­pe­ten­zen oder von immer wieder wech­seln­den Pro­gram­mie­ren­den ge­schrie­ben. Im letzteren Fall führt der schwan­ken­de Grad von Kompetenz, Kenntnis der Codebase und Ver­traut­heit mit Richt­li­ni­en und Standards zu man­geln­der Code-Qualität. Der Code fängt an zu stinken.

Ein Code Smell ist nicht zu ver­wech­seln mit punk­tu­el­len Defiziten, die in jeder Codebase zu erwarten sind. Zunächst ist ein Code Smell lediglich ein Indiz. Nimmt ein Pro­gram­mie­rer bzw. eine Pro­gram­mie­re­rin einen Code Smell wahr, gilt es, durch weitere Über­prü­fung fest­zu­stel­len, ob tat­säch­lich ein sys­te­mi­sches Problem vorliegt.

Zeigt sich, dass der Code riecht, weil er faul ist, gibt es un­ter­schied­li­che Vor­ge­hens­wei­sen, den Code zu be­rei­ni­gen. Oft handelt es sich dabei um so­ge­nann­te Re­fac­to­ring-Ansätze, die dazu dienen, die Struktur des Codes unter Bei­be­hal­tung der Funk­tio­na­li­tät zu ver­bes­sern. Je nach Umfang und Kom­ple­xi­tät des Codes kann es jedoch unmöglich sein, Code Smells zu entfernen. Dann hilft nur noch ein „Re-write“, d. h. ein Neu­schrei­ben von Grund auf.

Welche Arten von Code Smell gibt es?

Code ist ein Medium, das auf ver­schie­de­nen Abs­trak­ti­ons­ebe­nen existiert. Denken wir an eine Anwendung. Diese besteht aus einer Vielzahl von Ein­zel­stü­cken, die allesamt in Code definiert sind: Variablen, Funk­tio­nen, Klassen, Module. Zur Be­trach­tung der ver­schie­de­nen Code Smells un­ter­schei­den wir zwischen den Abs­trak­ti­ons­ebe­nen:

  1. Generelle Code Smells
  2. Code Smells auf Funk­ti­ons­ebe­ne
  3. Code Smells auf Klas­se­n­e­be­ne
  4. Code Smells auf An­wen­dungs­ebe­ne

Generelle Code Smells

Die ge­ne­rel­len Code Smells sind auf jeder Ebene einer Anwendung an­zu­tref­fen. Meist handelt es sich nicht um klare Fehler, sondern um sich negativ aus­wir­ken­de Muster. Eine Faust­re­gel der Pro­gram­mie­rung besagt, dass Code primär für mensch­li­che Be­trach­ten­de ge­schrie­ben werden sollte. Gerade noch un­er­fah­re­ne Pro­gram­mie­ren­de schreiben oft Code, der zwar das ge­wünsch­te Ergebnis liefert, jedoch un­ver­ständ­lich ist.

Schlechte Benennung von Code-Kon­struk­ten

Die einzelnen Code-Kon­struk­te wie Variablen und Funk­tio­nen gut zu benennen, ist eine hohe Kunst. Leider finden sich oft nichts­sa­gen­de, unsinnige oder sogar wi­der­sprüch­lich Namen im Code. Das Pa­ra­de­bei­spiel sind Va­ria­blen­na­men, die nur aus einem Buch­sta­ben bestehen: x, i, n etc. Da diese Namen keinerlei Kontext geben, sind Sinn und Zweck nicht er­sicht­lich. Besser ist es, aus­drucks­star­ke Namen zu verwenden. Dann liest sich der Code wie na­tür­li­che Sprache und es ist einfacher, dem Pro­gramm­fluss zu folgen und logische In­kon­sis­ten­zen zu erkennen.

Kein ein­heit­li­cher Code-Stil

Sauberer Code wirkt wie aus einem Guss; man sieht sofort, dass der Code auf Grundlage gewisser Regeln ent­stan­den ist. Fehlt diese Re­gel­mä­ßig­keit, handelt es sich um einen Code Smell. Va­ria­bi­li­tät in der Benennung deutet darauf hin, dass mehrere Personen un­ab­hän­gig von­ein­an­der Teile des Codes ge­schrie­ben haben. Handelt es sich um ein Team, kann eine ein­heit­li­che Vorgabe fehlen.

Tipp

Erfahren Sie mehr zum Thema "Clean Code".

Un­de­fi­nier­te Variablen

Manche Sprachen wie C++, Java und Ja­va­Script erlauben, eine Variable zu de­kla­rie­ren, ohne einen An­fangs­wert fest­zu­le­gen. Die Variable existiert ab der De­kla­ra­ti­on, ist jedoch nicht definiert. Daraus ergeben sich ggf. subtile Bugs. Besonders fatal ist dieser Code Smell in lose ty­pi­sier­ten Sprachen. Denn ohne an­fäng­li­che De­fi­ni­ti­on ist nicht erkennbar, welcher Typ für die Variable erwartet wird.

Hart­ko­dier­te Werte im Code

Gerade un­er­fah­re­ne Pro­gram­mie­ren­de machen diesen Fehler oft: Um eine Variable mit einem spe­zi­fi­schen Wert zu ver­glei­chen, tragen sie den Wert direkt ein. Man spricht dabei auch von „hart­ko­dier­ten Werten“. Hart­ko­dier­te Werte sind pro­ble­ma­tisch, wenn sie an mehreren Stellen im Programm auf­tau­chen. Denn die einzelnen Kopien tendieren dazu, über die Zeit un­ab­hän­gig von­ein­an­der zu mutieren. Besser ist es, für den Wert eine Konstante zu de­fi­nie­ren. Dadurch legt man eine „Single Source of Truth“ (SSOT) fest: Jeglicher Code, der den Wert benötigt, greift auf dieselbe, an einer einzigen Stelle de­fi­nier­te Konstante zu.

Magische Zahlen im Code

Bei einer magischen Zahl handelt es sich um einen Spe­zi­al­fall hart­ko­dier­ter Werte. Stellen wir uns vor, unser Code arbeitet mit einem auf 24 Bits li­mi­tier­ten Wert. 24 Bits ent­spre­chen 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 ent­stan­den. Das fehlende Wissen über den Ursprung des Werts erhöht das Risiko, dass dieser ver­se­hent­lich verändert wird. Diese Feh­ler­quel­len werden ver­hin­dert, wenn man die De­fi­ni­ti­on des Wertes als Ausdruck in die Zuweisung der Konstante aufnimmt.

Tief ver­schach­tel­te Kon­troll­an­wei­sun­gen

In den meisten Sprachen ist es möglich, Code-Kon­struk­te beliebig tief zu schalten. Leider wächst dadurch auch die Kom­ple­xi­tät, was es schwie­ri­ger macht, den Code beim Lesen zu durch­bli­cken. Ein besonders häufig an­zu­tref­fen­der Code Smell sind tief ver­schach­tel­te Kon­troll­an­wei­sun­gen wie Schleifen und Ver­zwei­gun­gen. Zum Auflösen der Ver­schach­te­lung gibt es mehrere Ansätze (z. B. boole­scher AND-Operator, um die zwei Be­din­gun­gen innerhalb einer if-Anweisung un­ter­zu­brin­gen, ein anderer Ansatz, der mit beliebig vielen Be­din­gun­gen funk­tio­niert, nutzt „Guard Clauses“).

Hinweis

Die Bewertung „tief ver­schach­telt“ folgt einem sub­jek­ti­ven Maß. Als Faust­re­gel gilt: Kon­troll­an­wei­sun­gen sollten maximal drei Ebenen tief ge­schach­telt werden, in Ausnahmen vier Ebenen. Tiefer ist fast nie ratsam oder notwendig. Fühlt man sich versucht, tiefer zu schach­teln, sollte man ein Re­fac­to­ring in Erwägung ziehen.

Code Smells auf Funk­ti­ons­ebe­ne

In den meisten Sprachen sind Funk­tio­nen die grund­le­gen­de Aus­füh­rungs­ein­heit von Code. Es gibt kleinere Stücke Code, z. B. Variablen und Ausdrücke, jedoch stehen diese für sich. Das Schreiben sauberer Funk­tio­nen erfordert einiges an Erfahrung. Wir zeigen ein paar der häu­figs­ten Code Smells auf Funk­ti­ons­ebe­ne.

Fehlende Be­hand­lung von Ausnahmen

Funk­tio­nen erhalten Ein­ga­be­wer­te als Argumente. In vielen Fällen sind nur bestimmte Werte bzw. Wer­te­be­rei­che gültig. Es liegt in der Ver­ant­wor­tung der Pro­gram­mie­ren­den, Ein­ga­be­wer­te auf Gül­tig­keit zu prüfen und Ausnahmen ent­spre­chend zu behandeln.

Zugriff auf Variable aus über­ge­ord­ne­tem Gül­tig­keits­be­reich in Funktion

Gül­tig­keits­be­rei­che sind grund­le­gen­des Merkmal der meisten Pro­gram­mier­spra­chen. Sie bestimmen darüber, welche Namen an welchen Stellen im Code definiert sind. Dabei erben un­ter­ge­ord­ne­te Gül­tig­keits­be­rei­che von den dar­über­lie­gen­den. Wird innerhalb einer Funktion auf eine außerhalb de­fi­nier­te Variable zu­ge­grif­fen, handelt es sich um einen Code Smell. Denn zwischen der De­fi­ni­ti­on der Variable und dem Funk­ti­ons­auf­ruf kann sich der Wert geändert haben. Idea­ler­wei­se greift man innerhalb von Funk­tio­nen 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ül­tig­keits­be­reich einer um­schlie­ßen­den Funktion erzeugt eine „Closure“. Dabei handelt es sich nicht um Code Smell. Closures sind ein wichtiges Konzept in der Ja­va­Script-Pro­gram­mie­rung.

Code Smells auf Klassen-Ebene

Die ob­jekt­ori­en­tier­te Pro­gram­mie­rung (OOP) kann helfen, Code-Reuse zu steigern und Kom­ple­xi­tät zu ver­rin­gern. Leider bieten sich viel­fäl­ti­ge Mög­lich­kei­ten, beim Klassen-Design Fehler zu machen, die sich später als Code Smell bemerkbar machen.

Einer der häu­figs­ten Code Smells auf Klassen-Ebene ist das „God Object“ bzw. die da­zu­ge­hö­ri­ge „Gott-Klasse“. Ein God-Object fasst allerlei ei­gent­lich nicht zu­sam­men­ge­hö­ren­de Funk­tio­na­li­tät zusammen und verletzt damit den Grundsatz der „Trennung der Belange“.

Häufig an­zu­tref­fen sind Code Smells im Zu­sam­men­hang mit der Ver­er­bungs­hier­ar­chie. Manchmal wird Code unnötig über mehrere Ver­er­bungs­ebe­nen hinweg verteilt. Oft wird auch der Fehler gemacht, Vererbung anstelle von Kom­po­si­ti­on als primären Ansatz für das Zu­sam­men­set­zen von Objekten zu nutzen.

Auch der un­be­ab­sich­tig­te Einsatz von „Data Classes“ gilt als Code Smell. Dabei handelt es sich um Klassen, die kein eigenes Verhalten im­ple­men­tie­ren. Sind außer ge­ne­ri­schen Gettern und Settern keinerlei Methoden definiert, sollte man überlegen, eine simplere Da­ten­struk­tur wie ein Dict oder eine Struct zu verwenden.

Code Smells auf An­wen­dungs­ebe­ne

Es ist der Albtraum eines jeden Pro­gram­mie­rers und einer jeden Pro­gram­mie­re­rin: Man wird be­auf­tragt, an einer be­stehen­den 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-Be­stand­tei­le in Zu­sam­men­hang stehen. Wie auf einem Teller Nudeln geht im „Spaghetti-Code“ alles drunter und drüber.

Jegliche Abs­trak­tio­nen fehlen, es gibt keine Un­ter­tei­lung in Klassen und bes­ten­falls wenige Funk­tio­nen. Dafür finden sich allerlei Code-Du­pli­zie­run­gen an, aus denen sich subtile Bugs ergeben. Ferner kommt es zum hau­fen­wei­sen Einsatz globaler Variablen.

Der Einsatz globaler Variablen ist ein besonders pe­ne­tran­ter Code Smell. Da sich globale Variablen po­ten­zi­ell von allen Orten im Code ändern lassen, öffnet man schwer zu be­he­ben­den Fehlern Tür und Tor. Beim Debuggen sieht man sich gezwungen, Fehler überall zu suchen.

Ein weiterer an­wen­dungs­spe­zi­fi­scher Code Smell ist die mangelnde Trennung der Belange. Besonders bei PHP-Projekten kommt es leicht dazu, Markup, Funk­tio­na­li­tät und Dar­stel­lung in einer Datei zu ver­mi­schen. Dies erschwert Code Reuse und Re­fac­to­ring. Besser ist es, eine saubere Trennung der Belange an­zu­stre­ben, z. B. durch den Einsatz des Musters „Model-View-Con­trol­ler“ (MVC).

Wie lässt sich ein Code Smell entfernen?

An sich ist es vor­zu­zie­hen, Code kon­ti­nu­ier­lich sauber zu halten. Am besten folgt man dazu dem Muster:

  1. Prototype: Entwurf erstellen
  2. Test: Funk­tio­na­li­tät testen
  3. Refactor: Code aufräumen
  4. Ship: Code in Pro­duk­ti­ons­um­ge­bung aus­lie­fern

Leider wird der dritte Punkt oft über­sprun­gen – ins­be­son­de­re dann, wenn einem Manager oder einer Managerin ohne Coding-Erfahrung die Ent­schei­dung obliegt. Die Ar­gu­men­ta­ti­on ist: Der Code funk­tio­niert doch, also wozu weiterhin Arbeit darauf verwenden? Über einen längeren Zeitraum fängt der Code an zu stinken; tech­ni­sche Schulden sammeln sich an.

Wenn eine be­stehen­de Codebasis Code Smells enthält, ist es nicht möglich, von vorne an­zu­fan­gen. Dann gilt es, den Code auf­zu­räu­men. Am besten geht man in­kre­men­tell vor und grenzt zunächst Bereiche innerhalb der Codebase ab, die sich relativ leicht be­rei­ni­gen lassen. Man trennt die funk­tio­nie­ren­den oder ver­bes­ser­ba­ren Be­stand­tei­le von den „dunklen Ecken“, die man besser in Ruhe lässt.

Das Abgrenzen der weniger von den stärker stin­ken­den Code-Bereichen ver­rin­gert die Kom­ple­xi­tät. So wird es einfacher, Code zu testen und Debugging-Schritte vor­zu­neh­men. Helfen können au­to­ma­ti­sier­te Code-Review-Tools, die Code Smells aufspüren und Vor­schlä­ge bzw. Hilfen zum Be­rei­ni­gen anbieten.

Effektiv zum Entfernen von Code-Smells sind Re­fac­to­ring-Ansätze. Man kapselt Funk­tio­na­li­tät in Funk­tio­nen bzw. spaltet be­stehen­de Funktion auf. Das Entfernen nicht genutzter Code-Ab­schnit­te und Ver­bes­sern der Na­mens­ge­bung lichtet die Code-Basis. Dabei helfen die goldenen Regeln „Don’t Repeat Yourself“ (DRY) und „You ain’t gonna need it“ (YAGNI) als Richt­li­ni­en.

Zum Hauptmenü