Ansel wird designt, nicht gehackt. Hacker mögen es genießen, “zu arbeiten” an dem beschleunigten Untergang von Darktable, indem sie seine technische Schulden erhöhen.
Was ist Design?
Design ist ein Prozess, bei dem eine Methodik entwickelt wird, um eine technische Lösung für ein menschliches Problem zu finden. Der Designprozess soll zur passendsten Lösung führen und gleichzeitig dem natürlichen Drang widerstehen, sich vorschnell auf die erste oder bequemste Idee zu stürzen.
Ohne Anforderungsdefinition und Design ist Programmierung die Kunst, Bugs in eine leere Textdatei einzufügen.
- Design beginnt mit einem Use-Case: eine definierte Aufgabe, die von einem bestimmten Benutzer in einem bestimmten Zeitrahmen erreicht werden soll. Gibt es keinen Use-Case, dann gibt es kein zu lösendes Problem, also halte dich von deinem Code-Editor fern.
- Design erfordert Kenntnisse über den Zielbenutzer: Ausbildung/Training, Niveau von handwerklichem Können/Mastership, etc.
- Design erfordert, die Bedürfnisse zu verstehen: Im Kontext von Ansel wird oft Kunstgeschichte und Kenntnisse der Dunkelkammerfotografie benötigt,
- Sobald das Problem und der Benutzer verstanden sind, muss Design spezifizieren:
- die erwarteten Funktionalitäten der Lösung,
- den Umfang der Lösung (wo in der Lebensdauer eines Bildes steht die Lösung?),
- die Einschränkungen und Anforderungen der Lösung (Unterstützung von Standards, Möglichkeit zur Verarbeitung von n Bildern pro Zeiteinheit, etc.),
- eine Reihe von Tests, die zur Validierung der Qualität der Lösung abgeschlossen werden müssen, um unproduktive Meinungen, Vorurteile und Subjektivität im Validierungsprozess zu begrenzen.
- Erst dann können die Entwürfe und das Brainstorming beginnen, gefolgt von Prototypen.
Was ist Design nicht?
Design befasst sich nicht mit:
- vagen Anforderungen eines undefinierten, zukünftigen oder fantasierten Benutzers,
- “es wäre cool, wenn…” (so erstellt man inkonsistente Plugins-Sammlungen),
- was die Leute mögen (für alles, was jemand mag, wird man jemand finden, der es hasst),
- was die Leute denken, dass sie wollen (es ist oft nicht das, was sie brauchen),
- magischen Tech-Buzzwords, die “die Zukunft sind” und daher überall eingebaut werden müssen, unabhängig von ihrer Relevanz oder Machbarkeit (ja, ich spreche von KI, NFT, Blockchain etc.)
Was ist gutes Design?
Gutes Design ist:
- minimalistisch,
- robust,
- zukunftssicher,
- generisch und verallgemeinert,
- mit begrenzten Ressourcen wartbar,
- durch Wissenschaft informiert,
- kompatibel/interoperabel mit Industriestandards.
Da Ansel eine Workflow-basierte Anwendung ist, berücksichtigt gutes Design auch den Workflow als Ganzes und wo sich das Problem/die Lösung darin befindet.
Wie wird gutes Design erstellt?
Um den Designprozess zu unterstützen, sollte die Kommunikation prägnant und fokussiert bleiben, und die an diesem Prozess beteiligten Personen sollten sicherstellen, dass sie ein angemessenes Verständnis der Theorie und des technischen Hintergrunds im Bezug auf den Problem- und Lösungsscope haben.
Es sollte betont werden, dass, obwohl das Projekt softwaregesteuert ist, nicht alle Lösungen das Coden beinhalten. Manchmal (oft?) reicht eine bessere Ausbildung oder Dokumentation aus.
Der Zweck eines gesunden Designprozesses ist es, zu vermeiden, dass die Lösungen zu früh mit einem bevorzugten Design/Technik beeinflusst werden und sich in den technischen Details zu verlieren, sondern immer zu den Grundprinzipien zurückzukehren: die Nachbearbeitung möglicherweise großer Stapel von Rohbildern für alle Arten von Ausgabemedien.
Dies wird dadurch gestützt, dass Benutzer selten ihre eigenen Bedürfnisse kennen, oder vielmehr, die geäußerten Bedürfnisse selten der Kern dessen sind, was sie eigentlich wollen. Die schwierige Aufgabe des Designs besteht darin, durch die Äste zu schneiden, um zur Wurzel zu gelangen, da die Lösung des eigentlichen Problems normalerweise zu eleganteren, generischeren und minimalistischeren Lösungen führt.
Probleme kommen zuerst
Der erste Schritt im Ansel-Designprozess besteht darin, einen Feature-Request im Community Forum einzureichen. Feature-Requests wurden von Github auf das Forum verlegt, da diese Plattform nicht einladend für Nicht-Programmierer und Nicht-Englischsprachige ist (obwohl das Forum nur Französisch und Englisch unterstützt).
Dieser Feature-Request konzentriert sich auf das zu lösende Problem und unterlässt es, eine Lösung vorzuschlagen. Das Problem wird in Bezug auf Aufgaben im Workflow eines Fotografen oder den erwarteten visuellen Ausgang des bearbeiteten Bildes definiert, also im Hinblick auf das zu erreichende Endziel, nicht im Hinblick auf die vermuteten erforderlichen Werkzeuge oder technischen Details. Dies könnte zu einer Diskussion führen, um die Wurzeln des Problems zu ergründen, die oft gut verborgen unter dem liegen, was der Benutzer für sein Problem hält.
In diesem Stadium wird kein Lösungsvorschlag akzeptiert.
Lösungen kommen als Zweites
Wenn die Definition und der Umfang des Problems zwischen den beteiligten Diskussionsteilnehmern vereinbart wird, können Lösungsvorschläge gemacht werden. Weitere Diskussionen könnten erforderlich sein, um die Nachteile und Vorteile jeder Lösung zu bewerten, was zur Annahme der besten Lösung auf Prinzipienführung führt. Lösungen werden durch ihre Funktionalitäten definiert (also, was sie tun sollten), nicht durch ihre Technologie oder Mittel (wie sie es tun sollten).
In diesem Stadium wird kein Prototypvorschlag akzeptiert.
Die angenommenen Lösungen führen zu einem neuen Eintrag im Projektmanagement-Kanban-Board .
Sie könnten einer theoretischen und technischen Recherche zur Beurteilung der Machbarkeit unterliegen, in diesem Fall werden sie in die Zu erforschen Spalte des Kanban-Boards einsortiert. Die Forschungsergebnisse werden dem ursprünglichen Thema hinzugefügt, bis die Machbarkeit der Lösung bewiesen ist. Wenn dies der Fall ist, wird das Thema in die “Zu erledigen” Spalte des Kanban-Boards verschoben.
Angenommene Lösungen könnten direkt in die Zu erledigen Spalte einsortiert werden, wenn nur bekannte Werkzeuge und Techniken erforderlich sind.
Idealerweise sollten die zu testenden Punkte und das Testverfahren zur Validierung des Prototyps geschrieben werden, noch bevor ein funktionierender Prototyp vorliegt. Zumindest sollten die Tests sicherstellen, dass es keine Regressionen bei verwandten Funktionen und Werkzeugen gegeben hat.
Prototypen kommen als Drittes
Nur die im Kanban-Board in die “Zu erledigen” Spalte einsortierten Themen werden bearbeitet, entweder von mir oder von jemandem, der sich ihnen widmen möchte.
Der Prototyp der Lösung wird in einem Pull Request eines Themenzweigs vorgeschlagen, der auf das ursprüngliche Thema verweist. Themenzweige müssen auf den master Zweig neu gebased werden, z. B. git rebase upstream master, oder wenn du deinen Zweig lokal mit neuen Master-Commits aktualisierst, git pull upstream master --rebase ausführen oder Git global einrichten , um durch rebase statt merge zu ziehen. Das stellt sicher, dass der Branch-Verlauf mit minimalem Aufwand sauber bleibt und der master Verlauf ebenfalls sauber bleibt, wenn dein PR zusammengeführt wird.
Wenn der Prototyp-Pull-Request überprüft wird und den Codequalitätsstandards entspricht (siehe unten), während er die Spezifikationen der angenommenen Lösung erfüllt, wird er genehmigt und automatisch in die “Zu testen/validieren” Spalte des Projektmanagement-Kanban-Boards einsortiert.
Validierung kommt als Viertes
Genehmigte Pull Requests werden früh im candidate oder dev Zweig für Tests zusammengeführt, je nachdem, ob sie die Bearbeitungsgeschichte von Bildern unterbrechen könnten (indem neue Modulparameter hinzugefügt oder die Datenbankschemata geändert werden). Dieser Zweig wird immer der master Zweig mit allen ausstehenden Pull Requests zur Validierung sein. Dies soll das Testen von Personen erleichtern, die nicht unbedingt up-to-date mit der manuellen Zusammenführung von Git-Zweigen sind. Im Gegensatz zum dev Zweig sollte candidate deine Bearbeitungen nicht zerstören.
Wenn nach einiger Zeit keine Fehler oder Unterbrechungen gemeldet werden und der Prototyp seinen ursprünglichen Zweck korrekt erfüllt, wird er in master zusammengeführt und das zugehörige Thema wird abgeschlossen und in die “Erledigt” Spalte des Projektmanagement-Kanban-Boards verschoben.
Wenn sich der Prototyp als unbefriedigend erweist, kann er abgelehnt werden und ein anderer muss entwickelt werden.
Pro tips from a seasoned designer
Not all software problems are coding problems
Many problems don’t require more tools (or toys), and more code. More code is always bad anyway, and should be avoided whenever possible. Very often, user’s problem is they can’t see how to bend existing features to fulfill their needs. This is solved by education, aka better documentation and more tutorials, and sometimes by better UI.
Listen but don’t listen to users
Users express what they want and what they like, never what they need. And you don’t need to listen to them to know what it will be:
- they will want the same thing as their neighbour just got,
- they will like what they are used to.
And then, for everything one likes, you will find another one to dislike it. So the Darktable way of solving conflict is to not solve conflict, but give everyone an option, a mode, a preference to enable that special thing they like, how they like it. This means more case in your switch, more nested if, more codepaths you will need to test now, debug, and maintain in the future, and then more preferences hiding the others in the pref window. Before you know it, the code is a tumor that nobody understands anymore, and fixing it only makes it more complicated.
When you scratch beneath the surface, you find than what people actually need is much closer to other people’s needs than what they say they want. So you can reconcile the needs much easier than the desires, and without compromising. But then you have to trace the root needs below the will, and that takes abstraction skills and psychology.
UI designers are dangerous idiots
Everybody who only sees, focuses and cares about the UI is a dangerous idiot. If your GUI is complicated, it means a lot more than just a “complicated GUI” : it means that the complexity of your backend has reached your frontend. I have found the hard way that GUI complexity is never separate, and can’t be solved separately, from backend complexity and overall application architecture. GUI is not parallel to backend architecture, it’s the termination of it.
The problem of UI designers is they typically don’t code, or if they do, they suck at low-level programming and software architecture. So they focus on what little they see and understand (typical streetlight effect ), and they only produce non-actionnable designs that conflicts with what the software actually needs to work. Because that GUI is only connecting user input to the backend, and if we need that many widgets, it’s because the architecture needs that many inputs. You can’t escape it : to remove widgets, you need to remove inputs, which means your architecture will have to work with fewer degrees of freedom first. That starts with simplifying the backend, which means stinky refactoring of dusty old code nobody understands anymore.
You don’t solve GUI issues with drawings and mockups, you solve GUI issues with solving backend issues. But then you need guys who understand both levels, and they may be too expensive for you.
Ask yourself 36 times per day what was the problem you were trying to solve
It’s super easy to get lost into technicalities when programming in a low-level language and fighting third-party libs or APIs, but sometimes the solution is simple and elegant and you got carried away too far into pointers and thread locks. Always go back to the initial problem at hand, that’s your lifeline to simplicity.
What is the problem ? Who faces it ? When ? How often ? Doing what ?
The best path is the simplest path towards your solution : low techs, little code, few layers.
Document your shitty design
Many times, I completely redid a design while documenting it, because it’s when you try to explain it that your realize it’s too complicated to explain, which means it’s too complicated to understand. If you can’t explain your design in a couple of paragraphs, or your documentation has too many “if this, then that”, there are usually two reasons :
- your GUI doesn’t expose the relevant info where user needs it, so you have to link half the documentation in your explanation to redirect users to everything they need to know or check before using the one thing you were documenting. The solution is to bring back relevant info where it’s needed.
- your GUI has too many trays, collapsible stuff, contextual behaviours, use cases or hidden preferences, and covering all bases makes you write a novel. The solution is to linearize the workflow, maybe remove options or split features.
GUI is how users control the backend, but it’s also where they learn about existing features and what they do. The documentation should provide context, guidelines and references regarding how we do stuff, but the GUI should explain what it does itself.
Of course, there is an obvious limitation to that : in a photography application, users need to understand photography and its language, which involves things like dynamic range, color gamut, tone mapping, etc. The GUI should be self-explanatory on how it’s supposed to be used, not remove the need to learn the trade (what should be done and how).
Design is an iterative process
An application is a virtual world in which one small change can reorder how the rest of the ecosystem adapts around it. Therefore any design change can trigger the need to change other things around (refactor tools, move widgets, prune features). Which then might trigger the need to correct the initial change again. It’s a step by step process in which it is foolish to even try to get everything right at each step, what matters it that each steps improves the environment from the previous.
Sometimes, (re)design can’t be done by small steps but by large leaps : that’s when you redo the architecture. These leaps will break many things around them, which is ok if the newer architecture is simpler and more robust overall, and if you give it some time to recover before taking the sledgehammer again. But that will create a transient state in which the new design will appear worse than the previous. This is telling us that how the design is percieved is not a valid input : design quality has to be assessed against its goals and evaluated with objective metrics, not with feelings and quick tests.
And sometimes, some steps are mistakes and should be reverted. The sunk cost fallacy should not be used to justify that some redesign should be kept because it was a lot of work to achieve. It’s expected that all research & development doesn’t make it into production.
Whack-a-mole sessions mean your architecture has run its course
Whether you keep creating new bugs while fixing old ones, or you keep creating edge cases by extending some feature, it all points in the same direction : your architecture can’t be bent any more because it has outgrown its design requirements. It might be that the backend has grown too convoluted or it might be that the existing architecture was really not planned for what you are trying to make it do, but both ways, you will have to redo the architecture and stop hacking in-place. Otherwise, you are only adding technical debt.
But then, the development cost changes scale and that saturday-afternoon project might become a month-long project.
Best-practices are guidelines, not rules
Best-practices help developing sane habits and clean code, unless you don’t understand the problem they tried to solve and use them out of their scope of validity. In that case, they become cargo cult : trying to mimic the effects in the hope that it will magically fix the causes too.
The first that comes to mind is code reuse/code sharing. If reusing the same code for (seemingly similar) features leads to too much internal branching (nested if, switch/case, etc.), to handle all possible paths, what you win on code volume is lost on cyclomatic complexity, and, by the way, your features are not as similar as you thought.
Also, duplicating code might be a starting point to locally optimize the duplicate later: once you have the complete procedure in front of you, you may spot steps that can be cached or factorized. Whereas if the procedure is only opaque, high-level, reusable API methods, then you loose the ability to spot and remove redundant computations. So, there is a principle of data reuse/sharing (aka caching computed data that will be used later with no change, to spare CPU cycles) that can be made impossible by code reuse/sharing, because it obfuscates and abstracts data lifecycle.
This becomes critical on pixel loops: you want to collapse all pixel-wise operations into the same loop, to pay the memory I/O price only once. Which means that you will have to re-implement the same affine correction ($y = a * x + b$) into each loop using it, rather than having a reusable method that does just that in its own loop.
Don’t brace yourself
If you find yourself overwhelmed by some cryptic and random bugs that keep coming and that you can’t make sense of, don’t keep fighting blindly and take a step back. Then instrument debug helpers, or higher-level managers that keep track of internal states and give you a map of the software data values at any relevant point in its lifecycle. This is especially critical in asynchronous setups, where several threads create, access or compute stuff in parallel on different timelines, and the actual ordering sequence depends on runtime context.
Translated from English by : ChatGPT. In case of conflict, inconsistency or error, the English version shall prevail.