Stranger Than Usual

Kontinuierlich Schrittweise

Heute habe ich diese Illusion in den Weiten des Netzes gefunden:

Zwei kurze, waagerechte Streifen (blau und gelb) bewegen sich parallel zueinander über einen Hintergrund mit regelmäßigen senkrechten schwarz-weißen Streifen. Obwohl sie sich gleichmäßig bewegen entsteht die Illusion, dass sie sich abwechselnd schrittweise bewegen. Gegen Ende der Animation wird der Hintergrund einheitlich grau und man kann klar erkennen, dass sich die Streifen gleichmäßig bewegen.

(Die Animation steht unter der CC BY-SA 4.0-Lizenz und wurde von Rinuraeni erstellt).

Ich weiß nicht, ob das für jeden Beobachter gleich aussieht, aber für mich sieht es so aus, als ob sich die farbigen Streifen abwechselnd Schrittweise bewegen. Tatsächlich bewegen sie sich aber kontinuierlich mit gleicher Geschwindigkeit. Wenn ich einen der Streifen abdecke und mich auf den anderen konzentriere, kann ich das auch sehen, bevor es am Ende mit grauem Hintergrund offensichtlich wird.

PS: Die ursprüngliche Datei war eine 296 kiB-große GIF-Datei. Als animiertes WebP ist sie nur noch etwa 42 kiB groß.

Rollenspielszenen: Bassbombe

Rollenspielszene. Wir sind die Band „Spice of Life“, die eigentlich eine Gruppe von Spionen der Wasted Lands ist, die jetzt auf Tournee durch verschiedene ikarische Militärlager reist.

Wir wissen seit einiger Zeit, dass das ikarische Militär (oder der Geheimdienst?) einen Anschlag auf uns plant, um publikumswirksam für den Krieg gegen die Wasted Lands zu werben. Wir sind gerade in einem Feldlager nahe der Front angekommen. Wir vermuten, dass der Anschlag hier passieren soll, weil die Gelegenheit am Günstigsten ist. Der Lead Singer ΦΦ (ausgesprochen: „Fiffi“) schafft es, mit seinen dressierten Ratten ein versiegeltes Dokument zu ergattern, in dem der Plan für unseren Tod steht: Während unseres Auftrittes soll eine Bombe auf der Bühne explodieren.

Wir machen einen Gegenplan. Wir suchen die Bombe, bringen sie ins Zelt des Lagerkommandeurs und machen uns mit dem Dokument aus dem Staub. Dazu müssen wir nur erst einmal die Bombe finden. Fiffi will schon wieder seine Ratten losschicken, doch Anis wirft nur einen Blick über die Bühne, geht zum Bassverstärker und identifiziert ihn korrekt als das Bombenversteck.

Der Spielleiter dazu:

Der Gedankengang dahinter war: Musiker überprüfen vor dem Auftritt immer ihr Equipment, aber Bassisten sind halt keine Musiker.

2026: So schnell vergessen

2026 ist schon wieder zu einem Drittel um. Und was für ein Jahr war es bisher. Weltpolitisch ein Scheißjahr, womit es ich gut in die Reihe der vorherigen Jahre einreiht. Ich möchte hier noch einmal die Aufmerksamkeit auf das enfant terrible, Donald Trump, lenken. Nicht, weil der nicht genug Aufmerksamkeit bekommt (die kriegt er, mehr als genug), sondern weil er es immer wieder schafft, dass wir seinen bisherigen Bullshit aus den Augen verlieren, weil er woanders einen neuen Haufen hingesetzt hat.

Akt 1: Venezuela

Wie fing das Jahr an? Donald Trump ist in ein fremdes Land einmarschiert, hat den Präsidenten / Diktator von Venezuela Nicolás Maduro entführt und sich hingestellt und behauptet, dass das Öl in Venezuelas Boden den USA gehört und er vollen Zugriff darauf haben möchte.

Ich habe jetzt kein besoneres Mitleid mit Maduro, aber dieser Angriff war nach meinem Verständnis definitiv völkerrechtswidrig. Und die Ausbeutung des Venezuelanischen Öls ist auch nicht so einfach, wie sich Trump das vorstellt.

Oh, und eine anonyme Person hat 400000 $ damit gemacht, auf die Entführung Maduros zu wetten. Ein Schelm, wer Böses dabei denkt.

Akt 1 Grönland (zweiter Handlungsstrang)

Parallel dazu hat Donald Trump erneut seinen Bullshit mit Grönland wieder angefacht. Obwohl die USA bereits militärische Basen auf Grönland haben und sogar nocht mehr bauen dürften, behauptet er, die Sicherheit der USA hingen davon ab. Er droht praktisch mit einem Angriff auf Grönland (offiziell Teil von Dänemark), was einen Angriff auf seine eigenen Nato-Verbündeten bedeuten würde. Grönland und Dänemark waren not amused. Wie das der Sicherheit der USA nutzen soll, ist unklar.

-Möglicherweise ging es auch hier wieder um Rohstoffe: Wenn Trump den Klimawandel weiter so aggressiv vorantreibt, sind Grönlands Rohstoffvorkommen bald viel besser zu erschließen.

Am Ende haben mehrere anderen Nato-Staaten (u.a. auch Deutschland) kleinere Gruppen ihrer Armeen nach Grönland gesandt, vermutlich hauptsächlich um den Amis zu demonstrieren: Wenn ihr Grönland angreift, greift ihr uns alle an.

Akt 2: Minnesota

Noch während sich die Sache mit Grönland hinzieht, belagert Trumps SA (auch bekannt als ICE) die Stadt Minneapolis. Friedliche Demonstranten werden getötet, Menschen werden entführt, Kinder werden als Geiseln genommen, um ihre Eltern hervorzulocken. Am Ende lässt Trumps Regime die Leute abziehen, ohne allerdings irgendein Fehlverhalten einzugestehen

Akt 3: Die Epstein-Akten

Nachdem das Justizministerium unter Trump dazu verdonnert wurde, die Epstein-Akten zu veröffentlichen und das mit einiger Verzögerung und einiges an Schwärzungen auch getan hat und dann einige der Schwärzungen zurücknahm, stehen auch Vorwürfe gegen Donald Trump im Raum. Während ein Europa einige ehemalige Politiker und ein ehemaliger Prinz genau untersucht werden, passiert bei Trump jedoch nicht viel. Trotzdem ist es natürlich schlecht für sein Image, also braucht er eine Ablenkung. Und da kommt sie auch schon:

Akt 4: Der Angriffskrieg auf den Iran

Ich habe schon gesagt, dass ich nichts über diesen Krieg schreiben will. Irans herrschendes Regime kann gerne zur Hölle fahren, aber die Auslöschung einer ganzen Zivilisation anzudrohen, wie es Trump tut, ist schon ohne es durchzuführen ein Kriegsverbrechen.

Dass Trump damit die Weltwirtschaft in eine Krise gestoßen hat, ist eigentlich nur eine Nebensache. Für Trump ist es natürlich die Hauptsache, weil es ihn schlecht dastehen lässt. Also gibt er anderen die Schuld, gibt vor, zu verhandeln, stellt Ultimaten, die er immer wieder verlängert (wobei damit mal wieder Insiderhandel betrieben wird) und führt sich auf die ein Kleinkind, dass nicht die Schokolade kriegt, die es will.

Worauf ich hinaus möchte

Das alles ist noch nicht so lange her. Es ist alles in den ersten vier Monaten dieses Jahres passiert. Trotzdem ist in der breiten Berichterstattung außer von dem Irankrieg davon kaum noch zu hören. Weil Trump den stinkenden Haufen, den er hingesetzt hat, immer wieder mit einem anderen Haufen überdeckt. An den ganzen Scheiß, der letztes Jahr passiert ist, garnicht zu denken.

Wir dürfen aber nicht vergessen, was Trump alles gemacht hat. Und das gilt nicht nur für Trump, sondern für alle seiner Art. Und insbesondere müssen wir (als Welt, aber insbesondere als Europa) uns merken, dass man den USA unter Trump nicht vertrauen kann. Und dass die Politik in den USA auch ohne Trump so weit entglitten ist, dass man einer gemäßigten Regierung nur innerhalb ihrer Legislaturperiode trauen kann, weil danach wieder der nächste größenwahnsinnige Rassist an die Macht kommen kann. Wir müssen es so ähnlich halten wie die Ölkonzerne mit Bezug auf Venezuela: Dort zu investieren ist eine schlechte Idee, weil es so instabil ist, dass morgen alles verloren sein kann.

Insbesondere bedeutet das auch, das wir kein Geld in Palantir, eine Spionagesoftware geben dürfen. Nur zwei der Gründe:

Ebensowenig dürfen wir US-Behörden erlauben, politische Ansichten, sexuelle Orientierung oder „Herkunft“ von uns abzufragen. Idealerweise sollten wir diese Daten selber nicht halten. So etwas ist brandgefährlich, solche Listen haben schon die Nazis genutzt. Wenn so eine Liste erst einmal besteht und in die falschen Hände gerät, dann gute Nacht. Dann lieber kein vereinfachtes Visumsverfahren für die USA.

Aber noch einmal das Wichtigste: Wir dürfen nicht vergessen, was Trump alles angestellt hat. Und wir dürfen uns nicht daran gewöhnen, es darf nicht das neue „normal“ werden, was gerade passiert. Weder bei Trump noch bei Russland noch bei der AfD, und nicht einmal bei dem, was die CDU/CSU bei uns gerade treibt.

Strengeres Clippy

Es gibt da ein Zitat, dessen Urheber ich nicht mehr finde, deswegen gebe ich es hier sinngemäß wieder (das Original war meine ich auf Englisch):

cargo clippy ist für diejenigen, die eine gewisse Leere fühlen, wenn der Rust-Compiler endlich ihren Code akzeptiert.

Der Rust-Compiler ist notorisch dafür, pingelig zu sein (was ich allerdings in diesem Fall für eine gute Eigenschaft halte). Cargo clippy (benannt nach der berüchtigten MS-Office-Büroklammer aus den 90ern) ist ein Linter für Rust, der unschöne unsaubere oder einfach uneinheitliche Codestellen findet, auflistet, und einige davon auch (nach Aufforderung) selbst korrigieren kann.

Clippy hat schon per default einen Haufen Regeln, und ich benutze es schon seit vielen Jahren. Bisher aber immer nur mit den Standardeinstellungen. Bis ich dann letzte Woche durch „This Week in Rust“ auf zwei Blogposts aufmerksam geworden bin: Your Clippy Config Should Be Stricter und als Antwort darauf, von einer anderen Person Your Clippy Config Should Be Stricter-er.

Im ersten Post geht es hauptsächlich darum, welche Lints man aktivieren sollte, wenn man nicht aus Versehen Stellen, die ein panic!() machen können, im Code haben will, plus ein paar anderer Lints für potentielle fallen. Der zweite Post ist eine Antwort darauf, und der Autor vertritt die Ansicht, man solle einfach ganze Listen von Lints freischalten und die, die man nicht haben will, wieder deaktivieren. So hat man eine allow-list und kann keine potentiell nützlichen Lints übersehen.

Ich wollte das einfach einmal ausprobieren und habe dazu direkt mein größtes aktives Rust-Projekt genommen: Den Generator für dieses Blog.

Pedantisch

Als erstes habe ich die „pedantischen“ lints aktiviert, indem ich diese Zeilen in meine Cargo.toml eingefügt habe:

[lints.clippy]
pedantic = { level = "warn", priority = -1 }

Alle weiteren Änderungen, die ich hier an der Cargo.toml vornehme, werden auch in dem lints.clippy-Block stehen.

Die pedantischen Regeln haben sofort zu einem Haufen Fehler geführt. Für einige davon gab es automatische Fixes, für andere nicht. Ich gehe jetzt nicht genauer auf alle Regeln ein, aber ich habe zum Beispiel ein paar hilfreiche Funktionen entdeckt. So kann man map().unwrap_or_else() an einer Option durch ein map_or_else() ersetzen.

Es waren auch ein paar Lints dabei, die theoretisch ein Problem sein könnten, aber praktisch nicht. Wie zum Beispiel ein type casting mit as von einem u64 in ein usize. Hier ging es um Dateigrößen. Praktisch gesehen werde ich keine Dateien haben, die mehr als 4 GiB groß sind (falls usize nur 32 bit groß ist), aber ich habe den Lint trotzdem drin gelassen, grundsätzlich ist er nützlich. Auch ein casting von u64 auf f64 wurde wegen möglichen Präzisionsverlustes bemäkelt. In diesem Fall habe ich die Regel ignoriert, weil ich die Zahl sowieso gerundet habe. Nicht global ignoriert, sondern nur für diese Stelle, damit ich auch an anderen Stellen gewarnt werde. Dazu habe ich am Anfang der Funktion, in der die Warnung auftrat, diese Zeile hinterlassen:

#[expect(clippy::cast_precision_loss, reason = "precision loss not a problem here")]

Ich habe expect genutzt anstatt allow, damit ich eine Warnung kriege, falls diese Ausnahme jemals überflüssig wird (z.B. weil ich den Code geändert habe). Ich finde ich es praktisch, dass man direkt einen Grund angeben kann. Auf diese beiden Details bin ich aber erst durch weitere Lints aus einem anderen Set aufmerksam geworden, aber dazu weiter unten mehr.

Die einzige pedantische Regel, die ich komplett deaktiviert habe, war die Längenbeschränkung für Funktionen. Meiner Erfahrung nach bringen willkürliche Längenbeschränkungen für Funktionen mehr Ärger als Nutzen. Eine lange, aber übersichtliche Funktion möchte ich nicht aufteilen, nur um dem Linter gerecht zu werden. In meinem Code war zwar auch nur eine Funktion davon betroffen, aber ich habe die Regel lieber global deaktiviert, mit folgender Zeile in der Cargo.toml:

too_many_lines = "allow"

Abgesehen davon waren die Regeln eigentlich allesamt recht brauchbar.

Restriktionen

Als nächstes habe ich die restrictions lints aktiviert:

restriction = { level = "warn", priority = -2 }

Und meine Güte. Die sind wirklich nicht dafür geeignet. Die erste Warnung, die man bekommt, ist, dass man diese Regeln nicht en bloc aktivieren sollte. Ich habe das mal ignoriert. Insgesamt sind bei mir seitenweise Warnungen aufgetaucht, von zusammengenommen 40 Regeln (darunter auch die Regel, dass man diese Regeln nicht en bloc aktivieren soll). Nur 11 von den Regeln habe ich behalten, den Rest habe ich deaktiviert. Ich habe die Regeln, die ich behalten habe, auch explizit aktiviert, falls mir die restrictions-Liste irgendwann mal zu nervig werden sollte, dann behalte ich wenigstens die Regeln, die ich für brauchbar befunden habe.

Brauchbare Regeln

Diese Regeln habe ich behalten, teilweise weil ich sie wirklich gut fand, teilweise weil ich sie nicht zu nervig fand:

  • allow_attributes verhindert, dass Regelausnahmen mit [allow()] mache, ich muss stattdessen except nehmen
  • allow_attributes_without_reason erzwingt, dass ich bei Regelausnahmen einen Grund angeben muss
  • ref_patterns erzwingt, statt etwas wie if let Some(ref a) = b etwas wie if let Some(a) = &b zu schreiben
  • default_numeric_fallback verhindert, dass bei Integerliteralen der Fallbacktyp verwendet wird, man muss immer einen Typ angeben
  • str_to_string statt str::to_string() soll lieber str::to_owned() verwendet werden
  • doc_paragraphs_missing_punctuation sorgt für einheitliche Strukturen in Doc-Kommentaren (in diesem Projekt eher unwichtig, schadet aber nicht)
  • create_dir statt create_dir lieber create_dir_all verwenden, das äquivalent zu mkdir -p. Würde ich nicht immer empfehlen, aber für dieses Projekt schon
  • arithmetic_side_effects markiert Stellen mit potentiellen Overflow-Effekten, Divisionen durch 0 bei arithmetischen Operatoren (+, -, *, /). Ich habe es mal aktiviert, obwohl meine Zahlen klein genug sind, dass eigentlich keine Überläufe passieren können. Ich habe stattdessen strict_add und vergleichbare Funktionen verwendet, das panicked bei Overflows. Für anderen Code würde ich vielleicht eher checked_add oder overflowing_add verwenden, je nachdem welches Verhalten gewünscht ist. Ein Nachteil: Arithmetische Ausdrücke sind danach viel schwieriger zu lesen.
  • wildcard_enum_match_arm: Wenn man kein catch-all beim Enum-Matching haben will. Hilft, damit neu hinzugefügte Enum-Varianten nicht übergangen werden. Für die Fälle, wo ich wirklich catch-alls haben wollte, habe ich Ausnahmen gesetzt.
  • as_conversions verhindert Typkonversionen mit as, die potentiell zu Fehlern führen. Auch hier habe ich mich entschieden, dass mein Programm im Fehlerfall lieber panicken soll. Eine Ausnahme habe ich hinzugefügt, weil ich keine Ahnung hatte, die ich sonst verlustbehaftet von u64 zu f64 casten sollte.
  • unwrap_used verhindert, dass man unwrap verwendet. Definitiv eine gute Regel. Wenn überhaupt, sollte man expect verwenden, dann kriegt man noch eine Information dazu, warum man meinte, dass ein Wert Some() oder Ok() sein sollte. Ich habe tatsächlich ein paar expects im Code, weil ich z.B. einen Formatter verwende, der in einen String-Buffer schreibt, was nicht schiefgehen kann.

Ein unwrap bin ich bei der Gelegenheit auch eine schöne Art und Weise losgeworden. Ich hatte zunächst eine match-expression die ungefähr so aussah:

let description = match blogposts.len() {
    0 => "Keine Blogposts".to_string(),
    1 => format!(
        "Blogposts vom {}",
        blogposts[0].0.date.format(DATE_FORMAT_GERMAN)
    ),
    _ => format!(
        blogposts[0].0.date.format(DATE_FORMAT_GERMAN),
        blogposts.last().unwrap().0.date.format(DATE_FORMAT_GERMAN)
    )
};

last() kann unten nie None zurückgeben, weil die Länge mindestens 2 sein muss. Aber unschön ist es trotzdem. Da ist mir eingefallen, dass es ja mittlerweile slice patterns gibt. Dasselbe Problem lässt sich also viel schöner so lösen:

let description = match blogposts {
    [] => "Keine Blogposts".to_owned(),
    [only] => format!("Blogposts vom {}", only.0.date.format(DATE_FORMAT_GERMAN)),
    [first, .., last] => format!(
        "Blogposts vom {} bis zum {}",
        first.0.date.format(DATE_FORMAT_GERMAN),
        last.0.date.format(DATE_FORMAT_GERMAN)
    ),
};

Gegebenenfalls brauchbare Regeln

Das sind Regeln, die ich in manchen Situationen für brauchbar halte, aber nicht in diesem Projekt hier:

  • single_char_lifetime_names verbietet Lifetime-Namen wie 'a. Für ein neues Projekt vielleicht eine gute Idee, hier nicht.
  • unneeded_field_pattern verhindert, dass man beim destructuring eines structs unbenutzte Felder ausschreibt anstatt .. zu verwenden. Ich habe die mal deaktiviert, weil ich im Zweifelsfall sicher sein will, dass ich kein Feld übersehen habe. .. kann ich ja trotzdem nutzen, wenn ich will
  • missing_docs_in_private_items macht Doc-Kommentare für private Elemente verpflichtend. Halte ich in dieser Codebasis nicht für Sinnvoll
  • module_name_repetitions verhindert, dass man Worte in der Module-Hierarchie doppelt verwendet, z.B. file::FileData. Grundsätzlich keine schlechte Idee, aber Data als Datentyp war mir dann doch zu generisch
  • min_ident_chars verhindert Bezeichner, die nur aus einem Zeichen bestehen. Ich verwende solche aber gerne in sehr kurzen Ausdrücken, wo die Bedeutung des Namens nicht verloren gehen kann. Ich muss dann halt manuell aufpassen, dass sich die Variable i nicht doch über die ganze Funktion zieht.
  • if_then_some_else_none verlangt, dass man etwas wie if v.is_empty() { Some(42) } else { None }; stattdessen let a = v.is_empty().then(|| {42}); ausschreibt. Ich finde die erste Variante doch ein bisschen einfacher zu lesen (zumindest, wenn sie umgebrochen ist)
  • absolute_paths: verbietet absolute Pfade für Bezeichner im Code, man solle die lieber in die use-statements packen. Könnte ich drüber nachdenken, aber gerade mit den ganzen verschiedenen Result-Typen gelangt man da schnell in Namenskonflikte.
  • indexing_slicing verbietet, direkte slice-Operationen à la a[i]. Gut, um sich vor panics abzusichern. Mein Code ist aber nicht kritisch, in diesem Fall habe ich die Regel deaktiviert
  • print_stdout verbietet print! und println!. Hilfreich, wenn man keine Ausgabe mit ´println!haben möchte. In diesem Fall ist es aber eine Kommandozeilen-App mit wenig Ausgabe, und daher will ich meinprintln!` schon haben
  • expect_used analog zu unwrap_used: verbietet expect() bei Result und Option. Gut, wenn man keine panics haben möchte. Für dieses Projekt habe ich aber, wie oben beschrieben, eine Handvoll expects, mit denen ich klarkommen muss.

Regeln für Nischenfälle

Und zum Schluss noch ein paar Regeln, die ich überhaupt nicht haben möchte bzw. die für sehr enge Nischenfälle gedacht sind:

  • missing_trait_methods verhindert, dass man ein trait implementiert ohne alle Funktionen des traits zu implementieren. Eine Hölle, wenn man Iteratoren implementiert. Die Doku sagt, man solle es am besten nur für Einzelfälle aktivieren (also für einzelne Trait-Implementierungen)
  • implicit_return verbietet impliztes return am Ende der Funktion. Da implizites return aber in den Standard-Clippy-Regeln erzwungen wird, bleibe ich lieber bei den Standardregeln.
  • mod_module_files verbietet mod.rs-module files. Ich komme mit dieser Struktur aber gut klar
  • std_instead_of_alloc verpflichtet, für bestimmte use statements aus alloc und nicht aus std zu importieren. Gut für std-only-crates
  • std_instead_of_core s.o. nur, für core anstatt alloc
  • unused_trait_names möchte, dass man traits nur nicht-anonym importiert, wenn man sie beim Namen verwendet, nicht, weil man sie nur für die Nutzung des traits importiert werden. Ich sehe den Nutzen hier nicht wirklich.
  • arbitrary_source_item_ordering erzwingt, dass man im Quellcode eine bestimmte Reihenfolge einhält, z.B. alle structs for alle Funktionen. Im Zweifelsfall verschlechtert das aber imho eher die Übersichtlichkeit.
  • shadow_reuse Ich mag das shadowing-Verhalten von rust. Wem das zu gefährlich ist, kann gerne diese Regel aktivieren
  • single_call_fn warnt vor Funktionen, die in der Codebasis nur ein Mal genutzt werden. Aber für mich ist es strukturell schon manchmal sinnvoll, eine solche Funktion zu schreiben.
  • non_ascii_literal erlaubt nur Ascii-Zeichen in Stringliteralen. Falls es Leute gibt, deren Editor kein UTF-8 kann. Mal im Ernst: wir habe 2026. Wenn euer Editor nach über 30 Jahren UTF-8 immer noch kein UTF-8 unterstützt, ist das nicht mein Problem. Wäre aber vielleicht für sicherheitsrelevanten Code interessant, um Homoglraph-Attacken zu verhindern.
  • pattern_type_mismatch irgendwas mit borrows und pattern matching. Hier vertraue ich aber einfach dem Compiler, dass der sich beschwert, wenn es ein Problem gibt
  • integer_division ich weiß, dass bei Integer-Division der Rest verloren geht, danke.
  • integer_division_remainder_used ist als Schutz gegen timing-Seitenkanalangriffe gedacht. Für Kryptographiecode. Das ist eine wichtige Nischenanwendung, aber halt eine Nischenanwendung und in meinem Code hier irrelevant.
  • impl_trait_in_params verhindert die impl Trait-Syntax in Funktionsparametern. Ich finde impl Trait eigentlich ganz praktisch, deswegen: raus mit der Regel
  • unseparated_literal_suffix dazu gibt es eine Gegenregel separated_literal_suffix. Man kann sich maximal eine aussuchen.
  • iter_over_hash_type verbietet iteratoren über ungeordnete Datenstrukturen (z.B. HashSet). Ich weiß aber, wie HashSets funktionieren und will über die Werte iterieren, danke.
  • float_arithmetic verbietet Fließkommazahlrechnungen. Praktisch für Mikroprozessoren, die keine FP-Hardware haben und das alles Simulieren müssen (mit extremen Performanceeinbußen). Also wieder nur eine Nischenanwendung.

Fazit

Ich habe an einigen Stellen meinen Code stabiler und sauberer gemacht. ob ich die restriction-Regeln weiterhin en bloc drin lasse oder nicht, muss ich mir aber noch überlegen. Ein paar von den Regeln sind jedoch ganz in Ordnung.

Bilder im Darkmode

Ich habe ja Ende Februar einen Darkmode für dieses Blog eingeführt. Weil ein paar Bilder hier aber transparente Teile haben und nur auf einem hellen Hintergrund funktionieren, habe ich für die Bilder grundsätzlich einen hellen Hintergrund eingestellt.

Das hat natürlich ein paar Nachteile. Insbesondere werden halt Bilder, die keinen hellen Hintergrund brauchen, trotzdem mit einem dargestellt. Das widerspricht der Idee des Darkmode.

Die meisten meiner Bilder hier haben sowieso keine transparenten Teile. Die Bilder mit transparenten Teilen brauchen nicht alle einen hellen Hintergrund. Also habe ich mich entschieden, den hellen Hintergrund optional zu machen. Bilder, die einen hellen Hintergrund benötigen, werden in den Metadatan speziell markiert, ihre <img>-Tags kriegen eine besondere Klasse und können dadurch im CSS anders dargestellt werden.

Man kann das zum Beispiel im Post über das Winkekatzen-Favicon gut sehen. Die Winkekatze ohne Rahmen hat dort einen hellen Hintergrund, die Winkekatze mit Rahmen, die sowohl bei hellen als auch bei dunklen Hintergründen funktionieren soll, hat keinen hellen Hintergrund.

Für den nicht-Darkmode ändert sich übrigens nichts.

LMAA 🦙

Letzte Woche ist mir auf Mastodon „LMAA“ untergekommen. Nein, nicht was ihr denkt (oder was ich denke, was ihr denkt, leckt mich am Arsch). Sondern Lokale Märkte. Alternative Anbieter. Offiziell wird das „Lama“ ausgesprochen.

Die Seite ist eine durchsuchbare Liste von Online-Shops (oder typischerweise Offline-Shops, die auch Versand online anbieten). Wenn man also nicht bei Amazon, AliExpress oder einem anderen großen Moloch bestellen möchte und der Versand aus der Nähe erfolgen soll, kann man dort suchen. Man kann auch selber Shops vorschlagen. Nach eigenen Angaben werden alle Shops, die vorgeschlagen werden, überprüft, ob sie den Kriterien entsprechen.

Wenn ihr also etwas online bestellen möchtet: Schaut nach, ob es da etwas Passendes gibt. Vielleicht ist sogar ein passender Laden in eurer Nähe, und ihr könnt da auch so vorbei. Umgekehrt: Wenn ihr einen guten Onlineshop kennt (oder selber betreibt), der noch nicht gelistet ist und die Aufnahmekriterien erfüllt, schlagt ihn vor. Damit können den auch andere finden, und ihr unterstützt einen Shop, mit dem ihr selber schon gute Erfahrungen gemacht habt.

Logbuch: Netzpolitik hat in der letzten Folge auch auf diese Seite verwiesen, im Rahmen der Berichterstattung über den Digital Independence Day. Unter Gekicher beim Nennen des Namens, natürlich.

Webbloat reduzieren

In den letzten Wochen habe ich beruflich an einer Angular-App gearbeitet. Der Kunde nutzt überall Angular, und möchte das auch hier nutzen. Der Kunde hat auch schon eine Bibliothek mit ordentlichen Komponenten, passend zum Corporate Design. Das Projekt ist für mich insofern ungewöhnlich, dass ich sonst nie reine Frontendarbeit mache, sondern sonst immer Backend und Frontend. Aber das ist hier irrelevant.

Wir haben die Entwicklung also vor knapp vier Wochen gestartet. Vorher war die App leer. Die App macht bisher auch nicht viel, der Kunde will erst einmal einen Klickdummy für eine Schulung, und die Backend-Endpunkte (wie gesagt, ein anderes Team) sind sowieso noch nicht fertig. Bzw. es wurde noch nicht damit angefangen (ja, ich weiß, das wird höchstwahrscheinlich noch… interessant).

Diese Woche habe ich mal darauf geschaut, wie groß das Javascript unserer App ist. Fuck! 1,5 MiB. Dabei macht die App praktisch noch nichts. Keine komplizierten, ungewöhnlichen Protokolle in den Abhängigkeiten, wie z.B. bei der Winkekatzenseite der Fall ist. Nur Standard-Angular und die Komponentenbibliothek, die der Kunde bereitstellt (von der nur ein Bruchteil tatsächlich genutzt wird).

So schnell geht es also, das Javascript (und CSS) unnötig aufzublähen. Seit ich auf so etwas achte (auch bei Websites, an denen ich nicht selber arbeite, ich bin das schon fast so obsessiv wie mit Datenkompression) habe ich mich immer gefragt, wieso so viele Seiten so groß sind, obwohl ihr Content das bei weitem nicht rechtfertigt. Meine aktuelle Hypothese: Es ist zu einfach. Man nimmt ein bisschen Bequemlichkeit (ein JS-Framework hier, eine Komponentenbibliothek da) und – schwupps! Alles ist riesig, und der größte Teil davon wird nicht gebraucht. Und niemand bezahlt einen dafür, das JS deutlich kleiner zu machen. In der Regel geht es um zwei Dinge: Features, und wieviel Zeit (und damit Geld) es kostet, sie zu implementieren.

Ich habe trotzdem einen gewissen Stolz, und möchte dieses JS deutlich kleiner kriegen. Ich weiß noch nicht genau wie, aber es muss gehen. Privat kriege ich das ja auch hin (meist, weil ich überhaupt kein Javascript und keine CSS-Frameworks verwende). Und ich möchte hier ein Beispiel geben, wie man das in einem einfachen Fall machen kann. Doch zuerst ein kleiner Exkurs:

Exkurs: Ein Känguru-Running-Gag.

Ein running Gag in den Känguru-Büchern von Mark-Uwe Kling ist, dass sich das Känguru (und später Mark-Uwe Kling selbst) sich über Preise beschweren, indem sie einen Satz in der Form „N Euro? Das sind ja fast 2N D-Mark! 4N Ostmark! 20N Ostmark auf dem Schwarzmarkt“ von sich geben. Zum Beispiel:

20 Euro? Das sind ja fast 40 D-Mark! 80 Ostmark! 400 Ostmark auf dem Schwarzmarkt!

Irgendwann dieses Jahr wollte ich diesen Spruch auf Mastodon in einem Kommentar bringen, wusste aber nicht mehr, wie das Verhältnis von D-Mark zu Ostmark zu Ostmark auf dem Schwarzmarkt war. Also habe ich eine Internetsuche gemacht und bin unter anderem auf den Känguru-Rechner gestoßen. Ein kleine HTML-Seite, die nicht viel macht, außer bei der Eingabe einer der vier Werte die anderen automatisch auszurechnen. Der Link zu dem Blogpost geht leider ins Leere, weil sich dir URL geändert hat (sollte nicht passieren), aber den Blogpost gibt es noch unter einere leicht anderen URL, tatsächlich hat sich hier nur ein „a“ in der URL in ein „ä“ umgewandelt.

Warum erwähne ich das? Zum Einen finde ich diese Idee von so einem Rechner ganz lustig. Das Internet braucht solche kleinen Späße. Dann kann man diesen Känguru-Satz wunderbar auf Dateigrößen umformen und dabei dieselbe Empörung zum Ausdruck bringen (z.B. „1,5 Megabyte? Das sind ja fast zwei Megabyte! Sechs Megabyte unkomprimiert! Zwanzig Megabyte im Debug-Build!“). Insbesondere aber ist diese Seite vom Inhalt her winzig, bringt aber trotzdem 232 kiB auf die Waage.

Zum Vergleich: Die Startseite meines Blogs ist, vor der Veröffentlichung dieses Posts hier, knapp 225 kiB groß. Knapp 158 kiB davon sind Bilder, knapp 70 kiB davon HTML (der größte Teil davon tatsächlich der eigentliche Textinhalt des Blogs), der Rest fällt auf das CSS.

Der Kängururechner ist also nicht riesig im Verhältnis zu manch einer aufgeblähten Seite, aber deutlich größer als er sein müsste. Und er ist vom Funktionsumfang her ziemlich klein, was ihn zu einem perfekten Demonstrationsobjekt macht, wie man seine Website kleiner kriegen kann.

Disclaimer: Ich will niemanden bloßstellen

Das ist vielleicht wichtig zu sagen, bevor es jemand falsch versteht: Ich will nicht den Autor dieses Rechners bloßstellen. Das ist jemand, der als Hobbyprojekt vor Jahren mal eben diesen Rechner gebaut und ihn seitdem vermutlich nie mehr angefasst hat. Der Autor fordert in dem Blogpost selbst dazu auf, den Code zuverbessern. Der Autor hat mittlerweile ein mit Hugo generiertes Blog, das deutlich weniger Platz einnimmt als der Känguru-Rechner. Der Autor hat explizit Wert darauf gelegt, dass dieses Blog nur noch Resourcen von der eigenen Domain lädt, um den Datenschutz zu verbessern.

Soweit ich das erkennen kann ist der Autor ein cooler Mensch. Ich zeige hier die Verbesserungsmöglichkeiten des Kängururechners nicht auf, weil ich sagen will, dass er schlechte Arbeit gemacht hat, sondern weil ich den Kängururechner lustig finde und er so klein ist, dass man ihn gut als Beispiel nehmen kann.

Die Verkleinerung

Mein erster Gedanke war: „Ich kriege das locker auf unter 10 kiB“. Der zweite Gedanke war: „Mist. Nicht wenn ich die Custom-Schriftart behalten will“. Also, wo fangen wir an?

HTML-Fehler

Der erste Schritt ist keine Verkleinerung. Es gibt ein paar Syntaxfehler im HTML. Die behebe ich zuerst, damit ich einen stabilen Stand habe auf dem ich arbeiten kann. Da ist zunächst ein <noscript>-Tag im <head> Da die Hauptfunktion dieser Seite (die Berechnung der Zahlen) nur mit Javascript läuft, ist ein <noscript>-Tag eine gute Idee. Dieses hier ist aber im <head> und enthält ein <p> und ein <img>.

Beim genaueren Draufschauen ist das aber nur ein Zählpixel eines Tracking-Scripts des Tracking-Systems piwik. Immerhin kein Google Analytics, und es liegt auf dem Server des Autor selber. Mein uBlock Origin hat das aber sowieso geblockt, also nehme ich direkt einmal den ganzen piwik-Teil raus. Stattdessen füge ich ein tatsächlich hilfreiches <noscript>-Tag hinzu.

Weg mit Bootstrap!

Das ändert leider noch nicht viel an der Größe. Laut den Browsertools jedoch sind bootstrap.min.css (121 kiB) und jquery.min.js (28 kiB) für den gößten Teil des Bloats verantwortlich. Also weg damit! Zudem wird das auch noch von Drittsystemen geladen, ist also potentiell ein Angriffspunkt und eine Tracking-Möglichkeit.

Die Sache hat nur einen Haken: Damit ist ein Großteil des Layouts weg. Aber das ist nichts, was man mit ein bisschen CSS nicht selber hinkriegt. Dank fle ist das mittlerweile ziemlich einfach. Mein Ergebnis sieht dem Original recht ähnlich. Ich könnte es noch ähnlicher kriegen, aber was mir gerade wichtig ist: Es sieht vom Format her so aus wie vorher und es ist responsive, d.h. es verhält sich auf verschiedenen Bildschirmgrößen vernünftig. Dafür musste ich auch noch eine media-query einbauen, aber das war kein großes Problem.

Oh Mist, es funktioniert nicht mehr!

Mit Bootstrap habe ich auch jquery entfernt. Dummerweise funktioniert damit das Berechnungsscript nicht mehr. Naja, kein Problem. Dank modernem Javascript braucht man jquery eigentlich nicht mehr. Ich kann zum das hier

var euro = $(".number#euro");

ohne jquery so schreiben:

const euro = document.getElementById("euro");

Ich bin mir ziemlich sicher, das ging auch schon vor jquery so. JQuery fügt allerdings noch ein paar Komfortfunktionen hinzu. Anstatt also wie mit jquery so auf ein Event zu lauschen:

euro.blur(function() {
  const value = parse(euro);
  changeNumber("euro", value);
});

mache ich es so:

euro.addEventListener("blur", () => {
  const value = parse(euro);
  changeNumber("euro", value);
});

Ach warte: Ich muss auch noch Stellen wie euro.text() in euro.innerText umwandeln. Danach geht aber alles super.

Schriftarten

Jetzt sind wir schon auf weniger als 24 kiB runter. Für das bisschen CSS und Javascript, das wir selber schreiben mussten, echt ordentlich. Das HTML, CSS und JS von uns ist jetzt unter 5 kiB groß. Den Rest der Größe macht hauptsächlich die verwendete Schriftart aus: Indie Flower. Die wird momentan automatisch von Googlefonts geladen. Der erste Nachteil daran ist natürlich Google. Aber die Schrift steht unter der Open Font License und wir können sie auch von unserem eigenen Server ausliefern. Mit etwa 20 kiB ist die sogar recht schlank.

Ich stelle hier aber die Frage: brauchen wir die Schrift? Gerade bei geschäftlichen Seiten ist die Antwort oft „Ja, das ist unsere Corporate Design“. In vielen Fällen kann man jedoch „Nein“ sagen. So wie ich in diesem Fall. Ich lasse die Referenz auf die Schrift aber mal drin, für den Fall, dass jemand sie installiert hat. Sonst wird als Fallback halt die erstbeste cursive-Schrift verwendet:

font-family: 'Indie Flower', cursive;

Finishing touches

Jetzt nur noch den Footer anpassen: den Link auf den ursprünglichen Autor korrigieren, mein Repo dazu verlinken… oh, warte, ich habe noch kein Repo. Und mit dem Repo brauche ich noch eine README-Datei. Alles kein Problem. Repo erstellt.

Es gibt noch kleine Verbesserungen, die ich machen würde. Allen voran ein Favicon. Aber das gehört nicht hier hin. Schaut euch das Repo an, vielleicht kommt da noch etwas.

Fazit

Der verkleinerte Kängururechner ist jetzt auf meinem Server. Sieht ein bisschen anders aus als das Original, insbesondere weil die Schriftart anders ist, funktioniert aber genau so gut. Und ist unter 5 kiB groß, mit Kompression sogar noch weniger.

War das hier jetzt ein Tutorial? Hell no. Ich habe fast nicht gezeigt, was ich gemacht habe und wie. In dem Oben verlinkten Repo kann man sich die Änderungen allerdings anschauen, die ersten paar commits sind die Änderungen, die ich in diesem Post erwähnt habe.

Hilft mir das jetzt, die 1,5 MiB von meinem Job zu verkleinern? Auch nicht, dort sind die Probleme etwas anders gelagert.

Trotzdem, es war ein schöner Weg für mich, mich zu entspannen