Ansel is ontworpen, niet in elkaar geflanst. Hackers vinden het misschien leuk om te „werken" aan het versnellen van de teloorgang van Darktable door de technische schuld ervan te vergroten.
Wat is ontwerpen?
Ontwerpen is een proces waarbij je een methodologie ontrolt om een technische oplossing te bieden voor het probleem van een mens. Het ontwerpproces is bedoeld om te convergeren naar de meest geschikte oplossing, terwijl het de natuurlijke drang bestrijdt om je halsoverkop op het eerste of het meest comfortabele idee te storten.
Zonder vereisten en ontwerp is programmeren de kunst van het toevoegen van bugs aan een leeg tekstbestand. — Louis Srygley
- Ontwerpen begint met een usecase: een gedefinieerde taak die moet worden volbracht (op een foto), door een gedefinieerde gebruiker, binnen een gedefinieerd tijdsbestek. Als er geen usecase is, dan is er geen probleem op te lossen, blijf dan uit de buurt van je code-editor.
- Ontwerpen vereist dat je de doelgebruiker kent: opleiding/training, niveau van vakmanschap/meesterschap, enz.
- Ontwerpen vereist dat je de behoeften begrijpt: in de context van Ansel zal dat vaak enige kennis van kunstgeschiedenis en donkerekamerfotografie vergen,
- Zodra het probleem en de gebruiker begrepen zijn, vereist ontwerpen dat je het volgende specificeert:
- de verwachte functionaliteiten van de oplossing,
- het bereik van de oplossing (waar in de levenscyclus van een afbeelding bevindt de oplossing zich?),
- de beperkingen en vereisten van de oplossing (het ondersteunen van een bepaalde standaard, het mogelijk maken om n afbeeldingen per tijdseenheid te verwerken, enz.),
- een reeks te voltooien tests die de kwaliteit van de oplossing zouden valideren, om onproductieve meningen, vooroordelen en subjectiviteit in het validatieproces te beperken.
- Pas dan kunnen de mockups en het brainstormen beginnen, gevolgd door prototypes.
Wat ontwerpen niet is?
Ontwerpen houdt zich niet bezig met:
- vage vereisten van een ongedefinieerde, toekomstige of gefantaseerde gebruiker,
- „het zou cool zijn als…" (zo creëer je inconsistente verzamelingen plug-ins),
- wat mensen leuk vinden (voor alles wat iemand leuk vindt, vind je wel iemand die het haat),
- wat mensen denken dat ze willen (het is vaak niet wat ze nodig hebben),
- magische tech-modewoorden die „de toekomst zijn" en, als zodanig, overal ingeplugd moeten worden, ongeacht hun relevantie of haalbaarheid (ja, ik heb het over AI, NFT, blockchain, enz.)
Wat goed ontwerp is
Goed ontwerp is:
- minimalistisch,
- robuust,
- toekomstbestendig,
- generiek en gegeneraliseerd,
- onderhoudbaar met beperkte middelen,
- gestoeld op wetenschap,
- compatibel/interoperabel met industriestandaarden.
Aangezien Ansel een op workflows gebaseerde applicatie is, houdt goed ontwerp ook rekening met de workflow als geheel, en met waar het probleem/de oplossing daarin past.
Hoe wordt goed ontwerp gedaan?
Om het ontwerpproces te helpen, moet de communicatie beknopt en gefocust blijven, en de mensen die aan dat proces deelnemen moeten ervoor zorgen dat ze een degelijk begrip hebben van de theorie en de technische achtergrond die bij het probleem/de oplossing komen kijken.
Er moet worden benadrukt dat, hoewel het project softwaregestuurd is, niet alle oplossingen coderen inhouden. Soms (vaak?) is betere educatie of betere documentatie alles wat nodig is.
Het doel van een gezond ontwerpproces is om te voorkomen dat de oplossingen te vroeg worden beïnvloed door iemands favoriete ontwerp/tech en om te voorkomen dat je verdwaalt in de technische details, maar om altijd terug te keren naar de kernprincipes en -basis van wat we doen: het nabewerken van mogelijk grote batches raw-afbeeldingen voor allerlei soorten uitvoermedia.
Dit wordt gestaafd door het feit dat gebruikers zelden hun eigen behoeften kennen, of beter gezegd, de behoeften die ze uiten zijn zelden de wortel van wat ze werkelijk willen. De moeilijke taak van het ontwerpen is om door de takken heen te snoeien om bij de wortel te komen, want het oplossen van het wortelprobleem leidt meestal tot elegantere, generiekere en minimalistischere oplossingen.
Problemen komen eerst
De eerste stap van het ontwerpproces van Ansel is het indienen van een functieverzoek, op de Community. Functieverzoeken zijn weggehaald uit Github omdat dat platform onvriendelijk is voor niet-programmeurs en niet-Engelstaligen (hoewel de Community alleen Frans en Engels ondersteunt).
Dit functieverzoek zal zich richten op het op te lossen probleem en zich onthouden van het voorstellen van welke oplossing dan ook. Het probleem zal worden gedefinieerd in termen van te volbrengen taken in de workflow van een fotograaf of van het verwachte visuele resultaat van de bewerkte afbeelding, oftewel in termen van het te bereiken einddoel, niet in termen van gereedschap of technische details waarvan gedacht wordt dat ze nodig zijn. Dit kan leiden tot een discussie om de wortels van het probleem uit te graven, die meestal goed verborgen liggen onder wat de gebruiker denkt dat zijn probleem is .
In dit stadium wordt geen enkel oplossingsvoorstel geaccepteerd.
Oplossingen komen tweede
Wanneer over de definitie en het bereik van het probleem overeenstemming is bereikt tussen de mensen die bij de discussie betrokken zijn, mogen oplossingen worden voorgesteld. Verdere discussie kan nodig zijn om de nadelen en voordelen van elke oplossing te evalueren, wat ertoe leidt dat de beste oplossing in principe wordt aangenomen. Oplossingen worden gedefinieerd door hun functionaliteiten (oftewel wat ze zouden moeten doen), niet door hun technologie of middelen (hoe ze het zouden moeten doen).
In dit stadium wordt geen enkel prototypevoorstel geaccepteerd.
Aangenomen oplossingen zullen leiden tot een nieuwe issue die getrieerd wordt op het Kanban-bord voor projectbeheer .
Ze kunnen afhankelijk zijn van onderzoek naar theoretische en technische aspecten om hun haalbaarheid te beoordelen, in welk geval ze getrieerd worden naar de kolom To research van het Kanban-bord. De onderzoeksbevindingen worden aan de oorspronkelijke issue toegevoegd totdat de haalbaarheid van de oplossing is bewezen. Wanneer dat het geval is, wordt de issue verplaatst naar de kolom „To do" van het Kanban-bord.
Aangenomen oplossingen kunnen rechtstreeks naar de kolom To do getrieerd worden als ze alleen bekende gereedschappen en technologieën vereisen.
Idealiter zouden de te testen punten en de testprocedure om het prototype te valideren al geschreven moeten zijn nog voordat er een werkend prototype is. Op zijn minst zouden de tests moeten waarborgen dat er geen regressie is opgetreden in gerelateerde functies en gereedschappen.
Prototypes komen derde
Er wordt alleen gewerkt aan de issues die getrieerd zijn in de kolom „To do" van het Kanban-bord voor projectbeheer , door mijzelf of door iedereen die ze wil aanpakken.
Het prototype van de oplossing zal worden voorgesteld in een pull request van een topicbranch die naar de oorspronkelijke issue linkt. Topicbranches moeten worden gerebased op de master-branch, bijv. git rebase ustream master of, als je je branch lokaal bijwerkt met nieuwe master-commits, doe je git pull upstream master --rebase of stel je git globaal in om te pullen via rebase in plaats van merge. Dit zorgt ervoor dat de geschiedenis van je branch met minimale moeite schoon blijft, en houdt ook de master-geschiedenis schoon wanneer je PR wordt gemerged.
Wanneer het pull request van het prototype is beoordeeld en als het voldoet aan de normen voor codekwaliteit (zie hieronder) en aan de specificaties van de aangenomen oplossing, wordt het goedgekeurd en automatisch getrieerd naar de kolom „To test/validating" van het Kanban-bord voor projectbeheer .
Validatie komt vierde
Goedgekeurde pull requests worden vroeg gemerged in de candidate- of dev-branch om te testen, afhankelijk van of ze bewerkingsgeschiedenissen van afbeeldingen kunnen breken (door nieuwe moduleparameters toe te voegen of het databaseschema te wijzigen). Deze branch zal altijd de master-branch zijn met bovenop alle pull requests die in afwachting van validatie zijn. Dit is bedoeld om het testen te vergemakkelijken voor mensen die niet noodzakelijkerwijs bekend zijn met het handmatig mergen van git-branches. In tegenstelling tot de dev-branch zou candidate je bewerkingen niet mogen breken.
Als er na enige tijd geen bug of breuk wordt gemeld en het prototype zijn oorspronkelijke doel correct vervult, wordt het gemerged in master en wordt de bijbehorende issue gesloten en verplaatst naar de kolom „Done" van het Kanban-bord voor projectbeheer .
Als het prototype onbevredigend blijkt, kan het worden afgewezen en zal er een ander uitgewerkt moeten worden.
Pro-tips van een doorgewinterde ontwerper
Niet alle software-problemen zijn codeer-problemen
Veel problemen vereisen niet meer gereedschappen (of speeltjes), en meer code. Meer code is sowieso altijd slecht, en moet waar mogelijk worden vermeden. Heel vaak is het probleem van de gebruiker dat hij niet ziet hoe hij bestaande functies kan buigen om in zijn behoeften te voorzien. Dit wordt opgelost door educatie, oftewel betere documentatie en meer tutorials, en soms door een betere UI.
Luister naar gebruikers, maar luister niet naar ze
Gebruikers uiten wat ze willen en wat ze leuk vinden, nooit wat ze nodig hebben. En je hoeft niet naar ze te luisteren om te weten wat het zal zijn:
- ze zullen hetzelfde willen als wat hun buurman net gekregen heeft,
- ze zullen leuk vinden waaraan ze gewend zijn.
En dan, voor alles wat iemand leuk vindt, vind je wel een ander die het niet leuk vindt. Dus de manier waarop Darktable conflicten oplost, is door conflicten niet op te lossen, maar iedereen een optie, een modus, een voorkeur te geven om dat speciale ding in te schakelen dat ze leuk vinden, zoals ze het leuk vinden. Dit betekent meer case in je switch, meer geneste if, meer codepaden die je nu moet testen, debuggen en in de toekomst onderhouden, en dan meer voorkeuren die de andere verbergen in het voorkeurenvenster. Voor je het weet is de code een tumor die niemand meer begrijpt, en het repareren ervan maakt het alleen maar ingewikkelder.
Wanneer je onder de oppervlakte krast, ontdek je dat wat mensen werkelijk nodig hebben veel dichter bij de behoeften van andere mensen ligt dan wat ze zeggen te willen. Dus je kunt de behoeften veel gemakkelijker met elkaar verzoenen dan de verlangens, en zonder compromissen. Maar dan moet je de wortelbehoeften onder de wil traceren, en dat vergt abstractievermogen en psychologie.
UI-ontwerpers zijn gevaarlijke idioten
Iedereen die alleen de UI ziet, zich erop focust en er alleen om geeft, is een gevaarlijke idioot. Als je GUI ingewikkeld is, betekent dat veel meer dan alleen een „ingewikkelde GUI": het betekent dat de complexiteit van je backend je frontend heeft bereikt. Ik heb op de harde manier ontdekt dat GUI-complexiteit nooit los staat van, en niet los kan worden opgelost van, backend-complexiteit en de algehele applicatiearchitectuur. GUI staat niet parallel aan de backend-architectuur, het is de terminatie ervan.
Het probleem met UI-ontwerpers is dat ze doorgaans niet coderen, of als ze het wel doen, dat ze slecht zijn in low-level programmeren en softwarearchitectuur. Dus focussen ze zich op het weinige dat ze zien en begrijpen (typisch lantaarnpaaleffect ), en produceren ze alleen niet-uitvoerbare ontwerpen die in conflict zijn met wat de software daadwerkelijk nodig heeft om te werken. Want die GUI verbindt alleen gebruikersinvoer met de backend, en als we zoveel widgets nodig hebben, is dat omdat de architectuur zoveel invoer nodig heeft. Je kunt er niet aan ontkomen: om widgets te verwijderen, moet je invoer verwijderen, wat betekent dat je architectuur eerst met minder vrijheidsgraden zal moeten werken. Dat begint met het vereenvoudigen van de backend, wat een stinkende refactoring betekent van stoffige oude code die niemand meer begrijpt.
Je lost GUI-problemen niet op met tekeningen en mockups, je lost GUI-problemen op door backend-problemen op te lossen. Maar dan heb je gasten nodig die beide niveaus begrijpen, en die zijn misschien te duur voor je.
Vraag jezelf 36 keer per dag af wat het probleem was dat je probeerde op te lossen
Het is supermakkelijk om te verdwalen in technische details wanneer je programmeert in een low-level taal en vecht met bibliotheken of API’s van derden, maar soms is de oplossing simpel en elegant en ben je te ver meegesleept in pointers en threadlocks. Keer altijd terug naar het oorspronkelijke probleem, dat is je reddingslijn naar eenvoud.
Wat is het probleem? Wie krijgt ermee te maken? Wanneer? Hoe vaak? Terwijl hij wat doet?
Het beste pad is het eenvoudigste pad naar je oplossing: low-tech, weinig code, weinig lagen.
Documenteer je waardeloze ontwerp
Vaak heb ik een ontwerp volledig opnieuw gedaan terwijl ik het documenteerde, want het is wanneer je het probeert uit te leggen dat je je realiseert dat het te ingewikkeld is om uit te leggen, wat betekent dat het te ingewikkeld is om te begrijpen. Als je je ontwerp niet in een paar alinea’s kunt uitleggen, of als je documentatie te veel „als dit, dan dat" bevat, zijn er meestal twee redenen:
- je GUI toont de relevante informatie niet waar de gebruiker die nodig heeft, dus je moet de helft van de documentatie in je uitleg linken om gebruikers door te verwijzen naar alles wat ze moeten weten of controleren voordat ze het ene ding gebruiken dat je aan het documenteren was. De oplossing is om relevante informatie terug te brengen naar waar ze nodig is.
- je GUI heeft te veel lades, inklapbare dingen, contextueel gedrag, use cases of verborgen voorkeuren, en het afdekken van alle scenario’s zorgt ervoor dat je een roman schrijft. De oplossing is om de workflow te lineariseren, misschien opties te verwijderen of functies op te splitsen.
De GUI is hoe gebruikers de backend bedienen, maar het is ook waar ze leren over bestaande functies en wat die doen. De documentatie zou context, richtlijnen en referenties moeten bieden over hoe we dingen doen, maar de GUI zou zelf moeten uitleggen wat hij doet.
Natuurlijk is daar een voor de hand liggende beperking aan: in een fotografieapplicatie moeten gebruikers fotografie en haar taal begrijpen, wat dingen omvat als dynamisch bereik, kleurgamut, tone mapping, enz. De GUI zou zelfverklarend moeten zijn over hoe hij gebruikt hoort te worden, en niet de noodzaak wegnemen om het vak te leren (wat gedaan moet worden en hoe).
Ontwerpen is een iteratief proces
Een applicatie is een virtuele wereld waarin één kleine verandering kan herordenen hoe de rest van het ecosysteem zich eromheen aanpast. Daarom kan elke ontwerpverandering de noodzaak triggeren om andere dingen eromheen te veranderen (gereedschappen refactoren, widgets verplaatsen, functies snoeien). Wat dan weer de noodzaak kan triggeren om de oorspronkelijke verandering opnieuw te corrigeren. Het is een stapsgewijs proces waarin het dwaas is om zelfs maar te proberen alles bij elke stap goed te doen; wat telt is dat elke stap de omgeving verbetert ten opzichte van de vorige.
Soms kan (her)ontwerp niet in kleine stappen gebeuren, maar in grote sprongen: dat is wanneer je de architectuur opnieuw doet. Deze sprongen zullen veel dingen eromheen breken, wat oké is als de nieuwere architectuur over het geheel eenvoudiger en robuuster is, en als je het wat tijd geeft om te herstellen voordat je de moker weer oppakt. Maar dat zal een overgangstoestand creëren waarin het nieuwe ontwerp slechter zal lijken dan het vorige. Dit vertelt ons dat hoe het ontwerp wordt waargenomen geen geldige input is: ontwerpkwaliteit moet worden beoordeeld aan de hand van zijn doelen en geëvalueerd met objectieve metrieken, niet met gevoelens en snelle tests.
En soms zijn sommige stappen fouten en zouden ze teruggedraaid moeten worden. De sunk cost-drogreden zou niet moeten worden gebruikt om te rechtvaardigen dat een herontwerp behouden moet blijven omdat het veel werk kostte om te bereiken. Het is te verwachten dat niet al het onderzoek en de ontwikkeling het tot in de productie halen.
Whack-a-mole-sessies betekenen dat je architectuur haar tijd heeft gehad
Of je nu steeds nieuwe bugs creëert terwijl je oude repareert, of je steeds edge cases creëert door een functie uit te breiden, het wijst allemaal in dezelfde richting: je architectuur kan niet verder gebogen worden omdat ze haar ontwerpvereisten is ontgroeid. Het kan zijn dat de backend te ingewikkeld is geworden of het kan zijn dat de bestaande architectuur echt niet was gepland voor wat je haar probeert te laten doen, maar hoe dan ook zul je de architectuur opnieuw moeten doen en moeten stoppen met ter plekke hacken. Anders voeg je alleen maar technische schuld toe.
Maar dan verandert de ontwikkelingskosten van schaal en kan dat zaterdagmiddagproject een maandenlang project worden.
Best practices zijn richtlijnen, geen regels
Best practices helpen bij het ontwikkelen van gezonde gewoontes en schone code, tenzij je het probleem niet begrijpt dat ze probeerden op te lossen en je ze buiten hun geldigheidsbereik gebruikt. In dat geval worden ze cargocultus: proberen de effecten na te bootsen in de hoop dat het op magische wijze ook de oorzaken zal repareren.
Het eerste dat in me opkomt is codehergebruik/code delen. Als het hergebruiken van dezelfde code voor (schijnbaar vergelijkbare) functies leidt tot te veel interne vertakking (geneste if, switch/case, enz.), om alle mogelijke paden af te handelen, dan wordt wat je wint op codevolume verloren op cyclomatische complexiteit, en trouwens, je functies zijn niet zo vergelijkbaar als je dacht.
Ook kan het dupliceren van code een startpunt zijn om het duplicaat later lokaal te optimaliseren: zodra je de complete procedure voor je hebt, kun je stappen ontdekken die gecachet of gefactoriseerd kunnen worden. Terwijl als de procedure alleen ondoorzichtige, high-level, herbruikbare API-methodes is, je het vermogen verliest om redundante berekeningen te ontdekken en te verwijderen. Dus is er een principe van datahergebruik/data delen (oftewel het cachen van berekende data die later zonder wijziging gebruikt zullen worden, om CPU-cycli te sparen) dat onmogelijk gemaakt kan worden door codehergebruik/code delen, omdat het de levenscyclus van data verhult en abstraheert.
Dit wordt kritiek bij pixel-loops: je wilt alle pixelsgewijze bewerkingen samenvouwen in dezelfde loop, om de prijs van geheugen-I/O maar één keer te betalen. Wat betekent dat je dezelfde affiene correctie ($y = a * x + b$) opnieuw zult moeten implementeren in elke loop die deze gebruikt, in plaats van een herbruikbare methode te hebben die precies dat doet in zijn eigen loop.
Zet je niet schrap
Als je jezelf overweldigd vindt door cryptische en willekeurige bugs die maar blijven komen en waar je geen wijs uit kunt worden, blijf dan niet blindelings vechten en neem een stap terug. Instrumenteer dan debug-hulpjes, of high-level managers die interne toestanden bijhouden en je een kaart geven van de datawaarden van de software op elk relevant punt in haar levenscyclus. Dit is vooral kritiek in asynchrone opstellingen, waar meerdere threads dingen parallel creëren, benaderen of berekenen op verschillende tijdlijnen, en de daadwerkelijke ordeningsvolgorde afhangt van de runtimecontext.
Translated from English by : Claude. In case of conflict, inconsistency or error, the English version shall prevail.