Ansel está diseñado, no improvisado. Los hackers pueden disfrutar ’trabajando’ en acelerar el descenso de Darktable aumentando su deuda técnica .

¿Qué es el diseño?

El diseño es un proceso por el cual se desarrolla una metodología para brindar una solución técnica a un problema humano. El proceso de diseño está destinado a converger en la solución más adecuada, mientras se lucha contra la tendencia natural a apresurarse hacia la primera o la idea más cómoda.

Sin requerimientos ni diseño, programar es el arte de añadir errores a un archivo de texto vacío. — Louis Srygley
  1. El diseño comienza con un caso de uso: una tarea definida a lograr (en una imagen), por un usuario definido, en un tiempo definido. Si no hay caso de uso, entonces no hay problema que resolver, entonces mantente alejado de tu editor de código.
  2. El diseño requiere conocer al usuario objetivo: educación/capacitación, nivel de artesanía/maestría, etc.
  3. El diseño requiere entender las necesidades: en el contexto de Ansel, a menudo se necesitará algún conocimiento de historia del arte y fotografía en cuarto oscuro.
  4. Una vez que el problema y el usuario sean comprendidos, el diseño requiere especificar:
    • las funcionalidades esperadas de la solución.
    • el alcance de la solución (¿dónde se encuentra la solución en el ciclo de vida de una imagen?).
    • las restricciones y requisitos de la solución (apoyar algún estándar, permitir procesar n imágenes por unidad de tiempo, etc.).
    • una serie de pruebas a completar que validarían la calidad de la solución, para limitar opiniones no productivas, sesgos y subjetividad en el proceso de validación.
  5. Solo entonces pueden comenzar los prototipos y sesiones de ideas.

¿Qué no es diseño?

El diseño no se ocupa de:

  • requisitos vagos de un usuario indefinido, futuro o imaginado,
  • “sería genial si…” (así es como se crean colecciones de complementos inconsistentes),
  • lo que le gusta a la gente (por cada cosa que a alguien le gusta, encontrarás a alguien que la odia),
  • lo que la gente cree que quiere (a menudo no es lo que necesita),
  • palabras de moda tecnológicas que “son el futuro” y, como tales, necesitan ser incorporadas en todas partes, sin importar su relevancia o viabilidad (sí, estoy hablando de IA, NFT, blockchain, etc.)

¿Qué es buen diseño?

Un buen diseño es:

  • minimalista.
  • robusto.
  • preparado para el futuro.
  • genérico y generalizado.
  • mantenible con recursos limitados.
  • informado por la ciencia.
  • compatible/interoperable con estándares industriales.

Dado que Ansel es una aplicación basada en flujos de trabajo, un buen diseño también tiene en cuenta el flujo de trabajo como un todo y dónde encajan el problema/solución en él.

¿Cómo se hace un buen diseño?

Para ayudar en el proceso de diseño, la comunicación debe ser concisa, centrada y las personas que participan en ese proceso deben asegurarse de tener una comprensión adecuada de la teoría y el trasfondo técnico involucrado en el alcance del problema/solución.

Se debe enfatizar que, aunque el proyecto está impulsado por software, no todas las soluciones involucran programación. A veces (¿a menudo?), una mejor educación o mejor documentación es todo lo que se necesita.

El propósito de un proceso de diseño sano es evitar sesgar las soluciones demasiado pronto con un diseño/tecnología favorita y evitar perderse en las complejidades técnicas, pero siempre volver a los principios básicos de lo que estamos haciendo: post-procesar potencialmente grandes lotes de imágenes en bruto para todo tipo de medios de salida.

Esto es respaldado por el hecho de que los usuarios rara vez conocen sus propias necesidades, o más bien, las necesidades que expresan rara vez son la raíz de lo que realmente quieren. La tarea difícil del diseño es cortar a través de las ramas para llegar a la raíz, porque resolver el problema raíz generalmente resulta en soluciones más elegantes, genéricas y minimalistas.

Los problemas vienen primero

El primer paso del proceso de diseño de Ansel es presentar una solicitud de función en la Comunidad. Las solicitudes de funciones se han movido de Github porque esta plataforma no es acogedora para personas no programadoras y no angloparlantes (aunque la Comunidad solo admite francés e inglés).

Esta solicitud de función se centrará en el problema a resolver y se abstendrá de proponer cualquier solución. El problema se definirá en términos de tareas a lograr en el flujo de trabajo de un fotógrafo o resultado visual esperado de la imagen procesada, es decir, en términos del objetivo final a lograr, no en términos de herramientas o tecnicismos que se creen necesarios. Esto puede llevar a una discusión para profundizar en las raíces del problema, que generalmente están bien ocultas debajo de lo que el usuario cree que es su problema .

No se acepta ninguna propuesta de solución en esta etapa.

Las soluciones vienen segunda

Cuando la definición y el alcance del problema sean acordados entre las personas involucradas en la discusión, se podrán proponer soluciones. Puede ser necesario un debate posterior para evaluar los inconvenientes y beneficios de cada solución, llevando a la adopción de la mejor solución en principio. Las soluciones se definen por sus funcionalidades (es decir, lo que deberían hacer), no por su tecnología o medios (cómo deberían hacerlo).

No se aceptan propuestas de prototipo en esta etapa.

Las soluciones adoptadas llevarán a una nueva incidencia que será triageada en el tablero Kanban de gestión de proyectos .

Pueden estar condicionadas a la investigación de aspectos teóricos y técnicos para evaluar su viabilidad, en cuyo caso estarán triageadas en la columna Por investigar del tablero Kanban. Los hallazgos de la investigación se añadirán a la incidencia original hasta que la viabilidad de la solución esté comprobada. Cuando lo esté, la incidencia se moverá a la columna ‘Por hacer’ del tablero Kanban.

Las soluciones adoptadas pueden ser directamente triageadas para la columna Por hacer si solo requieren herramientas y técnicas bien conocidas.

Idealmente, los puntos a probar y el procedimiento de prueba para validar el prototipo deberían estar escritos incluso antes de tener un prototipo funcional. Al menos, las pruebas deben asegurar que no ocurrió regresión en las características y herramientas relacionadas.

Los prototipos son el tercer paso

Solo las incidencias triageadas en la columna ‘Por hacer’ del tablero Kanban de gestión de proyectos  serán trabajadas, por mí mismo o por cualquiera dispuesto a abordarlas.

El prototipo de la solución se propondrá en una solicitud de extracción de una rama de tema que enlace la incidencia original. Las ramas de tema necesitan ser alineadas con la rama master por ejemplo, git rebase ustream master o, si actualizas tu rama localmente con nuevos commits de master, haz git pull upstream master --rebase o configura git globalmente  para extraer a través de rebase en lugar de merge. Esto asegura que el historial de tu rama se mantenga limpio con el mínimo esfuerzo, y también mantiene limpio el historial de master cuando se fusiona tu PR.

Cuando la solicitud de extracción de prototipo se revise y si cumple con los estándares de calidad del código (ver más abajo) mientras se ajusta a las especificaciones de la solución adoptada, será aprobada y automáticamente triageada a la columna ‘Por probar/validar’ del tablero Kanban de gestión de proyectos .

La validación es el cuarto paso

Las solicitudes de extracción aprobadas se fusionarán tempranamente en la rama candidate o dev para pruebas, dependiendo de si pueden romper los historiales de edición de imágenes (al agregar nuevos parámetros de módulo o cambiar el esquema de la base de datos). Esta rama siempre será la rama principal con todas las solicitudes de extracción pendientes de validación en la parte superior. Esto está destinado a ayudar en las pruebas de personas que no necesariamente están al día con la fusión manual de ramas de git. A diferencia de la rama dev, candidate no debería romper tus ediciones.

Si no se reporta ningún error o interrupción después de algún tiempo y el prototipo cumple su propósito inicial correctamente, se fusionará en master y la incidencia relacionada se cerrará y se moverá a la columna ‘Hecho’ del tablero Kanban de gestión de proyectos .

Si el prototipo demuestra ser insatisfactorio, puede ser rechazado y otro necesitará ser trabajado.

Consejos de un diseñador experimentado

No todos los problemas de software son problemas de código

Muchos problemas no requieren más herramientas (ni juguetes) ni más código. Más código siempre es malo de todos modos, y debería evitarse siempre que sea posible. Muy a menudo, el problema del usuario es que no ve cómo adaptar las funciones existentes para satisfacer sus necesidades. Esto se resuelve con educación, es decir, mejor documentación y más tutoriales, y a veces con una mejor interfaz de usuario.

Escucha pero no escuches a los usuarios

Los usuarios expresan lo que quieren y lo que les gusta, nunca lo que necesitan. Y no necesitas escucharlos para saber lo que será:

  1. querrán lo mismo que su vecino acaba de conseguir,
  2. les gustará aquello a lo que están acostumbrados.

Y luego, por cada cosa que a uno le gusta, encontrarás a otro al que le disgusta. Así que la forma que tiene Darktable de resolver los conflictos es no resolverlos, sino darle a cada uno una opción, un modo, una preferencia para activar esa cosa especial que le gusta, tal como le gusta. Esto significa más case en tu switch, más if anidados, más rutas de código que ahora tendrás que probar, depurar y mantener en el futuro, y luego más preferencias que ocultan a las demás en la ventana de preferencias. Antes de que te des cuenta, el código es un tumor que ya nadie entiende, y arreglarlo solo lo vuelve más complicado.

Cuando rascas bajo la superficie, descubres que lo que la gente realmente necesita está mucho más cerca de las necesidades de los demás que lo que dicen querer. Así que puedes reconciliar las necesidades mucho más fácilmente que los deseos, y sin ceder. Pero entonces tienes que rastrear las necesidades de raíz por debajo de la voluntad, y eso requiere capacidad de abstracción y psicología.

Los diseñadores de interfaces de usuario son idiotas peligrosos

Todo aquel que solo ve, se centra y se preocupa por la interfaz de usuario es un idiota peligroso. Si tu interfaz gráfica es complicada, significa mucho más que solo una “interfaz gráfica complicada”: significa que la complejidad de tu backend ha llegado a tu frontend. He descubierto por las malas que la complejidad de la interfaz gráfica nunca es independiente, y no puede resolverse por separado, de la complejidad del backend y de la arquitectura general de la aplicación. La interfaz gráfica no es paralela a la arquitectura del backend, es su terminación.

El problema de los diseñadores de interfaces de usuario es que normalmente no programan, o si lo hacen, son malos en programación de bajo nivel y arquitectura de software. Así que se centran en lo poco que ven y entienden (el típico efecto farola ), y solo producen diseños no implementables que entran en conflicto con lo que el software realmente necesita para funcionar. Porque esa interfaz gráfica solo conecta la entrada del usuario con el backend, y si necesitamos tantos widgets, es porque la arquitectura necesita tantas entradas. No puedes escapar de ello: para eliminar widgets, necesitas eliminar entradas, lo que significa que tu arquitectura tendrá que funcionar con menos grados de libertad primero. Eso empieza por simplificar el backend, lo que significa una refactorización maloliente de código viejo y polvoriento que ya nadie entiende.

Los problemas de interfaz gráfica no se resuelven con dibujos y maquetas, los problemas de interfaz gráfica se resuelven resolviendo los problemas del backend. Pero entonces necesitas gente que entienda ambos niveles, y puede que te resulten demasiado caros.

Pregúntate 36 veces al día cuál era el problema que intentabas resolver

Es muy fácil perderse en tecnicismos cuando programas en un lenguaje de bajo nivel y peleas con bibliotecas o API de terceros, pero a veces la solución es simple y elegante y te dejaste arrastrar demasiado lejos entre punteros y bloqueos de hilos. Vuelve siempre al problema inicial que tienes entre manos, esa es tu cuerda de salvamento hacia la simplicidad.

¿Cuál es el problema? ¿Quién lo tiene? ¿Cuándo? ¿Con qué frecuencia? ¿Haciendo qué?

El mejor camino es el camino más simple hacia tu solución: pocas tecnologías, poco código, pocas capas.

Documenta tu diseño de mierda

Muchas veces rehíce por completo un diseño mientras lo documentaba, porque es al intentar explicarlo cuando te das cuenta de que es demasiado complicado de explicar, lo que significa que es demasiado complicado de entender. Si no puedes explicar tu diseño en un par de párrafos, o tu documentación tiene demasiados “si esto, entonces aquello”, suele haber dos razones:

  1. tu interfaz gráfica no expone la información relevante donde el usuario la necesita, así que tienes que enlazar la mitad de la documentación en tu explicación para redirigir a los usuarios a todo lo que necesitan saber o comprobar antes de usar la única cosa que estabas documentando. La solución es traer de vuelta la información relevante allí donde se necesita.
  2. tu interfaz gráfica tiene demasiadas bandejas, elementos plegables, comportamientos contextuales, casos de uso o preferencias ocultas, y cubrir todos los frentes te hace escribir una novela. La solución es linealizar el flujo de trabajo, quizá eliminar opciones o dividir funciones.

La interfaz gráfica es cómo los usuarios controlan el backend, pero también es donde aprenden sobre las funciones existentes y lo que hacen. La documentación debería proporcionar contexto, pautas y referencias sobre cómo hacemos las cosas, pero la interfaz gráfica debería explicar por sí misma lo que hace.

Por supuesto, hay una limitación obvia a eso: en una aplicación de fotografía, los usuarios necesitan entender la fotografía y su lenguaje, lo que implica cosas como el rango dinámico, la gama de color, el mapeo tonal, etc. La interfaz gráfica debería ser autoexplicativa sobre cómo se supone que debe usarse, no eliminar la necesidad de aprender el oficio (qué debe hacerse y cómo).

El diseño es un proceso iterativo

Una aplicación es un mundo virtual en el que un pequeño cambio puede reordenar cómo se adapta el resto del ecosistema a su alrededor. Por lo tanto, cualquier cambio de diseño puede desencadenar la necesidad de cambiar otras cosas alrededor (refactorizar herramientas, mover widgets, podar funciones). Lo que a su vez podría desencadenar la necesidad de corregir de nuevo el cambio inicial. Es un proceso paso a paso en el que es insensato siquiera intentar hacerlo todo bien en cada paso; lo que importa es que cada paso mejore el entorno respecto del anterior.

A veces, el (re)diseño no puede hacerse con pequeños pasos sino con grandes saltos: es cuando rehaces la arquitectura. Estos saltos romperán muchas cosas a su alrededor, lo cual está bien si la nueva arquitectura es más simple y más robusta en conjunto, y si le das algo de tiempo para recuperarse antes de volver a coger el mazo. Pero eso creará un estado transitorio en el que el nuevo diseño parecerá peor que el anterior. Esto nos dice que cómo se percibe el diseño no es una entrada válida: la calidad del diseño tiene que evaluarse frente a sus objetivos y valorarse con métricas objetivas, no con sensaciones y pruebas rápidas.

Y a veces, algunos pasos son errores y deberían revertirse. La falacia del coste hundido  no debería usarse para justificar que un rediseño debería conservarse porque supuso mucho trabajo lograrlo. Es de esperar que no toda la investigación y el desarrollo llegue a producción.

Las sesiones de “golpea al topo” significan que tu arquitectura ha llegado a su límite

Ya sea que sigas creando nuevos errores mientras arreglas los viejos, o que sigas creando casos límite al extender alguna función, todo apunta en la misma dirección: tu arquitectura ya no puede doblarse más porque ha superado los requisitos de su diseño. Puede que el backend se haya vuelto demasiado enrevesado o puede que la arquitectura existente realmente no estuviera planeada para lo que intentas hacerle hacer, pero de ambas maneras, tendrás que rehacer la arquitectura y dejar de hacer parches sobre la marcha. De lo contrario, solo estás añadiendo deuda técnica.

Pero entonces, el coste de desarrollo cambia de escala y ese proyecto de tarde de sábado podría convertirse en un proyecto de un mes.

Las buenas prácticas son pautas, no reglas

Las buenas prácticas ayudan a desarrollar hábitos sanos y código limpio, a menos que no entiendas el problema que intentaban resolver y las uses fuera de su ámbito de validez. En ese caso, se convierten en culto cargo: intentar imitar los efectos con la esperanza de que arregle mágicamente también las causas.

La primera que viene a la mente es la reutilización/compartición de código. Si reutilizar el mismo código para funciones (aparentemente similares) conduce a demasiada ramificación interna (if anidados, switch/case, etc.), para manejar todas las rutas posibles, lo que ganas en volumen de código lo pierdes en complejidad ciclomática y, por cierto, tus funciones no son tan similares como pensabas.

Además, duplicar código puede ser un punto de partida para optimizar localmente el duplicado más adelante: una vez que tienes el procedimiento completo delante, puedes detectar pasos que pueden almacenarse en caché o factorizarse. Mientras que si el procedimiento solo son métodos de API opacos, de alto nivel y reutilizables, entonces pierdes la capacidad de detectar y eliminar cálculos redundantes. Así que hay un principio de reutilización/compartición de datos (es decir, almacenar en caché datos calculados que se usarán más tarde sin cambios, para ahorrar ciclos de CPU) que puede volverse imposible por la reutilización/compartición de código, porque ofusca y abstrae el ciclo de vida de los datos.

Esto se vuelve crítico en los bucles de píxeles: quieres colapsar todas las operaciones píxel a píxel en el mismo bucle, para pagar el precio de la E/S de memoria una sola vez. Lo que significa que tendrás que reimplementar la misma corrección afín ($y = a * x + b$) en cada bucle que la use, en lugar de tener un método reutilizable que haga justo eso en su propio bucle.

No te blindes

Si te encuentras abrumado por unos errores crípticos y aleatorios que no dejan de aparecer y a los que no consigues encontrar sentido, no sigas peleando a ciegas y da un paso atrás. Entonces instrumenta ayudantes de depuración, o gestores de más alto nivel que lleven registro de los estados internos y te den un mapa de los valores de datos del software en cualquier punto relevante de su ciclo de vida. Esto es especialmente crítico en configuraciones asíncronas, donde varios hilos crean, acceden o calculan cosas en paralelo en líneas de tiempo diferentes, y la secuencia de ordenación real depende del contexto en tiempo de ejecución.


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