Wenn Software-Updates mehr kaputt machen als sie reparieren: Ein persönlicher Erfahrungsbericht mit GIMP 3.0.4

Mit GIMP 3.0.4 wurde aus einem stabilen Werkzeug ein Bug-Monster: blockierte Shortcuts, verwaiste Dialoge, kaputte Export- und Font-Funktionen. Der Release wirkt wie KI-generierter Code ohne echte Tests – ein Beispiel dafür, dass KI, CI/CD und Automatisierung keine menschliche Qualitätskontrolle ersetzen können.

Vor wenigen Minuten habe ich über 1 Stunde 30 Minuten damit verloren, der GIMP-Community auf GitLab mehrere Bugs zu melden. Normalerweise mache ich das gerne, wenn ich auf Fehler stoße – schließlich lebt Open-Source-Software davon, dass Nutzer:innen Rückmeldung geben. Aber diesmal war es anders: Die Anzahl, Art und Systematik der Bugs in GIMP 3.0.4 geben mir ein sehr ungutes Gefühl.

Vor dem Update lief alles perfekt

Mit der Version vor 3.0.4 war ich rundum zufrieden. Alles funktionierte reibungslos, die Bedienung war konsistent, und es gab keine Stolperfallen im Workflow. Eigentlich hätte es kein Update gebraucht. Doch aufgrund des Splashscreens mit der neuen Versionsbenachrichtigung habe ich das Update trotzdem eingespielt – und plötzlich ging nichts mehr wie zuvor.

Ein Update voller Stolperfallen

Die Liste der Fehler ist lang, hier nur ein Auszug:

  • GIMP hat plötzlich einen globalen Keyboard Hook, der selbst außerhalb von GIMP alle Shortcuts wie Ctrl+C, Ctrl+V oder Ctrl+S blockiert (VS Code, VS 2022, Android Studio, Linqpad, Notepad++ usw.)
  • Mehrere Dialogfenster verlieren ihren Kontext und werden zu „verwaisten“ Fenstern, die sich unkontrolliert vervielfachen.
  • Im Export-Dialog wächst Text nicht nach rechts, sondern nach links aus dem Fenster hinaus – und die restliche UI reagiert nicht mehr.
  • Die Font-Auswahl und Größenänderung verhält sich grotesk: Statt den Wert im Feld zu ändern, landet meine Eingabe im eigentlichen Bildtext. (Mein Verdacht: die Beschreibung für Code-generierender KI war nicht eindeutig und missverständlich und dadurch hat der KI die Textfelder von Font-Eigenschaften mit dem Text im Bild (Layer) verwechselt. Das passiert oft und ist typisch für Coder mit sehr wenig Erfahrung in Coding und Software-Engineering)
  • Nach dem Rotieren oder Einfärben von Ebenen entstehen nach dem Zuschneiden plötzlich verschobene Layer-Kopien.

Das sind keine Kleinigkeiten, sondern fundamentale Fehler, die das Arbeiten massiv behindern.

Mein Verdacht: KI-Code ohne echte Tests

Die Häufung und Art dieser Bugs weckt bei mir einen starken Verdacht:

  • Es wirkt, als wären viele Code-Änderungen automatisiert entstanden, möglicherweise mit Hilfe von KI-generiertem Code. Z.B. durch unzureichende, nicht-eindeutige und missverständliche Beschreibung für KI, wurde ein Code generiert der zwar mit kein(e) einzige(r) Compiler-Fehler/Warnung übersetzt werden (und laufen) kann, aber sich bei der Nutzung völlig falsch verhält.
  • Darauf aufbauend hat man sich blind auf CI/CD-Pipelines verlassen, ohne dass echte Menschen die Software in Alltagsszenarien getestet haben.
  • Das Ergebnis: ein Release, das wie ein Krebs-Geschwür über die Community ausgerollt wurde.

Natürlich ist das nur mein Eindruck als Nutzer (mit 17+ Jahren Erfahrung in Software-Engineering und Entwicklung). Aber die Systematik der Fehler lässt kaum an Einzelfälle glauben.

KI ersetzt weder Menschen noch Tests durch Menschen

Ich möchte hier einen Punkt betonen: KI kann helfen, Code schneller zu schreiben oder Vorschläge zu liefern. Aber ohne menschliche Qualitätskontrolle, manuelles Testen und kritisches Hinterfragen wird jede Software zur Zumutung. Continuous Delivery (CD) und Continuous Integration (CI) sind mächtige Werkzeuge – aber sie ersetzen nicht das Verständnis für Usability und händisches Durchklicken, das echte Fehler sichtbar macht.

Fazit

Software-Updates sollten Verbesserungen bringen, nicht Regressionen. Im Fall von GIMP 3.0.4 war das Gegenteil der Fall. Ich musste auf die vorherige Version zurückrollen, weil grundlegende Dinge nicht mehr funktionierten.

Mein Appell an alle Entwickler:innen (egal ob Open Source oder kommerziell):

  • Nutzt KI, aber lasst sie nicht allein entscheiden.
  • Vertraut auf CI/CD, aber ergänzt sie mit echten Tests durch Menschen.
  • Denkt an eure Nutzer:innen – sie verlieren sonst Stunden ihrer Lebenszeit für Dinge, die gar nicht hätten passieren dürfen.

Buzzword-Bingo: Der überteuerte „Kaffeevollautomat mit künstlicher Intelligenz“ und die „natürliche Dummheit“

„Seit jeher verkaufen Geschäftsleute das, von dem Techniker schon immer geträumt haben.“
Anonym

Letztes Jahr, noch weit vor der Weihnachtszeit, bevor der Weihnachtsstress überhaupt erwähnt wurde, schickte mir mein Bruder einen Link zu einem „Kaffeevollautomaten mit künstlicher Intelligenz„.

Ein Kaffeeautomat mit KI? Wozu?“ fragte ich mich, und ich begann gleichzeitig neugierig und aufmerksam, die Texte zu dieser überteuerten Kaffeemaschine zu lesen. Die Webseite eines Online-Shops pries den Hersteller für seine angeblichen Innovationen in der KI-Technologie und den neuen Schritten in der Digitalisierung… und da dachte ich nur „OMG! Wieder einmal hat die Marketingabteilung oder ein einfallsloser Manager zu Marketing-Gimmicks gegriffen und Schlangenöl als Neuheit verkauft…

Ich war verärgert! Wie kann man nur seine Kunden für dumm verkaufen und sie derart belügen?

Statt mich nur zu ärgern, lenkte ich meinen Ärger um und entwickelte schnell eine einfache Webseite, die eine solche „KI-Kaffeemaschine“ simuliert. Dazu fügte ich eine Konsole mit kurzen Erläuterungen hinzu, was gemacht wird und warum – damit auch diejenigen, die nicht aus der IT-Welt stammen, es verstehen können.

Ich bin gespannt, wann die Taschenlampe mit Big Data, Blockchain, NFT und KI, die von einem Kernfusionsreaktor angetrieben wird, anstelle von Batterien/Akkus, angepriesen wird.

Wer wissen möchte, um welchen Hersteller es sich handelt, kann im Web nach „Kaffeemaschine mit künstlicher Intelligenz“ suchen. Im Simulator befindet sich auch ein Hinweis.


Wie drei Zeilen eines gewöhnlichen Algorithmus die Illusion von künstlicher Intelligenz bei überteuerten Geräten zerstören:

1. Zuerst werden zum Beispiel 6 verschiedene Kaffeesorten festgelegt. Für jede dieser Kaffeesorten wird ein Zähler für die jeweiligen 6 Tasten festgelegt. Es ist auch möglich, mehr Tasten und/oder Kaffeesorten festzulegen:

const coffeeList =
    [
        { id: 0, name: "Americano", count: 0 },
        { id: 1, name: "Mocca", count: 0 },
        { id: 2, name: "Cappuccino", count: 0 },
        { id: 3, name: "Latte", count: 0 },
        { id: 4, name: "Türkisch", count: 0 },
        { id: 5, name: "Frappé", count: 0 }
    ];

2. Danach wird für jede Kaffeesorte ein Knopf erstellt, der auch als Button oder Schaltfläche bezeichnet wird. Dem Mausklick-Mechanismus dieses Knopfes wird eine Methode zugewiesen, die den Zähler, auch als Counter bezeichnet, um eins erhöht und diesen Wert dann wieder in die Zähler-Variablen schreibt. Anschließend wird die Methode aufgerufen, die die angezeigten Knöpfe aktualisiert, um die Ansicht zu erneuern:

function increment(id) {
    // Finde das Kaffee-Objekt durch die ID (welche von der Taste geliefert wurde):
    const coffee = coffeeList.find((x) => x.id == id);

    // Zähler für den Kaffee/Button inkrementieren:
    coffee.count++;

    // Inkrementieren des Zählers und das Auffrischen der Anzeige:
    updateView(coffee);
}

3. In der Methode „updateView(coffee)“ wird eine weitere Methode aufgerufen, um zu überprüfen, ob eine Aktualisierung der Anzeige (der Knöpfe/Schaltflächen) erforderlich ist oder nicht. Diese Aktualisierung erfolgt nur dann, wenn sie notwendig ist.

function hasToUpdateView() {
    for (i = 0; i < 6; i++) {
        if (i < 5)
            if (coffeeList[i].count < coffeeList[i + 1].count) {
                return true;
            }
    }
    // Nein! Keine Änderung ==> Auffrischen nicht notwendig.
    return false;
}

4. Anschließend wird in der Methode „updateView(coffee)“ fortgefahren. Wenn die Antwort der Methode „hasToUpdateView()“ positiv ist, das bedeutet, wenn sie „true“ zurückgibt, dann werden die Knöpfe so lange nach links verschoben, bis der Wert des linken Knopfes höher oder gleich ist wie der Wert des ausgewählten Knopfes.

function updateView(coffee) {
    const btn = document.getElementById("btn" + coffee.id).innerText = coffee.name + "\n" + coffee.count + "x";
    if (hasToUpdateView()) {
        sortByCount();
        createButtons();
    }
}

Der vollständige Code hier nochmal zusammenhängend:

/*******************************************************
 *           J̳ava U̳nique R̳uthless A̳utomat 0.8.15       *
 *      Kaffevollautomat mit Künstlicher Intelligenz   *
 *       Copyright (C)2024 by Pedram GANJEH-HADIDI     *
 *******************************************************/
var shell = "***************************************\n* J̳ava U̳nique R̳uthless A̳utomat 0.8.15 *\n***************************************\n";
const ruler = "---------------------------------------";
var clickCounter = 0;
const coffeeList =
    [
        { id: 0, name: "Americano", count: 0 },
        { id: 1, name: "Mocca", count: 0 },
        { id: 2, name: "Cappuccino", count: 0 },
        { id: 3, name: "Latte", count: 0 },
        { id: 4, name: "Türkisch", count: 0 },
        { id: 5, name: "Frappé", count: 0 }
    ];

window.addEventListener('load', function () {
    const textArea = document.getElementById("txaShell");
    textArea.innerHTML = shell;
    createButtons();
    const w = window.screen.availWidth;
    const h = window.screen.availHeight;
    const e = document.getElementById("txaShell");
    e.setAttribute("width", w);
    e.setAttribute("height", h / 2);
});

window.addEventListener('resize', function () {
    createButtons();
});

function increment(id) {
    const coffee = coffeeList.find((x) => x.id == id);
    writeLog(`${++clickCounter}. T͟a͟s͟t͟e͟n͟d͟r͟u͟c͟k͟: Sie habe die Taste für\n${coffee.name} Kaffee geklickt. Nun wird die Anzahl dieser Taste hochgezählt!\n&#10233; ${coffee.count} + 1 = ${++coffee.count}`);
    updateView(coffee);
}

function updateView(coffee) {
    const btn = document.getElementById("btn" + coffee.id).innerText = coffee.name + "\n" + coffee.count + "x";
    if (hasToUpdateView()) {
        sortByCount();
        createButtons();
    }
}

function writeLog(str) {
    shell += str + "\n";
    const textArea = document.getElementById("txaShell");
    textArea.innerHTML = shell;
    textArea.scrollTop = textArea.scrollHeight;
}

function writeLine() {
    writeLog(ruler);
}

function hasToUpdateView() {
    for (i = 0; i < 6; i++) {
        if (i < 5)
            if (coffeeList[i].count < coffeeList[i + 1].count) {
                writeLog(`V͟e͟r͟g͟l͟e͟i͟c͟h͟e: ${coffeeList[i].name}: ${coffeeList[i].count} ≧ ${coffeeList[i + 1].name}: ${coffeeList[i + 1].count} ❌\nDa Links nicht größer oder gleich Rechts ist\n&#10233; Tasten neu ordnen!`);
                writeLog(`T͟a͟s͟t͟e͟n͟p͟o͟s͟i͟t͟i͟o͟n͟e͟n͟ a͟k͟t͟u͟a͟l͟i͟s͟i͟e͟r͟e͟n͟: Die Taste\nfür ${coffeeList[i + 1].name} so oft wie notwendig nach Links verschieben...`);
                writeLine();
                return true;
            }
    }
    writeLog("V͟e͟r͟g͟l͟e͟i͟c͟h͟e: Die Anzahl der Tastendrücke Links ist überall größer oder gleich der Taste Rechts davon ⭐\n&#10233; Keine Änderung notwendig.");
    writeLine();
    return false;
}

function sortByCount() {
    coffeeList.sort((a, b) => (a.count < b.count) ? 1 : -1);
}

function createButtons() {
    document.getElementById("coffeeButtons").innerHTML = addButtons();
}

function addButtons() {
    let str = "<table><tr>";
    for (i = 0; i < 3; i++) str += getButtonText(coffeeList[i]);
    str += "</tr>\n<tr>";
    for (i = 3; i < 6; i++) str += getButtonText(coffeeList[i]);
    return str + "</tr></table>";
}

function getButtonText(coffee) {
    return '<td><button id="btn' + coffee.id + '" type="button" class="coffeeButton" onclick="increment(' + coffee.id + ')" title="Klick here f&uuml;r ein Kaffee aus &quot;">' + coffee.name + "<br>" + coffee.count + "x</button></td>";
}

Über 100 Downloads in Zwei Wochen: Meine .NET-Bibliothek „Revertible“

Ich bin begeistert, bekannt zu geben, dass meine .NET Standard 2.0 Bibliothek „Revertible„, die ich vor zwei Wochen auf NuGet veröffentlicht habe, bereits über 100 Mal heruntergeladen wurde. Diese Bibliothek erlaubt es, Properties, die mit dem RevertibleAttribute gekennzeichnet sind, einfach auf ihren ursprünglichen Zustand/Wert zurückzusetzen.

Anfangs war ich unsicher, ob es einen Bedarf für eine solche Funktion gibt. Aber die Nachfrage in der .NET-Community hat meine Erwartungen übertroffen. Ich freue mich, dass mein Projekt Anklang findet und hilfreich für Entwickler ist.

Für weitere Informationen besuchen Sie das NuGet-Paket hier und das GitHub-Projekt hier.

To comment, or not to comment – that is the question

Eine kurze Geschichte übers Kommentieren und Kommentare

Ich wünsche die Kollegen beim Neuschreiben des Codes in C, C++, C#, Java,… oder WasAuchImmer++ viel Geduld, Kraft und Nervenstärke.
Wenn Kommentare genauso kryptisch sind, wie der Code
Was machen diese 15 Zeilen?
Man kann die Kommentare übersetzen und erahnen was diese Zeilen tun

Back to the Future…
Kommentare sind wichtige Informationen für die Zukunft.
Kommentare sind wichtige Informationen aus der Vergangenheit.
Kommentare sind Notizen/Erklärungs-Ergänzungen für sich selbst, Kolleginnen und die Nachwelt (neue Kolleginnen, wenn man nicht mehr in der Firma (oder am Leben) ist).

Ich lese immer wieder Sätze mit Rufzeichen am Ende, wie „Jede Code-Zeile muss kommentiert werden!“, oder „Pro Methode, mindestens drei Zeilen Kommentare!“ usw.

An der Uni hieß es „Alles muss kommentiert werden!“.

Dann hieß es (eine Show namens „Linux Nerds vs. Geeks“ glaube ich) „Wenn man schönen/guten Code schreibt, braucht man keine Kommentare“ (sagte der Linux Grafik-Programmierer-Guru!).

Irgendwann sagte ein ehemaliger Kollege „Kommentare verwirren nur“ und dass „Nur Amateure und Doofis kommentieren“ …

Mein derzeitiger Standpunkt ist:

  1. Extrem ist und bleibt extrem. Daher entscheide ich mich für die goldene Mitte.
  2. Bevor ich Kommentiere stelle ich mir folgende Fragen:
    1. Muss ich Kommentieren?
    2. Reicht der Name nicht?
    3. Ist der Name gut gewählt?
    4. Sollte ich nicht lieber den Namen ändern?
    5. Wie groß ist der Informationsgehalt des Kommentars?
    6. Bietet der Kommentar eine zusätzliche oder ergänzende Information, die sonst nicht (oder sehr schwer) zu lesen/sehen/wissen (Code) wäre?
    7. Erleichtert/Verkürzt der Kommentar die Suche/Recherche oder das Verständnis des Algorithmus?
    8. Lässt der Kommentar Raum für falsche Interpretationen zu?
    9. Hilft der Kommentar Fehler zu minimieren, oder verursacht es mehr Fehler durch Missverständnis?

Nehmen wir den (C#) Code von Unten als Beispiel und stellen wir uns gemeinsam pro Kommentar die obigen neun Fragen unter Punkt zwei:

public class Person
{
    /// <summary>
    /// Gets/Sets the ID of Person.
    /// </summary>
    public int ID { get; set; }

    /// <summary>
    /// Gets/Sets the first/given name of Person.
    /// </summary>
    public string FirstName { get; set; }

    /// <summary>
    /// Gets/Sets the last/second name of Person.
    /// </summary>
    public string LastName { get; set; }

    /// <summary>
    /// Gets/Sets the day of birth of Person.
    /// </summary>
    public DateTime BirthDay { get; set; }

    /// <summary>
    /// Returns the whole/full name beginning with FirstName then LastName in uppercase.
    /// </summary>
    /// <returns></returns>
    public string GetName()
    {
        // todo: check!
        return $"{FirstName} {LastName.ToUpper()}"; // NG! PGH 2021-07-05 V0.8.15
    }
}

Die Property-Namen sagen eindeutig, unmissverständlich und klar aus, was sie beinhalten, und deren Kommentare haben/bringen keine zusätzliche/ergänzende Informationen… Also ⇒ Weg damit!

Der Kommentar von der Methode „GetName“ möchte, und MUSS, etwas erklären, was der Name leider nicht tut. Zusätzlich verwirrt dieser Kommentar.
Innerhalb der Methode „GetName“ steht ein „todo“ Kommentar, was kein Mensch entschlüsseln kann: „check!“ Check-What? Was soll gecheckt/überprüft werden FirstName, LastName oder Return-Value (string)?
Am Ende der Return-Zeile steht wieder kryptische Buchstaben-Suppe:

  1. NG!“ steht für…??? No Guns? No Game? Next Generation? Not Good? No Grade? Network Grid?
  2. Danach steht „PGH„, meine Initialien plus Datum und anscheinend eine Version:
    1. Gibt es für sowas Versionsverwaltungs-Systemen (CVS, SVN und GIT). Dort kann man all das lesen, von Anbeginn der Zeit (Projekt-Geburt) bis zum letzten Commit: Wer (Name, E-Mail-Adresse) hat, wann (Datum + Uhrzeit mit Millisekunden) was (Verzeichnis, Datei, Zeile) in welche Version geändert (gelöscht, hinzugefügt, umbenannt, ergänzt, entfernt,…) hat.
    2. Was sollen die Kolleginnen damit anfangen? Mehrwert? Informationsgehalt?

Das Ganze könnte man auch so schreiben:

public class Person
{
    public int ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDay { get; set; }

    /// <summary>
    /// Returns full name, like: "John DOE", "Alice BOYLE".
    /// </summary>
    /// <returns></returns>
    public string GetFullName()
    {
        return $"{FirstName} {LastName?.ToUpper()}";
    }
}

Ich habe diese Beispiele nicht aus der Luft gegriffen.
Ähnliche, und teilweise noch schlimmere Fälle aus der Praxis (was mir unter die Augen gekommen sind) werde ich noch hinzufügen. Entweder hier oder im neuen Beitrag.

Englisch, Deutsch oder Denglisch?

Der Kunde zahlt fürs Code-Schreiben und nicht fürs Code-Lesen!

In einer Firma, wo alle Software-Entwickler und IT’ler Deutschsprachige waren, hat der Inhaber & CEO uns befohlen alle Kommentare (nur Kommentare) auf Deutsch zu schreiben. Ein Jahr später, nachdem das Projekt im Verzug war und die Entwicklung des Software-Systems nicht schnell genug vorangegangen war, wurden drei neue Software-Entwickler aus Spanien eingestellt. Die neuen Kollegen waren alle sehr gute Software-Entwickler und beherrschten souverän ihr Fach, jedoch verstanden kein Wort Deutsch. Danach wurden noch mindestens fünf weitere Software-Entwickler, ebenfalls aus Spanien, eingestellt…
Nun hatten wir alle Hände voll zu tun. Wir drei Deutschsprachigen müssten alle Kommentare nun auf Englisch übersetzen und ersetzen, anstatt unsere Aufgaben nachzugehen.

In eine andere Firma, hatte ich das „Vergnügen“ Code auf Denglisch zu lesen. Manche Namen waren Deutsch, manche waren Englisch, und der Rest eine beliebige Mischung aus Deutsch und Englisch. Dass alle Programm-Elemente (Interfaces, Klassen, Delegates, Events, Methoden, Properties etc.) in Microsoft .NET Frameworks auf US-Englisch waren, ist natürlich trivial…

Das Lesen der Code war extrem schwierig und eine Herausforderung an/für sich. Beispiel für Variable-Namen:
messValue, measWert, measValue, messWert (diese Namen standen für: measured value, bzw. gemessener Wert).
Auch wenn man alle Namen auf Deutsch schreibt, die Hälfte von Code ist und bleibt auf Englisch, da die Schlüsselwörter und alles Andere in MS .NET Frameworks in US-Englisch ist, wie: string.IsNullOrEmpty(…), PropertyChanged, File.Exist(…), using(var x = new MemoryStream(…)), usw. usf.

Deswegen behaupte ich: Es kann kein Code auf „Deutsch“ geschrieben werden, wenn man das versucht, dann entsteht immer ein Code auf „Denglisch“. Man stelle sich nur vor, jemand würde Denglisch schreiben oder reden:
„I war very froh to sehen you nochmal“.

Ich verwende gerne US englische Namen (Color statt Colour, Synchronize statt Synchronise, usw.). Somit bleibt alles einheitlich, unmissverständlich, eindeutig, klar und der Lese-Fluss bleibt sehr flüssig.

Denglisch kostet mehr Zeit, mehr Zeit zum Lesen und mehr Zeit zum Verstehen.
Je öfter der Code von je mehr Kollegen (wieder)gelesen wird, desto mehr Zeit wird verschwendet!
Zeit ist Geld. Somit verursacht Code auf Denglisch unnötige zusätzliche Kosten.

Der Kunde zahlt fürs Code-Schreiben und nicht fürs Code-Lesen!

Naming System & Code lesen

Der Kunde zahlt fürs Code-Schreiben und nicht fürs Code-Lesen!

Um Code schneller lesen und durchsuchen zu können, habe ich im Laufe der Zeit, mein System für die Benennung einige Programmelemente verfeinert:

  • Interfaces beginnen mit „I„, wie: IAuthor, IBook, IPublisher,…

  • Abstrakte Klassen beginnen mit „A„, wie: AAuthor, ABook, APublisher,…

  • Enums beginnen (bei mir) mit „E„, wie: „EOperationMode“, „EError“, „EDeviceType“, „EPeriodKind“,…

  • Helper-Klassen enden mit „Helper„, wie: „FileHelper“, „CommandHelper“, „SecurityHelper“,…

  • Model-Klassen (MVC, MVVM) enden mit „Model„, wie: ServiceModel, MachineModel, ConfigModel, ProductModel, CategoryModel,…

  • DTO-Klassen enden mit „DTO„, wie: PersonDTO, ProductDTO,…

  • POCO-Klassen enden mit „POCO„, wie: PersonPOCO, ProductPOCO,…

  • Delegates enden mit „Handler„, wie: CommandInvokedHandler, ValueChangedHandler, SafeModeActivationChangedHandler, StreamOpeningHandler, StreamOpenedHandler, StreamClosingHandler, StreamClosedHandler, UserLoggedInHandler, UserLoggedOutHandler,…

  • Event-Handling-Methoden beginnen mit „On“ plus Event-Name, und können -wenn passend- mit „ed“ enden, also: „On<EventName>ed„, wie: OnCommandInvoked, OnValueChanged, OnSafeModeActivationChanged, OnStreamOpening, OnStreamOpened, OnStreamClosing, OnStreamClosed, OnUserLoggedIn, OnUserLoggedOut, OnMachineReplaced, OnErrorOccured,…

  • Funktionen/Methode, die etwas Berechnen und eine Zahl zurückgeben beginnen mit „Calc“ oder „Calculate“ (und NIEMALS mit „Get“!), wie: CalculateCircleSurface(…), CalculateStandardDeviation(…) oder kurz: CalcStdDeviation(…)

  • Methoden die asynchron laufen enden mit „Async“, wie: CalcStdDeviationAsync(IEnumerable pTooManyNumbers),…

  • Boolsche Properties beginnen mit „Is„, „Has„, „Use„, „Contains“ udg., wie: „IsEnabled“, „IsReady“, „IsEmpty“, „HasElement“, „ContainsError“, „ContainsNull“, „UseForceMode“, „DoBackup“,…

  • Properties die Exception werfen können, werden NICHT als Properties, sondern mit „Get…()“ und „Set…()“ implementiert (so wie es die C# Sprach-Designer Anders Hejlsberg, Bill Wagner & Co vorgesehen haben), denn ein Property sollte niemals Exceptions werfen, schon gar nicht der Getter. Ausgenommen sind die Indexer (wegen IndexOutOfRangeException).

  • „Get…“ Methoden welche Exceptions abfangen und behandeln enden mit:
    • OrNull“ wenn bei Exception NULL zurückgeliefert wird
    • OrDefault“ wenn bei Exception Default-Wert (z. B. default(int)) zurückgeliefert wird

  • Member-Variablen beginnen mit „m“ oder „m_„, wie: mFirstName oder m_FirstName

  • Parameter beginnen mit „p“ wie: CalcRectSurface(int pWidth, int pHeight), SetTopLeft(int pX, int pY),…

  • Konstanten werden GROSS_GESCHRIEBEN: DEFAULT_VALUE, MAX_VALUE,…

Der Beispiel-Code unten erfüllt die oben erwähnten Regeln.
Versuche herauszufinden: Welcher Name ist ein Property, welcher ein Parameter, welcher eine Member-Variable und welcher eine Konstante:

Konstanten, Parameter, Properties etc. sind eindeutig zu erkennen

Syntax Highlighting & Code lesen

Der Kunde zahlt fürs Code-Schreiben und nicht fürs Code-Lesen!

Die Zeiten wo alles in Grün auf schwarzem Hintergrund stand (Borland Turbo Pascal/CPP Editor udg.) sind lange vorbei.
IDEs wie Visual Studio & Co bieten von Haus aus Syntax Highlighting.
Die Einfärbung der Schlüsselwörter, Kommentare, Typen etc. machen das Lesen von Code leichter, flüssiger und angenehmer.
Für mich jedoch reichte die Einfärbung für C# in Visual Studio nicht.
Ich möchte schnell auf einem Blick, ohne zu lesen erkennen, ob bei ein Programm-Element (in C#) es sich um ein Interface, Klasse, Enum, Delegate, Event oder „Magic Number“ handelt.
Dazu habe ich für Interfaces, Delegates, Enums und Zahlen eigene Farben zugewiesen (siehe Bild 1!)

  1. Kommentare sehe ich deutlicher (Dunkelgrün statt Standard Leichtgrau)
  2. Enums sind eindeutig sofort zu erkennen (Orange)
  3. Delegates sind ebenfalls eindeutig zu erkennen (Violett)
  4. Interfaces haben eine andere Farbe als (abstrakte) Klassen oder Structs
  5. Events sind Braun
  6. Magic Numbers“ haben bei mir keine Chance, da sie sofort erkannt werden 😉
  7. Unterscheidung zwischen Enums und Konstanten sind ebenfalls leichter
Bild 1


So gelangt man zu Farben- und Font-Style von Programm-Elementen in Visual Studio (Bild 2):

Bild 2

Color Theme & Code lesen

Seit Visual Studio 2012 wird „Dark Theme“ (Tools → Options → Environment → General → Color Theme) als Standard verwendet.
Das hat den Vorteil, dass die Augen mit weniger Licht belastet werden.
Jedoch ist es so, dass „Dark Theme“ sich eher für Grafik, 3D und Animations-Editoren eignet, um die Tool-Leisten weg- und das Bild/Modell (Bild, 3D Objekt udg.) in die Mitte zu rücken (dadurch wird automatisch die Fokussierung auf das Bild/Modell geleitet). Für das Lesen und Schreiben von Text (hier Code) ist es eher ungeeignet (laut Augen-Mediziner). Die Augen müssen sich mühsam auf die hellen Buchstaben fokussieren. Vor allem, wenn durch große Fenster, was heutzutage modern ist, das Sonnenlicht auf dem Bildschirm fällt, oder über helle Flächen auf dem Bildschirm reflektiert wird.


Deshalb probiert es selbst aus!
Nachdem ich eine Zeit lang extreme Augen- und Kopfschmerzen hatte, und deshalb zum Augenarzt gehen müsste, bin ich auf dem „Light“ Theme umgestiegen. Das reflektierte Sonnenlicht ist seitdem kein Thema (für mich) mehr.
Für detaillierte Informationen, siehe hier und hier!
Anmerkung: bei OLED und AMOLED Bildschirm, wird oft Dark Theme zwecks Energiesparen eingesetzt.

Augen & Code lesen

Der Kunde zahlt fürs Code-Schreiben und nicht fürs Code-Lesen!

Wenn wir ein Buch, eine Zeitung, ein Magazin oder nur ein A4 Blatt lesen, dann halten wir es so, dass die obere Kante etwas weiter hinten ist, und die untere Kante näher zu uns ist. Beim Lesen nach Unten bewegen sich die Augen weiter nach Innen (zu Nase), und beim Lesen nach Oben bewegen sich die Augen auseinander.
Wir halten ein Buch niemals parallel vor unserem Kopf, wir neigen den Kopf etwas nach Unten.
Kurze Zeilen unter einander machen den Lese-Fluss leichter.
Lange Zeilen stören den Lese-Fluss, da die Augen sich auch horizontal bewegen müssen. Bei sehr lange Zeilen muss sich sogar noch der Kopf sich horizontal drehen.
Je mehr Augenmuskeln beansprucht werden, desto früher wird man Müde (Kopf-Schmerzen). Man kann sich schlecht konzentrieren/fokussieren.
Deshalb ist das Lesen während einer Zug- oder Busfahrt (für längere Zeit) nicht ratsam.

Das gilt auch für Code lesen. Deshalb sollte darauf geachtet werden das beim Code schreiben, die Zeilen möglichst kurz und untereinander stehen.

// Hindert den Lesefluss
if ((a > B && C < D) && !(x == y || y != z)) dataCommunicator.SomeMethod(a, b, c, true);
// Noch mehr Code...

// Macht den Lesefluss leichter
if ((a > B && C < D) && !(x == y || y != z))
    dataCommunicator.SomeMethod(a, b, c, true);
// Noch mehr Code...

Naming-Conventions

Nomen est omen
Namen spielen beim Schreiben von Code eine essenzielle Rolle.
Gut gewählte Namen können viele Grübeleien, Verwirrungen und Missverständnisse sowie Fehler vermeiden, und machen den Code leichter lesbar.
Gute Namen sind:

  • Selbsterklärend
  • Klar
  • Eindeutig
  • Unmissverständlich
  • Einprägsam
  • Sagen genau was das Ding tut oder enthält, nicht mehr und nicht weniger
  • Müssen nicht kommentiert werden

Zum Beispiel hier (C# Code):
Der Klassen-Name sagt aus „Ich enthalte Geometrie-Methoden“, die erste Methode sagt „Ich berechne eine Kreisfläche, wenn du mir die Radius-Länge gibst“, usw.
Man braucht nicht (mit F12 ins Visual Studio) sich den Code innerhalb einer Methode zu lesen, um zu wissen, was es tut.

    public class GeometryCalculator
    {
        public double CalculateCircleSurface(double radiusLength)
        {
            return radiusLength * radiusLength * Math.PI;
        }

        public double CalculateSquareSurface(double sideLength)
        {
            return sideLength * sideLength;
        }
    }

Nomen est omen… eben.

Ugly vs Clean Code (zum Aufwärmen)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Ugly.Code
{
    public class GeoCalc
    {
        public double GetCS(double value)
        {

            double val1;
            double calc;


            val1 = value;
            calc = Math.Pow(val1, 2) * Math.PI;


            return calc;


        }

        public double GetSS(double value)
        {

            double val1;
            double calc;


            val1 = value;
            calc = Math.Pow(val1, 2);


            return calc;
        }

        // Some other methods...
    }
}

Warum ist der Code oben „Ugly“?

  1. Ungenutzte „using“ Zeilen
  2. Der Klassen-Name sagt nicht, was die Klasse enthält/anbietet/tut
  3. Die Methoden-Namen sagen nicht, was sie tun oder berechnen/zurückliefern
  4. Die Parameter-Namen sagen nicht, was sie enthalten
  5. Die lokalen Variable-Namen sagen nicht, was sie enthalten
  6. Lokale Variablen kaschieren die Parameter und dessen Werte, machen den Algorithmus schwer verständlich
  7. Unnötige Leerzeilen verlängern unnötig die Methode. Man muss mehr scrollen und mit den Augen rauf & runterschauen.

Die Klasse „GeoCalc“ von Oben, könnte man auch so schreiben:

using System;

namespace Clean.Code
{
    public class GeometryCalculator
    {
        public double CalculateCircleSurface(double radiusLength)
        {
            return radiusLength * radiusLength * Math.PI;
        }


        public double CalculateSquareSurface(double sideLength)
        {
            return sideLength * sideLength;
        }

        // Some other methods...
    }
}