Zu viel von Darktables „Design“ hat mit „es wäre cool, wenn wir könnten …“ angefangen. Ich sage Ihnen, was cool ist: gute Bilder von Ihnen ASAP an Ihre Wände zu hängen. Bildende Künste sind keine darstellende Kunst (wie Musik oder Theater), sodass nur das Ergebnis zählt. Alles, was davor kommt, ist Overhead, und normalerweise möchten Sie es minimal halten. Das bedeutet nicht, dass der Prozess nicht an sich genossen werden kann. Um den Prozess zu genießen, müssen Sie jedoch Ihre Werkzeuge beherrschen und zu Ihrem Willen biegen, andernfalls kämpfen Sie nur mit ihnen und der gesamte Prozess summiert sich zu Frustration. Das Problem ist, dass Darktables „Design“ zu viel Aufwand darauf verwendet, anders zu sein, um es anders zu haben.
In diesem Prozess der Hinzufügung von „coolen neuen Dingen“ hat Darktable Tastaturkürzel und viele grundlegende GUI-Verhaltensweisen gebrochen, sauberen Code durch Spaghetti ersetzt und mehr GUI-Unordnung hinzugefügt, ohne jemals etwas zu entfernen.
Ansel hat einen
expliziten Designprozess, der zwingend mit definierten Problemen beginnt, die von definierten Benutzern erlebt werden. Es stellt sich heraus, dass die Menge des zu schreibenden Codes umgekehrt proportional zu dem Ausmaß des Denkens ist, das Sie über Ihre Lösung gemacht haben, typischerweise um das Grundproblem aus dem herauszufinden, was Benutzer Ihnen sagen, und den einfachsten Weg zur Lösung zu finden (der oft nicht einmal eine Softwarelösung ist…).
Aber Bugs warten nicht im Denken auf Sie, sie warten nur im geschriebenen Code. Je mehr Sie also denken, desto weniger codeen Sie, desto weniger Wartungsaufwand erzeugen Sie für sich selbst in der Zukunft. Aber natürlich… müssen Sie genügend Zeit haben, um die Dinge durchzudenken. Im Wesentlichen bedeutet das Tschüss zu Samstag-Brille, von Amateuren getriebenes Hacken!
Erweitere es nicht, wenn du es nicht zuerst vereinfachen kannst
Viel Darktable-Hacking wurde durch Copy-Pasting von Code aus anderen Teilen der Software oder sogar aus anderen Projekten gemacht, hauptsächlich weil Mitwirkende nicht die Zeit oder Fähigkeiten haben, um große Neuschreibungen zu unternehmen. Dies löst Code-Duplikation aus und erhöht die Länge der Funktionen und fügt interne Verzweigungen hinzu und führt if- und switch case-Nested manchmal auf mehr als 4 Ebenen ein, was die Struktur und Logik schwerer verständlich macht und Bugs schwerer (und frustrierender) zu jagen, während es wahrscheinlicher passiert.
In jedem Fall, wenn der Code, der für vorhandene Funktionen verantwortlich ist, nur wächst (manchmal innerhalb von 4 Jahren um den Faktor 10), wirft das ernsthafte Fragen zur Zukunftsfähigkeit in einem Kontext auf, in dem Mitwirkende nicht mehr als ein paar Jahre da sind und Entwickler eine begrenzte Zeit zum Investieren haben. Es ist einfach unverantwortlich, da es langfristige Wartbarkeit für glänzende neue Dinge opfert.
Code zu vereinfachen und zu verallgemeinern, durch saubere APIs, bevor neue Features hinzugefügt werden, ist Pflicht und Ansel akzeptiert nur Code, den ich persönlich verstehe und zu pflegen in der Lage bin. KISS.
Grundlegende Code-Logik
Pull-Requests, die nicht den Mindestanforderungen an die Codequalität entsprechen, werden nicht akzeptiert. Diese Anforderungen zielen darauf ab, langfristige Wartbarkeit und Stabilität zu gewährleisten, indem klarer, lesbarer Code mit einfacher Logik erzwungen wird.
Prozeduren müssen in einheitliche wiederverwendbare Funktionen aufgeteilt werden, wann immer möglich. Eine Ausnahme sind spezielle lineare Prozeduren (keine Verzweigungen), die Aufgaben ausführen, die zu spezifisch sind, um irgendwoanders wiederverwendet zu werden, aber in diesem Fall verwenden Sie Kommentare, um die Prozeduren in „Kapitel“ oder Schritte aufzuteilen, die leicht erkannt und verstanden werden können.2. Funktionen sollten jeweils nur eine Aufgabe gleichzeitig ausführen. Zum Beispiel sollte GUI-Code nicht mit SQL- oder Pixel-Verarbeitungscodes gemischt werden. Getter- und Setter-Funktionen sollten verschiedene Funktionen sein.3. Funktionen sollten nur einen Eintritts- und einen Austrittspunkt (return) haben. Die einzigen Ausnahmen sind ein frühzeitiges Zurückkehren, wenn der Speicherpuffer, auf den die Funktion zugreifen soll, nicht initialisiert ist, oder wenn ein Thread-Mutx-Schloss bereits ergriffen ist.4. Funktionen sollten lesbare, explizite Namen und Argumentennamen haben, die ihren Zweck bewerben. Programme sollen von Menschen gelesen werden, wenn Sie für die Maschine programmieren, tun Sie es in Binär.5. Funktionen dürfen nur bis zu zwei if-Strukturen schachteln. Wenn mehr als 2 verschachtelte if erforderlich sind, muss die Struktur Ihres Codes überarbeitet und wahrscheinlich in granularere Funktionen aufgeteilt werden.6. if sollte nur gleichartige Fälle wie den Zustand oder den Wert idealerweise einer (aber vielleicht auch mehrerer) Variablen des gleichen Typs testen. Wenn ungleichartige Fälle getestet werden müssen (wie IF user param IS value AND picture buffer IS initialized AND picture IS raw AND picture HAS embedded color profile AND color profile coeff[0] IS NOT NaN), sollten sie in eine Überprüfungsfunktion verschoben werden, die ein gbooleanTRUE oder FALSE zurückgibt und ordnungsgemäß so benannt ist, dass Mitentwickler den Zweck der Überprüfung ohne Zweideutigkeit beim flüchtigen Durchlesen des Codes verstehen, wie color_matrix_should_apply(). Der verzweigte Code wird dann if(color_matrix_should_apply()) pix_out = dot_product(pix_in, matrix); sein.7. Kommentare sollten erwähnen, warum Sie getan haben, was Sie getan haben, wie Ihre Grundannahmen, Ihre Gründe und jede akademische oder doc-Referenz, die Sie als Grundlage verwendet haben (DOI und URLs sollten da sein). Ihr Code sollte explizit machen, was Sie gemacht haben. Wenn Sie sich dabei ertappen, erklären zu müssen, was Ihr Code in Kommentaren macht, dann ist das in der Regel ein Zeichen dafür, dass Ihr Code schlecht strukturiert ist, Variablen und Funktionen schlecht benannt sind usw.8. Schnelle Workarounds, die Probleme verstecken, anstatt sie an der Wurzel zu lösen, werden nicht akzeptiert. Wenn Sie daran interessiert sind, sollten Sie vielleicht in Betracht ziehen, zum Upstream-Darktable beizutragen. Die einzigen Ausnahmen sind, wenn die Probleme blockierend sind (die Software abstürzen lassen) und keine bessere Lösung gefunden wurde, nachdem eine anständige Menge an Zeit in die Recherche investiert wurde.9. Denken Sie immer daran, dass der beste Code der einfachste ist. KISS. Um dieses Ziel zu erreichen, ist es in der Regel besser, Code von Grund auf neu zu schreiben, anstatt Bits vorhandenen Codes zu mischen und durch intensives Copy-Pasting zu kombinieren.
Einige zufällige Weisheitsstücke aus dem Internet:
Jeder weiß, dass Debugging doppelt so schwierig ist wie das Schreiben eines Programms. Wenn Sie also so clever sind, wie Sie es nur sein können, wenn Sie es schreiben, wie werden Sie es jemals debuggen?— Brian W. Kernighan
Jeder Narr kann Code schreiben, den ein Computer verstehen kann. Gute Programmierer schreiben Code, den Menschen verstehen können.— Martin Fowler, Refactoring: Improving the Design of Existing Code
Programmieren Sie immer so, als wäre der Typ, der am Ende Ihren Code wartet, ein gewalttätiger Psychopath, der weiß, wo Sie wohnen.— John Woods
Wann immer ich überlegen muss, um zu verstehen, was der Code macht, frage ich mich, ob ich den Code umgestalten kann, damit dieses Verständnis sofort ersichtlich ist.— Martin Fowler, Refactoring: Improving the Design of Existing Code
Code ist schlecht. Es verrottet. Es erfordert regelmäßige Wartung. Es hat Fehler, die gefunden werden müssen. Neue Features bedeuten, dass alter Code angepasst werden muss. Je mehr Code Sie haben, desto mehr gibt es, wo sich Fehler verstecken können. Je länger dauern Auschecken oder Kompilieren. Je länger dauert es, bis ein neuer Mitarbeiter Ihr System versteht. Wenn Sie refaktorisieren müssen, gibt es mehr Dinge, die bewegt werden müssen.
Code wird von Ingenieuren produziert. Um mehr Code zu machen, benötigt man mehr Ingenieure. Ingenieure haben n^2 Kommunikationskosten, und all der Code, den sie zum System hinzufügen, während sie dessen Fähigkeit erweitern, erhöht auch einen ganzen Korb voller Kosten. Sie sollten alles tun, um die Produktivität einzelner Programmierer in Bezug auf die Ausdruckskraft des geschriebenen Codes zu steigern. Weniger Code, um dasselbe zu tun (und möglicherweise besser). Weniger Programmierer einzustellen. Geringere Kommunikationskosten in der Organisation.
Ansel sowie Darktable sind in C geschrieben. Diese Sprache ist für fortgeschrittene Programmierer gedacht, um schnelle Fehler in Betriebssystemen und auf Systemebene anzuwandeln. Sie gibt zu viel Freiheit, um schädliche Dinge zu tun und kann nicht vor der Programmausführung-debugged werden, oder Sie sind gezwungen, Ihre eigenen Tests zu schreiben (die selbst fehlerhaft sein können oder die Art von Fehlern, die sie durchlassen, möglicherweise verzerren, und sowieso schreibt niemand Tests). Doch die meisten Mitwirkenden sind nicht für C ausgebildet, viele von ihnen sind nicht einmal professionelle Programmierer (obwohl professionelle C-Programmierer wahrscheinlich nicht in der Nähe von Endbenutzeranwendungen sein sollten), daher ist C eine gefährliche Sprache für jede Open-Source-App.
C lässt Sie in Puffer schreiben, die nicht zugeordnet wurden (was zu segfault-Fehler führt) und lässt Sie sie mehr als einmal freigeben, wird jedoch keine Puffer freigeben, wenn sie nicht mehr benötigt werden (was zu Speicherlecks führt, wenn Sie vergessen haben, es manuell zu tun). Das Problem ist, da das Zuweisen/Aufheben von Puffern sowohl im Programmlebenszyklus als auch im Quellcode weit entfernt sein kann von dem Ort, an dem Sie sie verwenden, kann man sich dort leicht vertun. C lässt Sie auch jeden Zeiger in jeden Datentyp konvertieren, was viele Programmierer-Fehler und Datenkorruption ermöglicht. Die nativen String-Handhabungsmethoden sind nicht sicher (aus Gründen, die ich nie zu verstehen versuchte), wir müssen daher die GLib-Methoden verwenden, um Exploits in der Sicherheit zu verhindern.
Im Grunde macht sich C zu Ihrem besten und schlimmsten Feind, und es liegt an Ihnen, die Sicherheitsregeln zu beachten, deren Weisheit nur offensichtlich wird, wenn Sie sie verletzen. Viel wie die Fehler in einem C-Programm. Überlegen Sie, dass Sie Ihren Code schreiben, damit er von Dummköpfen gelesen werden kann, die noch nie zuvor in C programmiert haben.
Sie müssen auch berücksichtigen, dass der Compiler die meisten Optimierungen für Sie durchführt, jedoch dabei äußerst konservativ sein wird. Der Grundsatz lautet, wenn Ihr Code für einen Menschen leicht verständlich (einfache Logik) ist, wird er auch dem Compiler richtig verständlich sein, der die entsprechenden Optimierungsmaßnahmen ergreifen wird. Umgekehrt versagen manuelle Optimierungen im Code, die kryptischen Code annehmen, der auf Einzelprozessoren schnell sein soll, in der Regel und ergeben nach der Kompilierung langsamer laufende Programme.
Muster und Strukturen
for-Schleifen sind reserviert für Iterationen über Arrays mit bekannter Größe, sodass die Anzahl der Schleifendurchgänge bekannt ist. In diesem Sinne können sie auch verwendet werden, um über GList *-Elemente zu iterieren (die keine Größeigenschaft haben, da sie dynamisch zugewiesen werden), obwohl dabei überprüft wird, ob jedes Element (GList *)->next nicht NULL ist. for-Schleifen sollten im Allgemeinen keine break- oder return-Anweisungen innerhalb ihres Kontrollflusses verwenden, es sei denn, die Schleife sucht nach einem bestimmten Element im Array und gibt es zurück, sobald es gefunden wird. Wenn Ihre Schleife eine Abbruchbedingung hat, verwenden Sie while.
C ist keine objektorientierte Sprache, aber Sie können und sollten OO-Logik in C verwenden, wenn sie relevant ist, indem Sie Strukturen verwenden, um Daten und Zeiger auf Methoden zu speichern und dann einheitliche getter und setter verwenden, um die Daten zu definieren und darauf zuzugreifen.
Strukturen wie while, for, if oder switch sollten nicht tiefer als 3 (und vorzugsweise 2) Ebenen geschachtelt werden. Verwenden Sie Funktionen, wenn dies passiert:
Lange Reihen von Überprüfungen sollten in Funktionen eingebaut werden, die gboolean zurückgeben, wobei klar dargestellt wird, was wir überprüfen, sodass wir in Prozeduren Folgendes erhalten:
Greifen Sie immer mit der array-ähnlichen Syntax über ihren Basiszeiger auf Daten aus Puffern zu, anstatt Nicht-Konstante-Zeiger zu verwenden, an denen Sie Arithmetik durchführen. Beispielsweise:
1float*const buffer =malloc(64*sizeof(float));
2for(int i =0; i <64; i++)
3{
4 buffer[i] = ...
5}
Machen Sie folgendes nicht:
1float*buffer =malloc(64*sizeof(float));
2for(int i =0; i <64; i++)
3{
4*buffer++= ...
5}
Die letztgenannte Version ist nicht nur weniger klar zu lesen, sondern verhindert auch Parallelisierungen und Compileroptimierungen, da der Wert des Zeigers von der Schleifeniteration abhängt und zwischen Threads geteilt werden müsste, wenn welche vorhanden sind. Die erstgenannte Version führt zu einer Speicherzugriffslogik, die unabhängig von der Schleifeniteration ist und sicher parallelisiert werden kann.
Die Verwendung von Inline-Variablenerhöhungen (siehe ein Albtraumbeispiel hier ) ist strengstens untersagt, es sei denn, sie ist die einzige Operation in der Zeile. Diese sind ein Chaos, das viele Programmierfehler verursacht. Dies ist erlaubt:
1uint32_t counter;
2for(int i =0; i <64; i++)
3{
4if(array[i] > threshold)
5 counter++;
6}
Die case-Anweisungen in der switch-Struktur sollten nicht additiv sein. Machen Sie Folgendes nicht:
Beim flüchtigen Lesen wird nicht sofort klar sein, dass der VALUE3-Fall die Klauseln der vorherigen Fälle übernimmt, insbesondere in Situationen, in denen es mehr Fälle gibt. Machen Sie Folgendes:
Jeder Fall ist in sich geschlossen und das Ergebnis hängt nicht von der Deklarationsreihenfolge der Fälle ab.
Sortieren und speichern Sie Ihre Variablen in Strukturen, die Sie als Funktionsargumente übergeben, anstatt Funktionen mit mehr als 8 Argumenten zu verwenden. Machen Sie Folgendes nicht:
Das vorherige Beispiel stammt aus darktable . Das Kopieren und Einfügen von Funktionsaufrufen ist unnötig und die Vielzahl von Positionsargumenten macht es unmöglich, sich zu merken, welches welches ist. Es zeigt auch nicht, welche Argumente über die verschiedenen Zweige hinweg konstant sind, was das Refactoring erschwert. Das spätere Beispiel ist nicht kürzer, aber die Struktur macht die Funktion nicht nur einfacher aufzurufen, sondern die Strukturdeklaration erlaubt es, jedes Argument explizit zu setzen, mit inline-Prüfungen bei Bedarf. Die Abhängigkeit der Eingabeargumente von den externen Bedingungen ist ebenfalls sofort klar, und die booleschen Argumente werden direkt von den Bedingungen gesetzt, was das Programm in Zukunft einfacher zu erweitern macht und weniger anfällig für Programmierfehler aufgrund von Missverständnissen in den Variablenabhängigkeiten.
OpenMP-Optimierungen
Pixel sind im Wesentlichen 4D-RGBA-Vektoren. Seit 2004 verfügen Prozessoren über spezielle Fähigkeiten zur Vektorbearbeitung und zur Anwendung von Single Instructions on Multiple Data (SIMD). Dies ermöglicht es uns, die Berechnungen zu beschleunigen, indem wir ein ganzes Pixel (SSE2) bis zu 4 Pixel (AVX-512) gleichzeitig verarbeiten und dabei viele CPU-Zyklen einsparen.
Moderne Compiler verfügen über Optionen zur automatischen Vektorisierung, die reines C optimieren können, und die OpenMP-Bibliothek bietet Hinweise zur Verbesserung, vorausgesetzt, der Code ist in einer vektorisierbaren Weise geschrieben und es werden einige Pragma verwendet.
vermeiden Sie Verzweigungen in Schleifen, die den Kontrollfluss ändern. Verwenden Sie Inline-Anweisungen wie absolute = (x > 0) ? x : -x;, sodass sie in SIMD in Byte-Masken umgewandelt werden können,
pixel sollten nur über den Basiszeiger ihres Arrays und die Indizes der Schleifen referenziert werden, sodass Sie vorhersagen können, welche Speicheradresse nur aus dem Schleifenindex abgerufen wird,
vermeiden Sie es, struct-Argumente in Funktionen zu verwenden, die in OpenMP-Schleifen aufgerufen werden, und entpacken Sie die struct-Mitglieder vor der Schleife. Die Vektorisierung kann nicht auf Strukturen durchgeführt werden, sondern nur auf float- und int-Skalaren und Arrays. Zum Beispiel:
1typedef struct iop_data_t
2{
3float[4] pixel DT_ALIGNED_PIXEL; // auf 16-Bit-Adressen ausrichten
4float factor;
5} iop_data_t;
6 7#ifdef _OPENMP 8#pragma declare simd 9#endif10/* die Funktion als vektorisierbar deklarieren und inline einfügen, um Aufrufe innerhalb der Schleife zu vermeiden */11inline float foo(constfloat x, constfloat pixel[4], constfloat factor)
12{
13float sum = x;
1415/* eine SIMD-Reduktion verwenden, um die Summe zu vektorisieren */16#ifdef _OPENMP17#pragma omp simd aligned(pixel:16) reduction(+:sum)18#endif19for(size_t k =0; k <4; ++k)
20 sum += pixel[k];
2122return factor * sum;
23}
2425void loop(constfloat*const restrict in,
26float*const restrict out,
27const size_t width, const size_t height,
28const struct iop_data_t bar)
29{
30/* entpacken Sie die Struct-Mitglieder */31constfloat*const restrict pixel = bar->pixel;
32constfloat factor = bar->factor;
3334#ifdef _OPENMP35#pragma omp parallel for simd default(none) \36 dt_omp_firstprivate(in, out, pixel, factor, width, height) \
37 schedule(simd:static) aligned(in, out:64)
38#endif39for(size_t k =0; k < height * width; ++k)
40 {
41 out[k] = foo(in[k], pixel, factor);
42 }
43}
wenn Sie verschachtelte Schleifen verwenden (z.B. Schleifen über die Breite und Höhe des Arrays), deklarieren Sie die Pixelzeiger in der innersten Schleife und verwenden Sie collapse(2) im OpenMP-Pragma, damit der Compiler den Cache-/Speichergebrauch optimieren kann und die Schleife gleichmäßiger auf die verschiedenen Threads aufteilen kann,
verwenden Sie eine flache Indexierung von Arrays, wann immer möglich (for(size_t k = 0 ; k < ch * width * height ; k += ch)) anstelle von verschachtelten Breite/Höhe/Kanälen-Schleifen,
verwenden Sie das restrict-Schlüsselwort für Bild-/Pixelzeiger, um Aliasing zu vermeiden, und vermeiden Sie In-Place-Operationen auf Pixeln (*out muss immer unterschiedlich von *in sein), so dass Sie keine Variablenabhängigkeiten zwischen Threads auslösen,
richten Sie Arrays auf 64 Bytes und Pixel auf 16 Bytes Blöcke aus, sodass der Speicher zusammenhängend ist und die CPU vollständige Cache-Zeilen laden kann (und vermeiden Sie Segfaults),
schreiben Sie kleine Funktionen und optimieren Sie lokal (eine Schleife/Funktion), unter Verwendung von OpenMP und/oder Compiler-Pragmas,
halten Sie Ihren Code einfach und systematisch und vermeiden Sie kluge Zeigerarithmetik, da dies nur dazu führt, dass der Compiler Variablenabhängigkeiten und Zeigeraliasing dort erkennt, wo keine existieren,
vermeiden Sie Typumwandlungen in Schleifen,
deklarieren Sie Eingabe-/Ausgabe-Zeiger als *const und Variablen als const, um falsches Sharing in parallelen Schleifen zu vermeiden (Verwendung des OpenMP-Pragmas shared(variable)).
Code-Formatierung
Verwenden Sie Leerzeichen anstelle von Tabs,
Die Einrückung erfolgt mit 2 Leerzeichen,
Entfernen Sie nachfolgende Leerzeichen,
{ und } gehen in ihre eigene Zeile,
Richtlinien
Tun Sie Dinge, die Sie beherrschen: Ja, es ist schön, neue Dinge zu lernen, aber Ansel ist kein Sandkasten, es ist eine Produktionssoftware und nicht der richtige Ort, um zu üben.
KISS und sei faul: Ansel hat nicht 50 Vollzeit-Entwickler an Deck, minimalistisch sowohl in den Funktionen als auch im Volumen des Codes zu sein, ist vernünftig und gesund für das aktuelle Management, aber auch für die zukünftige Wartung. (KISS: keep it stupid simple).
Machen Sie es wie der Rest der Welt: Sicher, wenn alle aus dem Fenster springen, haben Sie das Recht, ihnen nicht zu folgen, aber die meisten Probleme mit der Software-UI/UX wurden bereits irgendwo gelöst und es macht in den meisten Fällen Sinn, einfach diese Lösungen wiederzuverwenden, da die meisten Benutzer damit bereits vertraut sind.
Programmieren ist nicht das Ziel: Programmieren ist ein Mittel zum Zweck, das Ziel besteht darin, große Mengen an Bildern in kurzer Zeit zu verarbeiten, während das gewünschte Aussehen auf jedem Bild erreicht wird. Programmieraufgaben sind als Overhead zu betrachten und sollten minimal gehalten werden, und das Volumen des Codes ist für jedes Projekt eine Belastung.
Translated from English by :
ChatGPT.
In case of conflict, inconsistency or error, the English version shall prevail.