Ansel è progettato, non messo insieme alla bell’e meglio. Gli smanettoni possono anche divertirsi a «lavorare» per accelerare la fine di Darktable aumentandone il debito tecnico .

Cos’è la progettazione ?

La progettazione è un processo attraverso il quale svolgi una metodologia per portare una soluzione tecnica al problema di una persona. Il processo di progettazione è pensato per convergere verso la soluzione più adatta, contrastando al contempo l’impulso naturale a gettarsi sulla prima idea o su quella più comoda.

Senza requisiti e progettazione, programmare è l’arte di aggiungere bug a un file di testo vuoto. — Louis Srygley
  1. La progettazione parte da un caso d’uso : un compito definito da realizzare (su un’immagine), da parte di un utente definito, entro un intervallo di tempo definito. Se non c’è un caso d’uso, allora non c’è alcun problema da risolvere, e allora stai lontano dal tuo editor di codice.
  2. La progettazione richiede di conoscere l’utente di destinazione : istruzione/formazione, livello di abilità/padronanza, ecc.
  3. La progettazione richiede di comprendere i bisogni : nel contesto di Ansel, ciò richiederà spesso una certa conoscenza di storia dell’arte e di fotografia da camera oscura,
  4. Una volta compresi il problema e l’utente, la progettazione richiede di specificare :
    • le funzionalità attese della soluzione,
    • l’ambito della soluzione (in quale punto del ciclo di vita di un’immagine si colloca la soluzione ?),
    • i vincoli e i requisiti della soluzione (supportare un certo standard, permettere di elaborare n immagini per unità di tempo, ecc.),
    • una serie di test da completare che convaliderebbero la qualità della soluzione, così da limitare opinioni improduttive, pregiudizi e soggettività nel processo di convalida.
  5. Solo allora possono cominciare i mockup e il brainstorming, seguiti dai prototipi.

Cosa non è la progettazione ?

La progettazione non si occupa di :

  • requisiti vaghi di un utente indefinito, futuro o fantasticato,
  • «sarebbe bello se…» (è così che si creano collezioni di plugin incoerenti),
  • ciò che alle persone piace (per ogni cosa che piace a qualcuno, troverai qualcuno che la odia),
  • ciò che le persone pensano di volere (spesso non è ciò di cui hanno bisogno),
  • magiche parole d’ordine tecnologiche che «sono il futuro» e che, in quanto tali, vanno inserite ovunque, a prescindere dalla loro pertinenza o fattibilità (sì, sto parlando di IA, NFT, blockchain, ecc.)

Cos’è una buona progettazione

Una buona progettazione è :

  • minimalista,
  • robusta,
  • a prova di futuro,
  • generica e generalizzata,
  • mantenibile con risorse limitate,
  • fondata sulla scienza,
  • compatibile/interoperabile con gli standard del settore.

Dato che Ansel è un’applicazione basata sul flusso di lavoro, una buona progettazione tiene conto anche del flusso di lavoro nel suo insieme, e di dove il problema/soluzione vi si inserisce.

Come si realizza una buona progettazione ?

Per agevolare il processo di progettazione, la comunicazione dovrebbe rimanere concisa, focalizzata, e le persone che partecipano a tale processo dovrebbero assicurarsi di avere una corretta comprensione della teoria e del contesto tecnico coinvolti nell’ambito del problema/soluzione.

Va sottolineato che, sebbene il progetto sia guidato dal software, non tutte le soluzioni comportano la scrittura di codice. A volte (spesso ?), tutto ciò che serve è una migliore istruzione o una migliore documentazione.

Lo scopo di un sano processo di progettazione è evitare di condizionare le soluzioni troppo presto con la propria tecnologia/progettazione preferita ed evitare di perdersi nei tecnicismi, ma tornare sempre ai fondamenti e ai principi basilari di ciò che stiamo facendo: post-elaborare lotti potenzialmente numerosi di immagini raw per ogni sorta di supporto di destinazione.

Ciò è avvalorato dal fatto che gli utenti raramente conoscono i propri bisogni, o meglio, i bisogni che esprimono raramente sono la radice di ciò che effettivamente vogliono. Il difficile compito della progettazione è tagliare attraverso i rami per arrivare alla radice, perché risolvere il problema alla radice di solito si traduce in soluzioni più eleganti, generiche e minimaliste.

Prima vengono i problemi

Il primo passo del processo di progettazione di Ansel è inviare una richiesta di funzionalità, sulla Community. Le richieste di funzionalità sono state spostate fuori da Github perché questa piattaforma è poco accogliente per chi non programma e per chi non parla inglese (sebbene la Community supporti solo il francese e l’inglese).

Questa richiesta di funzionalità si concentrerà sul problema da risolvere e si asterrà dal proporre qualsiasi soluzione. Il problema sarà definito in termini di compiti da realizzare nel flusso di lavoro di un fotografo o di esito visivo atteso dell’immagine elaborata, ovvero in termini dell’obiettivo finale da raggiungere, non in termini di strumenti o tecnicismi ritenuti necessari. Ciò può portare a una discussione per scavare fino alle radici del problema, che di solito sono ben nascoste sotto ciò che l’utente crede sia il proprio problema .

In questa fase non viene accettata alcuna proposta di soluzione.

Poi vengono le soluzioni

Quando la definizione e l’ambito del problema sono concordati tra le persone coinvolte nella discussione, si possono proporre soluzioni. Un’ulteriore discussione può essere necessaria per valutare gli svantaggi e i vantaggi di ciascuna soluzione, portando all’adozione per principio della soluzione migliore. Le soluzioni sono definite dalle loro funzionalità (ovvero cosa dovrebbero fare), non dalla loro tecnologia o dai loro mezzi (come dovrebbero farlo).

In questa fase non viene accettata alcuna proposta di prototipo.

Le soluzioni adottate porteranno alla creazione di una nuova issue smistata nella bacheca Kanban di gestione del progetto .

Possono essere subordinate alla ricerca di aspetti teorici e tecnici per valutarne la fattibilità, nel qual caso verranno smistate nella colonna To research della bacheca Kanban. I risultati della ricerca saranno aggiunti alla issue originale finché la fattibilità della soluzione non sarà dimostrata. Quando lo sarà, la issue verrà spostata nella colonna «To do» della bacheca Kanban.

Le soluzioni adottate potrebbero essere smistate direttamente nella colonna To do se richiedono solo strumenti e tecnologie ben noti.

Idealmente, i punti da testare e la procedura di test per convalidare il prototipo dovrebbero essere scritti ancora prima di avere un prototipo funzionante. Come minimo, i test dovrebbero garantire che non sia avvenuta alcuna regressione nelle funzioni e negli strumenti correlati.

Poi vengono i prototipi

Solo le issue smistate nella colonna «To do» della bacheca Kanban di gestione del progetto  verranno lavorate, da me o da chiunque sia disposto ad affrontarle.

Il prototipo della soluzione sarà proposto in una pull request di un topic branch che collega la issue originale. I topic branch devono essere ribasati sul branch master, ad es. git rebase ustream master oppure, se aggiorni il tuo branch localmente con nuovi commit di master, esegui git pull upstream master --rebase o configura git globalmente  per fare pull tramite rebase anziché merge. Questo garantisce che la cronologia del tuo branch sia mantenuta pulita con il minimo sforzo, e mantiene pulita anche la cronologia di master quando la tua PR viene unita.

Quando la pull request del prototipo viene revisionata e se soddisfa gli standard di qualità del codice (vedi sotto) rispettando al contempo le specifiche della soluzione adottata, viene approvata e smistata automaticamente nella colonna «To test/validating» della bacheca Kanban di gestione del progetto .

Poi viene la convalida

Le pull request approvate verranno unite precocemente nel branch candidate o dev per il test, a seconda che possano corrompere le cronologie di modifica delle immagini (aggiungendo nuovi parametri ai moduli o cambiando lo schema del database). Questo branch sarà sempre il branch master con tutte le pull request in attesa di convalida in cima. Ciò è pensato per aiutare i test da parte di persone che non sono necessariamente al passo con l’unione manuale dei branch git. A differenza del branch dev, candidate non dovrebbe corrompere le tue modifiche.

Se non viene segnalato alcun bug o guasto dopo un certo tempo e il prototipo assolve correttamente al suo scopo iniziale, verrà unito in master e la issue correlata verrà chiusa e spostata nella colonna «Done» della bacheca Kanban di gestione del progetto .

Se il prototipo si rivela insoddisfacente, può essere rifiutato e ne dovrà essere elaborato un altro.

Consigli da esperto di un progettista navigato

Non tutti i problemi software sono problemi di codice

Molti problemi non richiedono più strumenti (o giocattoli) e più codice. Più codice è comunque sempre un male, e andrebbe evitato ove possibile. Molto spesso, il problema dell’utente è che non riesce a vedere come piegare le funzioni esistenti per soddisfare i propri bisogni. Ciò si risolve con l’istruzione, ovvero una migliore documentazione e più tutorial, e talvolta con una migliore interfaccia utente.

Ascolta ma non dare ascolto agli utenti

Gli utenti esprimono ciò che vogliono e ciò che gli piace, mai ciò di cui hanno bisogno. E non hai bisogno di dare loro ascolto per sapere cosa sarà:

  1. vorranno la stessa cosa che il loro vicino ha appena ottenuto,
  2. gli piacerà ciò a cui sono abituati.

E poi, per ogni cosa che piace a qualcuno, ne troverai un altro a cui non piace. Quindi il modo di Darktable di risolvere i conflitti è non risolverli, ma dare a ciascuno un’opzione, una modalità, una preferenza da attivare per quella cosa speciale che gli piace, come piace a lui. Questo significa più case nel tuo switch, più if annidati, più percorsi di codice che ora dovrai testare, correggere in futuro e mantenere, e poi più preferenze che ne nascondono altre nella finestra delle preferenze. Prima che tu te ne accorga, il codice è un tumore che nessuno capisce più, e correggerlo lo rende solo più complicato.

Quando gratti sotto la superficie, scopri che ciò di cui le persone hanno effettivamente bisogno è molto più vicino ai bisogni delle altre persone di quanto lo sia ciò che dicono di volere. Quindi puoi conciliare i bisogni molto più facilmente dei desideri, e senza compromessi. Ma allora devi rintracciare i bisogni alla radice sotto la volontà, e ciò richiede capacità di astrazione e psicologia.

I progettisti di interfacce utente sono idioti pericolosi

Chiunque veda, si concentri e si preoccupi solo dell’interfaccia utente è un idiota pericoloso. Se la tua GUI è complicata, significa molto di più di una semplice «GUI complicata» : significa che la complessità del tuo backend ha raggiunto il tuo frontend. Ho scoperto a mie spese che la complessità della GUI non è mai separata, e non può essere risolta separatamente, dalla complessità del backend e dall’architettura complessiva dell’applicazione. La GUI non è parallela all’architettura del backend, ne è la terminazione.

Il problema dei progettisti di interfacce utente è che tipicamente non programmano, o se lo fanno, fanno schifo nella programmazione di basso livello e nell’architettura del software. Perciò si concentrano su quel poco che vedono e capiscono (il tipico effetto lampione ), e producono solo progetti non attuabili che confliggono con ciò di cui il software ha effettivamente bisogno per funzionare. Perché quella GUI si limita a collegare l’input dell’utente al backend, e se ci servono così tanti widget, è perché l’architettura ha bisogno di così tanti input. Non puoi sfuggirvi : per rimuovere i widget, devi rimuovere gli input, il che significa che la tua architettura dovrà lavorare con meno gradi di libertà per primo. Ciò parte dalla semplificazione del backend, il che significa un puzzolente refactoring di vecchio codice impolverato che nessuno capisce più.

I problemi della GUI non si risolvono con disegni e mockup, i problemi della GUI si risolvono risolvendo i problemi del backend. Ma allora ti servono persone che comprendono entrambi i livelli, e potrebbero essere troppo costose per te.

Chiediti 36 volte al giorno qual era il problema che stavi cercando di risolvere

È facilissimo perdersi nei tecnicismi quando si programma in un linguaggio di basso livello lottando con librerie o API di terze parti, ma a volte la soluzione è semplice ed elegante e ti sei fatto trasportare troppo lontano tra puntatori e blocchi dei thread. Torna sempre al problema iniziale in questione, è la tua ancora di salvezza verso la semplicità.

Qual è il problema ? Chi lo affronta ? Quando ? Quanto spesso ? Facendo cosa ?

Il percorso migliore è il percorso più semplice verso la tua soluzione : poca tecnologia, poco codice, pochi livelli.

Documenta la tua progettazione scadente

Molte volte ho rifatto completamente una progettazione mentre la documentavo, perché è quando provi a spiegarla che ti rendi conto che è troppo complicata da spiegare, il che significa che è troppo complicata da capire. Se non riesci a spiegare la tua progettazione in un paio di paragrafi, o la tua documentazione ha troppi «se questo, allora quello», di solito ci sono due ragioni :

  1. la tua GUI non espone le informazioni pertinenti dove l’utente ne ha bisogno, quindi devi collegare metà della documentazione nella tua spiegazione per reindirizzare gli utenti a tutto ciò che devono sapere o controllare prima di usare quell’unica cosa che stavi documentando. La soluzione è riportare le informazioni pertinenti dove servono.
  2. la tua GUI ha troppi cassetti, elementi comprimibili, comportamenti contestuali, casi d’uso o preferenze nascoste, e coprire tutte le eventualità ti costringe a scrivere un romanzo. La soluzione è linearizzare il flusso di lavoro, magari rimuovere opzioni o suddividere le funzioni.

La GUI è il modo in cui gli utenti controllano il backend, ma è anche il luogo in cui vengono a conoscenza delle funzioni esistenti e di cosa fanno. La documentazione dovrebbe fornire contesto, linee guida e riferimenti su come facciamo le cose, ma la GUI dovrebbe spiegare da sé cosa fa.

Naturalmente, c’è un’ovvia limitazione a ciò : in un’applicazione fotografica, gli utenti devono comprendere la fotografia e il suo linguaggio, che coinvolge cose come la gamma dinamica, il gamut dei colori, la mappatura tonale, ecc. La GUI dovrebbe essere autoesplicativa su come è pensata per essere usata, non eliminare la necessità di imparare il mestiere (cosa va fatto e come).

La progettazione è un processo iterativo

Un’applicazione è un mondo virtuale in cui una piccola modifica può riordinare il modo in cui il resto dell’ecosistema si adatta attorno ad essa. Pertanto qualsiasi modifica di progettazione può innescare la necessità di cambiare altre cose attorno (fare il refactoring degli strumenti, spostare widget, sfoltire funzioni). Il che potrebbe poi innescare la necessità di correggere di nuovo la modifica iniziale. È un processo passo dopo passo in cui è folle persino provare a fare tutto perfettamente a ogni passo, ciò che conta è che ogni passo migliori l’ambiente rispetto al precedente.

A volte, la (ri)progettazione non può essere fatta a piccoli passi ma a grandi balzi : è quando rifai l’architettura. Questi balzi romperanno molte cose attorno ad essi, il che va bene se la nuova architettura è complessivamente più semplice e più robusta, e se le dai un po’ di tempo per riprendersi prima di riprendere in mano la mazza. Ma ciò creerà uno stato transitorio in cui la nuova progettazione apparirà peggiore della precedente. Questo ci dice che il modo in cui la progettazione viene percepita non è un input valido : la qualità della progettazione va valutata rispetto ai suoi obiettivi e misurata con metriche oggettive, non con sensazioni e test rapidi.

E a volte, alcuni passi sono errori e andrebbero annullati. La fallacia del costo sommerso  non dovrebbe essere usata per giustificare che una certa riprogettazione vada mantenuta perché è stata frutto di molto lavoro. È previsto che non tutta la ricerca e sviluppo arrivi in produzione.

Le sessioni acchiappa-la-talpa significano che la tua architettura ha fatto il suo tempo

Che tu continui a creare nuovi bug mentre correggi quelli vecchi, o che tu continui a creare casi limite estendendo una certa funzione, tutto punta nella stessa direzione : la tua architettura non può più essere piegata perché ha superato i suoi requisiti di progettazione. Potrebbe essere che il backend sia diventato troppo contorto o che l’architettura esistente non fosse davvero pensata per ciò che stai cercando di farle fare, ma in entrambi i casi dovrai rifare l’architettura e smettere di rimediare sul posto. Altrimenti, stai solo aggiungendo debito tecnico.

Ma allora, il costo dello sviluppo cambia scala e quel progetto da sabato pomeriggio potrebbe diventare un progetto lungo un mese.

Le buone pratiche sono linee guida, non regole

Le buone pratiche aiutano a sviluppare abitudini sane e codice pulito, a meno che tu non comprenda il problema che cercavano di risolvere e le usi al di fuori del loro ambito di validità. In tal caso, diventano culto del cargo : cercare di imitare gli effetti nella speranza che ciò risolva magicamente anche le cause.

La prima che viene in mente è il riuso/condivisione del codice. Se riusare lo stesso codice per funzioni (apparentemente simili) porta a troppe ramificazioni interne (if annidati, switch/case, ecc.) per gestire tutti i percorsi possibili, ciò che guadagni sul volume di codice lo perdi in complessità ciclomatica e, tra l’altro, le tue funzioni non sono così simili come pensavi.

Inoltre, duplicare il codice potrebbe essere un punto di partenza per ottimizzare localmente il duplicato in seguito: una volta che hai davanti la procedura completa, potresti individuare passi che possono essere memorizzati in cache o fattorizzati. Mentre se la procedura è fatta solo di metodi API opachi, di alto livello e riutilizzabili, allora perdi la capacità di individuare e rimuovere calcoli ridondanti. Quindi, c’è un principio di riuso/condivisione dei dati (ovvero mettere in cache dati calcolati che verranno usati in seguito senza modifiche, per risparmiare cicli di CPU) che può essere reso impossibile dal riuso/condivisione del codice, perché offusca e astrae il ciclo di vita dei dati.

Ciò diventa critico nei cicli sui pixel: vuoi far collassare tutte le operazioni pixel per pixel nello stesso ciclo, per pagare il prezzo dell’I/O di memoria una sola volta. Il che significa che dovrai reimplementare la stessa correzione affine ($y = a * x + b$) in ogni ciclo che la usa, invece di avere un metodo riutilizzabile che fa esattamente questo nel suo ciclo.

Non irrigidirti

Se ti ritrovi sopraffatto da bug criptici e casuali che continuano ad arrivare e a cui non riesci a dare un senso, non continuare a combattere alla cieca e fai un passo indietro. Poi predisponi degli helper di debug, o gestori di livello più alto che tengono traccia degli stati interni e ti danno una mappa dei valori dei dati del software in qualsiasi punto rilevante del suo ciclo di vita. Ciò è particolarmente critico nelle configurazioni asincrone, dove diversi thread creano, accedono o elaborano cose in parallelo su linee temporali diverse, e l’effettiva sequenza di ordinamento dipende dal contesto di runtime.


Translated from English by : Claude. In case of conflict, inconsistency or error, the English version shall prevail.