compow als Privatprojekt in Form einer Referenz wieder online

Eines meiner Projekte, dass ich vor wenigen Jahren gemacht hatte, lag noch auf meiner Festplatte und war bereits lange nicht mehr aktiv. Ich dachte mir allerdings, dass ich das noch einmal gerne als Referenz von mir online stellen wollte: compow.

Bei compow handelt es sich um eine Website, auf der sich Firmen vorstellen können und Stellenanzeigen schalten können. Ursprünglich war das mal kostenpflichtig, was ich aber herausgenommen habe, da ich nicht selbständig bin und damit kein Geld verdiene, es ist lediglich eine meiner Referenzen.

Das Interessante an der Website ist der Tech-Stack, denn anstelle einer der üblichen Webprogrammiersprachen wie Ruby, PHP, Python, Go, ASP.NET (keine Sprache, aber ihr wisst, was ich meine), basiert diese Website auf folgenden Technologien:

Wie gesagt, die Seite dient einfach nur als Referenz, womit ich mich in den letzten Jahren beschäftigte. Die Website ist nicht weiterentwickelt und wird es mitunter auch nicht.

Mini-Kurztipp: LLVM für Windows

Ein wenig peinlich, aber vielleicht geht es dem ein oder anderem Benutzer unixoider Betriebssysteme genauso: auf FreeBSD nutze ich clang(++), auf macOS auch. Auf Linux kann ich das auch. Ich habe jetzt erst bemerkt, dass es auch auf Windows geht. Und noch mehr: die Libs, die am Ende herausfallen, sind mit denen von Visual Studio kompatibel.

LLVM (clang++) auf Windows

Wer es ausprobieren möchte, findet die Downloads hier.

Ein paar Gedanken: Ist statische Code-Analyse und Code-Coverage ein Garant für guten Code und gute Software?

In den letzten Wochen setzte ich mich mit verschiedenen Werkzeugen zur Verbesserung meines Codes und dem Code anderer auseinander. Teilweise laß und hörte ich, dass durch statische Code-Analyse sowie Code-Coverage, einfach gesagt, der eigene Code und die eigene Software besser wird. Dabei hatte ich ein flaues Gefühl im Magen.

Ich sah mir also folgende Dinge an: SonarQube für die statische Analyse von Quellcode, Google-Test mit gcov beziehungsweise llvm-cov für Code-Coverage, aber auch zusätzlich clang-format und clang-tidy, um einheitliche Codeformatierung hinzubekommen und bereits Warnings zur Bearbeitungszeit zu Problemen in meinem Code. Dies versuchte ich mittels VIM und ALE abzudecken, wobei es da zu größeren, bisher ungelösten Problemen kam.

SonarQube

SonarQube gibt es in verschiedenen Lizensierungsmodellen. Neben der Community-Edition, die nur wenige Programmier- und Auszeichnungssprachen beherrscht, gibt es beispielsweise auch die Developer-Edition, die einige mehr kann. Meine Erfahrungen habe ich mit C++-Code versucht, zu machen. Dementsprechend kommt man um die Developer-Edition nicht herum. Diese ist kostenpflichtig und wird nach zu analysierenden Zeilen von Code gezahlt. Das kann schnell teuer werden, zum jetzigen Zeitpunkt, Oktober 2023, kostet das System 150 USD für 100.000 Zeilen Quellcode pro Jahr. SonarQube unterteilt sich in unterschiedliche Programme, sofern man es für C++ (und C und Objective-C) einsetzt: Die Website, auf der die Ergebnisse illustriert werden, den build-wrapper, der aber auch durch compiledb ersetzt werden kann und den sonar-scaner. Die SonarQube-Werkzeuge sind in Java geschrieben und bringen ihr eigenes JRE mit, können aber auch mit einem anderen genutzt werden. Hier ist Vorsicht geboten, sind die Scripte, die die SonarQube-Komponenten aufrufen, in der Shebang doch fest auf /bin/bash gestellt, was auf verschiedenen Betriebssystemen zu Problemen führen kann.

Nicht jedes jedes System wird mit allen Tools unterstützt. Beispielsweise wird macOS arm64 nicht vom build-wrapper unterstützt, so dass man schlussendlich auf compiledb ausweichen muss. Das impliziert: verschiedene Architekturen, verschiedene Tools. Darum soll es hier aber nicht gehen.

Der Vorgang ist folgender: nachdem SonarQube installiert und initial konfiguriert wurde, erstellt man sein erstes eigenes Projekt und erhält einen Token, den man dann beim sonar-scanner entweder auf der Kommandozeile übergibt oder in die SonarQube-Konfiguration einträgt. Dann lässt man sein Make-System einmal mit dem build-wrapper oder compiledb durchlaufen, damit SonarQube weitere Informationen erhält. Zum Schluß wird der sonar-scanner gestartet, der dann die Code-Analyse durchführt, das Coverage verarbeitet, das aber durch andere Tools vorher erstellt werden muss, und die Ergebnisse ins Internet schiebt, so dass sie auf der Website erscheinen.

So, wie ich das verstanden habe, besteht die Analyse aus verschiedenen Teilbereichen. Es wird teils nach echten Bugs geschaut, teils nach sogennanten Code-Smells. SonarQube erkannte tatsächlich sowas:

auto *x = new y();
if (x) {
    //
} else {
    x->method();
}

Aber auch sowas:

const int x = 10;
return x == 10 ? a : b;

SonarQube stufte diese Fehler als Bugs ein und markierte sie als kritisch, was auch gut war. Womit ich nicht zufrieden war, war die Kategorisierung der anderen Regeln. Dies lässt sich in der Konfiguration zwar abändern, aber ich gebe diese zwei Beispiele trotzdem. Bei beiden geht es um sogenannte Code-Smells. Dabei handelt es sich um Code-Konventionen, die keine Bugs sind, sondern dem in SonarQube eingestellten Regelwerk unterliegen und gefixt werden sollten. Im ersten Fall gilt eine Einrückungstiefe innerhalb von Kontrollstrukturen tiefer als drei als kritisch:

if (true) {
    if (true) {
        if (true) {
            if (true) {
            }
        }
    }
}

Im Gegensatz dazu wurde folgender Code “nur” als Major eingestuft, obwohl er meiner Meinung nach kritisch sein sollte:

int var = 10;
if (true)
    int var = 5;

Man sieht klar, dass das Regelwerk für jeden Programmierer oder jedes Projekt oder jedes Team innerhalb verschiedener Konstellationen angepasst werden muss.

Aber kommen wir zur wichtigen Frage: wird mein Code und/oder meine Software durch den Einsatz von statischer Code-Analyse, wie mit SonarQube, automatisch gut?

Meine Antwort darauf ist nein. Das Positive war, dass SonarQube im Projekt zwei potentielle Bugs gefunden hat, das meiste Andere war eher kosmetischer Natur. Dabei kennt SonarQube keinen Kontext. Es prüft nicht, ob Abläufe richtig sind, ob externe Bibliotheken mit hineinspielen, ob Toolkits bestimmte Voraussetzungen haben, an die man sich halten muss und es prüft natürlich auch nicht die Logik oder Algorithmen. Weiterhin verpflichtet SonarQube nicht. Mittels // NOSONAR direkt im Code schaltet man alle Prüfungen für die Zeile ab. Je nach Situation macht das durchaus Sinn, kann aber natürlich auch vom “faulen” Programmierer missbraucht werden.

Ein anderes Problem ist, dass je nach Regelwerk der Code komplexer und auch schwieriger zu lesen wird und der Programmierer mitunter beginnt, an diesem Regelwerk vorbeizuarbeiten. Je nach Firma und Verständnis ist die Übersichtseite (z.B. im Falle von SonarQube) natürlich gut für jeden einsehbar und auch Entscheidungsträger können hier anhand von roten, gelben und grünen Symbolen auf die vermeintliche Qualität der Software schielen und Mitarbeiter dementsprechend beurteilen. Somit könnte der Programmierer natürlich versuchen, gewisse, nervende Regeln zu umgehen. Ein Beispiel: In SonarQube dürfen Klassen aus nicht mehr als 35 Methoden und 20 Membern bestehen. Spätestens bei GUIs kann das schnell zum Problem werden. Diese Klassen dann aufzusplitten, erhöht den Aufwand, die Komplexität und bietet aber mitunter keinen vernünftigen Nutzwert. In diesem Fall könnte der Programmierer beginnen – bei C++ – mit inneren Klassen und/oder Structs zu arbeiten, um dieser Problematik zu entgehen, da er sicher nicht die gesamte GUI-Datei in der Konfiguration ausklammern möchte. Dabei könnte dann folgendes herauskommen:

class MyGUI : public GUI {

public:
    struct UI {
        Button b0;
        Button b1;
    };
    UI ui;

};

Das stimmt SonarQube an der Stelle glücklich. Wie man aber sieht, wird der Code selbst umfangreicher.

Die Qualität kann zunehmen, muss sie aber nicht. Für mich ist an der Stelle statische Codeanalyse ein Werkzeug für mich, aber absolut kein Garant für guten Code, noch für gute Software.

Code-Coverage

Code-Coverage ist vom Wording meiner Meinung nach nicht ganz korrekt. Test-Code-Coverage oder ähnlich sollte es eher immer heißen.

Damit ist gemeint, dass mit Testszenarien (Unit-Tests, Intergrations-Tests, …) eine gewisse Menge Code getestet wird, also abgedeckt ist. Nimmt man hier als Beispiel gcov, ist es recht einfach. Man kompiliert sein Programm mit –coverage und führt es aus. Beim Kompilieren und Ausführen werden verschiedene Parameter gesammelt. Wichtig hier ist, welche Zeile im Code aufgerufen wird. Wird sie mindestens einmal aufgerufen, gilt sie als abgedeckt.

Hier sieht man, dass mitunter Funktionen oder Methoden selbst nicht explizit aufgerufen werden, sondern implizit und dennoch gelten sie als gecovert. Auch sagt Code-Coverage nichts darüber aus, wie sinnvoll ein Test ist, ob es nicht noch weitere Tests geben müsste, ob wirklich alles getestet ist und damit, ob die Qualität der eigenen Software verbessert wurde.

Noch ein Wort zu Formattern und Lintern

Beide Teile, also Formatter und Linter, sind spätestens im Team natürlich durchaus praktisch. Der Linter zeigt an, wo es noch Probleme bei Konventionen im Code geben könnte. Hier funktioniert clang-tidy recht gut. Der Formatter formatiert den Code so, dass er für alle gleich ist. Sprich, er achtet auf Einrückungen und ähnliches.

Fazit

Also, was denke ich? Wenn man diese Programme nutzt, als das, was sie sind, können sie dem Programmierer durchaus nützlich sein. Selbst, wenn man aus verschiedenen Gründen nicht alle Regeln einhalten kann oder möchte, regen sie doch zum Nachdenken an. Aber sie sichern selbst nicht die Qualität des Codes. Das muss der Entwickler selbst tun, wobei es sich hier um einen iterativen Prozess handeln kann.

Diese Tools dürfen aber niemals zum Einsatz gegen den Programmierer genutzt werden, sondern bei Problemen sollte dieser immer erst befragt werden.

wxWidgets auf Windows per Kommandozeile mit Visual Studio kompilieren

Wer schnell und einfach wxWidgets auf Windows auf der Kommandozeile kompilieren will, öffnet ein Visual Studio Terminal, also ein Terminal, in dem die Umgebungsvariablen für VS gesetzt sind, navigiert zu den Sourcen von wxWidgets und dort ins Verzeichnis build\msw und kann via nmake den Kompilationsprozess in Gang setzen:

nmake /f makefile.vc BUILD=release SHARED=0 TARGET_CPU=X64 RUNTIME_LIBS=static

Wir sehen am Beispiel, dass wir eine Release-Version von wxWidgets bauen, also keine Debug-Informationen drin sind (ansonsten release einfach durch debug tauschen). SHARED=0 bedeutet, dass wir keine dynamischen Libraries bauen möchten (wenn doch, einfach weglassen). Die restlichen Parameter sollten ebenso selbsterklärend sein.

KooKooK (Update 1): Ein paar Implementierungen ohne Video

Ich habe überlegt: ich kann leider nicht alles zu dem Projekt aufnehmen. Ich habe noch ein paar Erweiterungen gemacht. Was alles, seht ihr im GitHub-Projekt.

Hier eine Liste:

  • Über die Server.ini kann man den Server jetzt teils konfigurieren
  • Einfaches Logging wurde eingebaut
  • Threads können beendet werden
  • Kompilierungsscript wurde erweitert
  • Unix-Signal-Handling wurde implementiert (TERM und HUP)

Schreibt mir gerne bei Fragen und Anregungen.

wxWidgets-Tutorial 007 – wxWidgets auf macOS – App-Bundles, Icons und Deployment

Dieses Video zeigt, wie ihr einfach App-Bundles inklusive Icons für eure wxWidgets-Programme auf macOS erstellen könnt. Ebenfalls gehe ich auf den dylibbundler ein, um ein einfache Deployment durchzuführen.

wxWidgets auf macOS - App-Bundles, Icons und Deployment
wxWidgets auf macOS – App-Bundles, Icons und Deployment

Hier noch die Dateien:

Info.plist

<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>       <string>English</string>
	<key>CFBundleExecutable</key>              <string>wx</string>
	<key>CFBundleInfoDictionaryVersion</key>   <string>6.0</string>
	<key>CFBundlePackageType</key>             <string>APPL</string>
	<key>CSResourcesFileMapped</key>           <true/>
	
	<key>CFBundleVersion</key>                 <string>0.0.1</string>
	<key>CFBundleShortVersionString</key>      <string>0.0.1</string>
	
	<key>CFBundleName</key>                    <string>My wxApp</string>
	<key>CFBundleIconFile</key>                <string>AppIcon</string>
</dict>
</plist>

Script zum Kompilieren

#!/bin/sh

PROGRAM="wx"
BUNDLE="wx.app"
ICON="AppIcon.icns"

test -d "${BUNDLE}" && rm -Rf "${BUNDLE}"

c++ *.cpp -o wx -std=c++11 `/usr/local/Cellar/wxWidgets/3.2.1/bin/wx-config --libs --cppflags`
if [ $? == 0 ]
then
	mkdir -p "${BUNDLE}/Contents/MacOS"
	echo -n 'APPL????' > "${BUNDLE}/Contents/PkgInfo"
	mv "${PROGRAM}" "${BUNDLE}/Contents/MacOS/"
	cp "Info.plist" "${BUNDLE}/Contents/"

	mkdir -p "${BUNDLE}/Contents/Resources"
	cp "${ICON}" "${BUNDLE}/Contents/Resources/"

	mkdir "${BUNDLE}/Contents/lib" && ln -s "${BUNDLE}/Contents/lib" "${BUNDLE}/Contents/libs"
	/usr/local/Cellar/dylibbundler/1.0.4/bin/dylibbundler -od -b -x "${BUNDLE}/Contents/MacOS/${PROGRAM}" -d "${BUNDLE}/Contents/lib"
fi

Programmcode

#include <wx/wx.h>

class MyApp : public wxApp {

	public:
		bool OnInit();

};

class MyFrame : public wxFrame {

	public:
		MyFrame();

};

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit() {
	MyFrame *myFrame = new MyFrame;
	myFrame->Show();
	SetTopWindow(myFrame);

	return true;
}

MyFrame::MyFrame() : wxFrame(nullptr, wxID_ANY, "Meine wx-App") {
	wxStaticText *staticText = new wxStaticText(this, wxID_ANY, "Hello World");
}

Hier geht es zum Video.