· 

Anti-Patterns: Wie du SCHLECHTEN Code schreibst

1. Einführung

In meinem Artikel zum Thema Clean Coding bei if-Anweisungen habe ich dir erklärt, wie du besseren Code schreibst. In diesem Artikel geht es um das genaue Gegenteil, nämlich die Frage, wie du SCHLECHTEN Code schreiben kannst. Hierfür können sogenannte Anti-Patterns genutzt werden, die für  den Erfolg eines Softwareprojekts schädliche Code-designtechnische Vorgehensweisen beschreiben. Du bekommst in diesem Artikel insgesamt 14 Tipps, wie du mit Anti-Pattern die Code-Base deines Projekts systematisch sabotieren kannst.

Natürlich hat dieser Artikel einen vorrangig belehrenden Charakter, um dir zu zeigen, wie man schlecht coded, damit du daraus lernst, was du besser machen kannst und solltest. Es gibt verschiedene Motivationen dafür, weshalb man sich bewusst für das Schreiben von schlechtem bzw. nicht wartbarem Code entscheidet:

  • Jobsicherheit Wenn du Code schreibst, den nur du verstehst, dann ist es sehr wahrscheinlich, dass du deinen Job von Seiten des Arbeitgebers aus lange behalten und für das Unternehmen quasi unverzichtbar wirst. Denn wenn nur du deinen Code lesen und ausbauen kannst (bzw. andere zu lange brauchen, um sich in deinen Code hineinzudenken), wird es auch deine Aufgabe sein, damit zu arbeiten. Das ist selbstverständlich eine sehr hinterhältige Taktik und da Programmieren ein Teamsport ist, solltest du dieses Vorgehen (auch um das Wohl deiner Kollegen) tunlichst vermeiden! Diese Vorgehensweise hat natürlich auch Nachteile, denn wenn andere dennoch mehr oder weniger gezwungen werden mit deinem Code zu arbeiten, dann bist du der einzige Ansprechpartner für sie, was dich ggf. von anderen (spannenderen) Aufgaben ablenkt.
  • Schlechtes Vorbild als Lehrstück Vielleicht befindest du dich in einer Rolle, in der du mit der Ausbildung von Azubis betraut bist und dann schadet es nie, schlechte Code-Beispiele aus dem eigenen Unternehmen parat zu haben, um zu zeigen, wie es gerade nicht geht! Der entsprechend nach Anti-Patterns entworfene Code sollte aber nach Möglichkeit nicht in einer Produktivumgebung angewendet werden.
  • Rache Vielleicht möchtest du dich aber auch an deinem Unternehmen für langweilige Aufgaben oder unkollegiales Verhalten rächen. Dann sind Anti-Patterns eine gute Möglichkeit, um die Code-Base eines gesamten Projekts systematisch zu zerstören und trotzdem deine Arbeit zu verrichten! Man sollte aber - wenn man sich wirklich für ein so unkollegiales Verhalten entscheidet - darauf achten, dass die Sabotage nicht auffällt. Code mit plumpen Minifier-Tools oder kryptischen Variablennamen unlesbar bzw. unverständlich zu machen ist nicht zu empfehlen! 

Wie gesagt:  jeder hat seine eigene Motivation, weshalb er sich ggf. für das Schreiben von schlechtem Code entscheidet. Grundsätzlich würde ich dir allerdings von solchen Spielereien abraten und dich lieber darauf fokussieren, wie du "schönen" Code schreibst. Ein Koch versalzt sich auch nicht absichtlich die Suppe und gerade als Informatiker möchte man doch stolz auf seine Erzeugnisse sein.

Aber genug der langen Vorrede! Wir steigen nun direkt in die verschiedenen Sabotagetaktiken - äh - Anti-Patterns ein.


2. Zwiebeltaktik

Viele Programmierer wollen sich nicht in den Code ihrer Kollegen einarbeiten und stülpen deshalb ihre Lösung einfach über die bereits vorhandene Lösung. Insbesondere dann, wenn es um den Einbau von Erweiterungen geht, wird einfach eine komplette Neuimplementierung einiger Features vorgenommen, um sich nicht erst in eine andere Herangehensweise hineindenken zu müssen. Das perfide ist, dass du du viele bereits vorhandene Codestücke einfach mit einer eigenen Lösung versehen und so um den Code der anderen herum eine komplizierte Lösung bauen kannst, die keiner versteht. Über die Jahre entstehen durch diese "Umschiffungslösungen" eigenständige Versionen von ggf. ein und demselben Code, für die jeweils nur ein Entwickler der Experte ist. Das erschwert die Einarbeitung massiv!


3. Durch Methoden getarnter Spaghetti-Code

Spaghetti-Code ist eine gute Möglichkeit, um Code zu verkomplizieren. Oft ist diese Art des Programmierens, bei der man ständig im Code hin- und herspringt das Ergebnis zu weniger Vorüberlegungen. In Code-Reviews wird solcher Code für  gewöhnlich nicht akzeptiert. Du kannst es aber auch geschickt anstellen und den Leser durch eine Vielzahl von Methoden in einen nicht enden wollenden Aufruf-Dschungel führen. Du könntest z. B. extra eine Methode für das Addieren zweier Zahlen, eine Methode zum Erzeugen einer for-Schleife, einer while-Schleife, einer if-Anweisung oder eines Konstruktors schreiben. Wenn dich jemand fragt, wieso du für solche eigentlich in der Sprache bereits vorhandenen Mittel eigene Methoden schreibst, kannst du antworten, dass du diese Vorgehensweise wählst, um duplizierten Code zu vermeiden oder um das einfache Testen via Unit-Tests zu gewährleisten und so dein Programm "testbarer" zu machen. Dass du eigentlich mutwillig Spaghetti-Code hinter einer vermeintlichen Designoptimierung verbirgst, bleibt vielen verborgen.


4. Gott-Objekte

Als Gott-Objekte bezeichnet man in der Objektorientierung Instanzen von Klassen, die "zu viel können". Der Sinn der Objektorientierung ist unter anderem, durch eine saubere Modularisierung Aufgaben so zu verteilen, dass sie zum einen ein Abbild der realen Welt und zum anderen leicht wart- und erweiterbar sind. Wenn man z. B. eine Web-Anwendung baut, bietet es sich an, die Funktionalitäten für die Kommunikation mit der Datenbank, die Kommunikation mit dem Client, den Aufbau der einzelnen Seiten usw. eigenen Klassen zu überlassen. Eine Gott-Klasse könnte z. B. alle diese Aufgaben übernehmen, was sie sehr mächtig und ggf. kritisch für den Betrieb bzw. zu einem Single-Point-of-Failure macht. Außerdem gestaltet sich die Fehlersuche dann sehr schwierig, da du unter Umständen mehrere 1000 Codezeilen in einem File überprüfen und ihre Logik nachvollziehen musst. Wenn diese für das Überleben einer Anwendung geschriebene Gott-Klasse dann auch noch böswillig kryptisch und kompliziert programmiert wurde, wird der weitere Betrieb sehr schwierig. 


5. Lavafluss

Mit dem Lavafluss ist gemeint, dass man viele Leichen im Code hinterlässt, d. h. Code, der nicht mehr genutzt wird, den man nicht löscht und der auch nicht in anderen Code überführt wird. Um den toten Code herum können komplizierte if-else-Konstrukte gestrickt werden, die den  Anschein erwecken, dass der Code noch gebraucht bzw. unter bestimmten Bedingungen reaktiviert werden kann. 


6. Redefluss (Überdokumentation)

Wenn du ursprünglich mal Schriftsteller werden wolltest, wird es dich freuen zu hören, dass du beim Kommentieren von Code für gewöhnlich sehr viele künstlerische Freiheiten hast. So kannst du seitenlange Abhandlungen darüber schreiben, wie ein bestimmtes Codestück funktioniert. Der nette Nebeneffekt: keiner wird deinen Code anfassen, da er sich sonst durch die langen Kommentare "quälen" muss, um die Dokumentation anzupassen. Das schreckt ab und sorgt dafür, dass du mehr oder weniger Narrenfreiheit bei der Implementierung der entsprechenden Codepassagen hast. Dieser Effekt wird dadurch unterstützt, dass bei wirklich langen Dokumentationen ständig zwischen dem eigentlichen Code und dem Kommentar hin und her gesprungen werden muss.

 


7. Copy-Code

Eine weitere Methode, um die Wartbartkeit von Code zu zerschießen, ist das Kopieren von Code-Blöcken. Ich habe mal eine Anwendung erweitern müssen, bei der derselbe Code auf 5 verschiedenen Systemen lief (mit einer hart-codierten Angabe der System-ID) - allerdings wurde der Code nicht von einer zentralen Stelle bezogen, die man einfach nur anpassen musste, sondern es existierte jeweils eine Kopie auf jedem der 5 Systeme. Meine erste Amtshandlung bestand also darin, eine einzige Codequelle zu erzeugen und die System-ID dynamisch einzubinden. Wenn du denselben Code in Methoden mit unterschiedlichen Namen doppelt und x-fach in der Anwendung "versteckst", kaschierst du deine Missetaten. Kopierter Code ist normalerweise ein Indikator dafür, dass du deine Anwendung nicht sauber durchdacht hast.


8. One-Liner

Statt einen komplizierten Algorithmus in einfache Schritte aufzuteilen, sodass sie für jeden verständlich sind, kannst du One-Liner und so wenige Zwischenvariablen wie möglich verwenden. Die folgende Methode zur rekursiven Berechnung der Fakultätsfunktion ist ein gutes Beispiel dafür: 

static int faculty(final int n){return n == 1 || n == 0 ? 1 : n * faculty (n-1);}

Da kommt doch Freude auf!

Du könntest Boolesche Ausdrücke auch mithilfe der Aussagenlogik in lange, komplizierte Aussagen verwandeln.

9. Creepy Code

Code-Obfuscation ist eine tolle Möglichkeit, um den Leser zum Staunen und Nachdenken zu bringen. In Java kannst du mit dem folgenden Code jemanden auf Koreanisch begrüßen: 

public class Obfuscation {/**\u002a\u002f\u0020\u0070\u0075\u0062\u006c\u0069\u0063\u0020\u0073\u0074\u0061\u0074\u0069\u0063\u0020\u0076\u006f\u0069\u0064\u0020\u006d\u0061\u0069\u006e\u0028\u0066\u0069\u006e\u0061\u006c\u0020\u0053\u0074\u0072\u0069\u006e\u0067\u002e\u002e\u002e\u0020\u0061\u0072\u0067\u0073\u0029\u007b\u0053\u0079\u0073\u0074\u0065\u006d\u002e\u006f\u0075\u0074\u002e\u0070\u0072\u0069\u006e\u0074\u006c\u006e\u0028\u0022\u0041\u006e\u006e\u0079\u0065\u006f\u006e\u0067\u0068\u0061\u0073\u0065\u0079\u006f\u0021\u0022\u0029\u003b\u007d}

Oder vielleicht möchtest du auch einen Primzahlentest mit einem regulären Ausdruck durchführen:

public boolean prime(int n){return !new String(new char[n]).matches(".?|(..+?)\\1+");}

All das ist mit Code-Obfuscation möglich. ###### In der Videobeschreibung habe ich dir einen Link zu einem GitHub-Repository verlinkt, in dem du viele solcher Schönheiten finden und in deinem nächsten Projekt verwenden kannst.


10. Doppelte Negationen streuen

Auch bei Booleschen Ausdrücken kannst du tricksen, um deinen Code kompliziert wirken zu lassen. Doppelte Negationen sind dafür ein gutes Beispiel. Statt eine Bedingung im True-Case einfach direkt zu verwenden, kannst du mit sie zuerst negieren und dann erneut negieren.


11. Rekursionen verwenden

Der nächste "Tipp", mit dem du deinen Code noch komplizierter aussehen lassen kannst, ist die häufig (und teils unnötige) Verwendung von Rekursionen. Wie du weißt, lässt sich jede rekursive Funktion iterativ darstellen. Umgekehrt ist das auch möglich! Eine while-Schleife könntest du mit einer Rekursion z. B. implementieren, indem du innerhalb einer Funktion mit einer if-Anweisung die Bedingung prüfst und im Positivfall die Funktion erneut aufrufst. Viele Informatiker können Rekursionen nicht lesen, werden sich in einem Code-Review aber nicht dagegen aussprechen, weil rekursive Lösungen (oft zu Unrecht!) als elegant bezeichnet werden.

Um z. B. die Zahlen von 1 bis hundert auf der Konsole auszugeben, kannst du anstelle einer For-Schleife auch den folgenden "hübschen" Code verwenden:

static int i = 0;

static void  loop() {
    
    if(i < 100){
        i++;
        System.out.println(i);
        loop();
    }
}

12. Magische Werte

Nicht selten werden im Quellcode hart-codiert systemspezifische Werte eingetragen (wie bei meinem zuvor erwähnten Fall mit den 5 Systemen, bei dem einfach die System-ID in den Quelltext eingetragen wurde). Daneben können auch andere Werte wie Benutzernamen und Passwörter oder dedizierte IP-Adressen hart-kodiert im Quellcode hinterlegt werden statt sie aus einer Datenbank auszulesen. Das sorgt für eine Menge "Spaß" und "Sicherheit".


13. Übersynchronisation

Auch wenn deine Anwendung nicht für verteilte Systeme oder ein Multikernsystem ausgelegt ist, kannst du so viele Synchronisationsblöcke und Race-Condition-Checks einbauen, dass es einem beim Lesen schwindelt. Falls dich jemand fragt, wieso du das machst, könntest du argumentieren, dass du bereits vorausdenkst und die Anwendung für eine Anwendung in verteilten Systemen optimierst.


14. Unnötige Komplexität

Um die Komplexität deines Programms zu steigern bzw. zu "overengineeren", kannst du viele Klassen, Superklassen, Interfaces und Vererbung verwenden. Am besten ist dabei die Verwendung kryptischer Namen für die einzelnen Klassen, z. B. "ConcreteMotorizedCarConstructionGeneratorClass". Diese Klassen sollten möglichst viele Interfaces implementieren und im Optimalfall immer von einer anderen Klasse erben.


15. Bestehenden Code verschlimmbessern

Unter dem Vorwand, bestehenden Code optimieren und faktorisieren zu wollen, kannst du die zuvor genannten Anti-Pattern nutzen, um die Code-Base zu zerstören.

Wie gesagt: du solltest solche Anti-Pattern unbedingt vermeiden, wenn nicht eine der zu Beginn dieses Artikels genannten Motivationen auf dich zutreffen.