¿Qué sucede cuando una banda de fotógrafos aficionados, convertidos en desarrolladores aficionados, se une a un grupo de desarrolladores de backend que desarrollan bibliotecas para desarrolladores y deciden trabajar sin método ni estructura en un software de la industria para usuarios finales, cuya competencia principal (colorimetría y psicofísica) se encuentra entre una licenciatura en fotografía y una maestría en ciencias aplicadas, mientras prometen entregar 2 versiones cada año sin gestión de proyectos? Todo eso, por supuesto, en un proyecto donde los fundadores y la primera generación de desarrolladores se movieron y huyeron?
¡Adivina!
Desmejorando características básicas
Los años 2020 están 40 años demasiado tarde para reinventar los paradigmas de interacción entre usuario y computadora, ya sea cómo usamos un teclado y un mouse para manejar la interfaz o el comportamiento de un navegador de archivos. Desde la década de 1980, todos los dispositivos informáticos de audiencia general han convergido hacia semánticas más o menos unificadas, donde la tecla escape cierra la aplicación actual, el doble clic abre archivos y la rueda del mouse desplaza la vista actual. Darktable1 toma un placer mal ubicado en ignorar todo eso y los cambios recientes empeoran las cosas: ahora es obligatorio leer la documentación para realizar tareas tan simples como ordenar archivos o asignar atajos de teclado a las acciones de la GUI.
Grupos de módulos
Todo comienza con la revisión de los grupos de módulos , en 2020, que oculta la decisión de no decidir un orden de módulos unificado.
Desde 2018 , lucho por limpiar la interfaz gráfica de Darktable, y en particular la organización de módulos. Una interfaz gráfica debe promover las mejores prácticas al organizar las herramientas en el orden típico en que deben usarse. Las malas prácticas son aquellas que aumentan el riesgo de inconsistencias colorimétricas o de edición circular, donde uno debe volver a aplicar cambios realizados después, aunque las malas prácticas puedan funcionar en casos simples. En el contexto del procesamiento de imágenes, una tarea altamente técnica donde muchas cosas están ocultas para los usuarios finales bajo la GUI, las mejores prácticas también permiten a personas poco calificadas utilizar el software de manera que se reduzca la probabilidad de errores.
Este orden de uso se dicta principalmente por consideraciones técnicas como el orden de aplicación de módulos en la secuencia de la línea de procesamiento y el uso de máscaras dibujadas y paramétricas, cuyo efecto depende de los módulos aguas arriba. Ignorar estas consideraciones es equivalente a buscar problemas, aunque la tendencia candente en los años 2010 y 2020 es creer que las tecnologías digitales funcionan desvinculadas de cualquier realidad material para la sola felicidad del usuario.
Por ejemplo, la restricción impuesta al diseño de la línea de procesamiento para permitir un orden arbitrario de uso del módulo crea problemas matemáticamente insolubles con respecto al cálculo de las coordenadas de los nodos de máscaras, y no hay una solución programada posible (aparte de relajar esa restricción) porque las matemáticas dijeron no.
Excepto que una parte significativa de los programadores-usuarios que giran en torno al proyecto en Github y en la lista de correo de desarrollo permanecen convencidos de que no hay flujos de trabajo buenos ni malos, solo preferencias personales, lo que probablemente sea cierto cuando se practica un arte sin restricción de tiempo, presupuesto o resultado. Como tal, los módulos deberían poder reordenarse a voluntad, ya sea en la línea de procesamiento o en el flujo de trabajo. La confusión proviene del hecho de que la edición no destructiva se ve erróneamente como asincrónica (lo que sería casi el caso si no usáramos máscaras ni modos de mezcla) mientras que la línea de píxeles es secuencial y más cercana a una lógica de capas, como la encontramos en Adobe Photoshop, Gimp, Krita, etc.
Desacoplar el orden de los módulos en la GUI del orden de la línea de procesamiento equivale a habilitar todos los usos, incluso los patológicos, y obliga a escribir páginas y páginas de documentación para advertir, explicar qué hacer, cómo y por qué; documentación que nadie leerá para terminar preguntándose en loop las mismas preguntas cada semana en todos los foros.
En esta historia, todos pierden su tiempo gracias a un diseño de interfaz que intenta ser tan flexible que no puede hacerse seguro y robusto por defecto. En el cuerpo humano, cada articulación tiene algunos grados de libertad a lo largo de ciertos ejes; si cada articulación pudiera girar 340° alrededor de cada eje del espacio 3D, la estructura sería inestable por ser demasiado flexible y incapaz de trabajar con cargas altas. La metáfora se sostiene en el software industrial. Nadamos en el culto al cargamento de FLOSS, donde la gente ama tener la ilusión de elección, es decir, se les ofrece muchas opciones de las cuales la mayoría son inutilizables o peligrosas, a expensas de la simplicidad (KISS ), y donde la mayoría de los usuarios no entienden las implicaciones de cada opción (y no tienen el menor deseo de entender).
A falta de un consenso sobre el orden de los módulos de la interfaz, se introdujo una herramienta complicada, frágil y pesada a finales de 2020 para permitir que cada usuario configure la disposición de los módulos en pestañas. Proporciona muchas opciones inútiles y almacena la disposición actual en la base de datos, utilizando el nombre traducido de los módulos, lo que significa que cambiar el idioma de la interfaz te hace perder tus configuraciones. Completamente configurable, permite al usuario decidir cómo lastimarse a sí mismo, sin ninguna guía de mejores prácticas. Esta basura está codificada con 4000 líneas mezclando alegremente solicitudes SQL en medio del código de interfaz GTK , y las configuraciones predefinidas se crean a través de macros del compilador redundantes , mientras que los módulos han tenido una bandera binaria para siempre, permitiendo establecer su grupo predeterminado… de manera modular.
Más importante aún, reemplaza una característica simple y eficiente, disponible hasta Darktable 3.2:
Un clic sobre el nombre del módulo lo habilita, un segundo lo agrega a la columna de favoritos, un tercero lo oculta de la interfaz. Todo permite preconfiguraciones y almacenar el diseño actual en texto simple en el archivo darktablerc
. Simple y robusto, codificado en más de 688 líneas de código legible y bien estructurado, la característica no fue lo suficientemente divertida para el desarrollador diletante de mediana edad y fue urgente reemplazarlo por un sistema laberíntico.
Atajos de teclado
En 2021 se añadió lo que llamo el gran MIDI turducken . El objetivo es extender la interfaz de atajos de teclado (ya ampliada en 2019 para soportar “atajos dinámicos”, permitiendo combinar acciones de mouse y teclado), para soportar dispositivos MIDI y… controladores de videojuegos.
A finales de 2022, eso es un año y medio después de esta función, en la encuesta que realicé , menos del 10% de los usuarios poseen un dispositivo MIDI, y solo el 2% lo usan con Darktable. Para comparar con el 45% de los usuarios que poseen una tableta gráfica (similar a Wacom), cuyo soporte en Darktable aún es tan defectuoso que solo el 6% lo usa. A pesar de la mala gestión de prioridades, lo que no tolero aquí son los efectos colaterales introducidos por este cambio y el costo global que tuvo, comenzando por el hecho de que no importa los atajos definidos por el usuario de versiones anteriores a la 3.2 y hace que la configuración de nuevos atajos sea terriblemente complicada.
Antes del gran turducken, solo una lista limitada de acciones GUI podía ser asignada a atajos de teclado o mixtos (teclado + mouse). Esta lista fue seleccionada manualmente por los desarrolladores. El gran MIDI turducken permite asignar todas las acciones GUI a atajos, presentando a los usuarios una lista de varios miles de entradas configurables, en la que es difícil encontrar las únicas 3 que realmente necesitas, y el motor de búsqueda de texto es demasiado básico para ser útil:
Note el uso de “efectos”, sobre los cuales la documentación no es de ayuda. Solo por deducción (porque el código no está comentado tampoco ) terminé entendiendo que son emulaciones de interacciones típicas de escritorio (ratón y teclado) destinadas a ser usadas con dispositivos MIDI y controladores de juego (pero aún necesitas explicarme qué significan Ctrl-Toggle
, Right-activate
o Right-Toggle
en términos de interacción típica de escritorio).
Lo inaceptable es que el uso del teclado numérico está roto por diseño , notablemente para atribuir calificaciones numeradas (estrellas) a las miniaturas en la tabla de luz. De hecho, los modificadores de teclas (NumLock y CapsLock) no son decodificados correctamente por la cosa, y los números se tratan de manera diferente si se ingresan desde el teclado típico de “texto” o desde el teclado numérico. Entonces, el 1
del teclado numérico se decodifica como Keypad End
, independientemente del estado del NumLock. Así es como tuve que configurar atajos numéricos en un teclado francés BÉPO y duplicar la configuración para el teclado numérico:
Solo tienes que recordar que Shift+"
y Kp End
ambos significan 1
y recordar duplicar todos los atajos para el teclado numérico y el resto del teclado. En resumen, rompemos una expectativa básica del usuario y enviamos las críticas de diseño al infierno. La regresión se menciona en todos los foros de Darktable pero parece no molestar a nadie.
La solución de este error esta característica se ha realizado en Ansel y las teclas del teclado numérico se reasignaron a teclas estándar directamente en el código , para un total de 100 líneas de código incluyendo comentarios. Hacer esta corrección ha sido muy difícil de hecho: leí la documentación de Gtk y tomé su ejemplo línea por línea. 2 años esperando eso…
La guinda del pastel es, una vez más, reemplazamos 1306 líneas de código claro y estructurado por una monstruosidad de cerca de 4400 líneas , con perlas como:
- el
while
loop de la muerte (fuente ):
1 gboolean applicable;
2 while((applicable =
3 (c->key_device == s->key_device && c->key == s->key && c->press >= (s->press & ~DT_SHORTCUT_LONG) &&
4 ((!c->move_device && !c->move) ||
5 (c->move_device == s->move_device && c->move == s->move)) &&
6 (!s->action || s->action->type != DT_ACTION_TYPE_FALLBACK ||
7 s->action->target == c->action->target))) &&
8 !g_sequence_iter_is_begin(*current) &&
9 (((c->button || c->click) && (c->button != s->button || c->click != s->click)) ||
10 (c->mods && c->mods != s->mods ) ||
11 (c->direction & ~s->direction ) ||
12 (c->element && s->element ) ||
13 (c->effect > 0 && s->effect > 0 ) ||
14 (c->instance && s->instance ) ||
15 (c->element && s->effect > 0 && def &&
16 def->elements[c->element].effects != def->elements[s->element].effects ) ))
17 {
18 *current = g_sequence_iter_prev(*current);
19 c = g_sequence_get(*current);
20 }
- El
switch
case
que contieneif
anidado en 2 niveles (fuente ):
1 switch(owner->type)
2 {
3 case DT_ACTION_TYPE_IOP:
4 vws = DT_VIEW_DARKROOM;
5 break;
6 case DT_ACTION_TYPE_VIEW:
7 {
8 dt_view_t *view = (dt_view_t *)owner;
9n vws = view->view(view);
10 }
11 break;
12 case DT_ACTION_TYPE_LIB:
13 {
14 dt_lib_module_t *lib = (dt_lib_module_t *)owner;
15n const gchar **views = lib->views(lib);
16 while(*views)
17 {
18 if (strcmp(*views, "lighttable") == 0)
19 vws |= DT_VIEW_LIGHTTABLE;
20 else if(strcmp(*views, "darkroom") == 0)
21 vws |= DT_VIEW_DARKROOM;
22 else if(strcmp(*views, "print") == 0)
23 vws |= DT_VIEW_PRINT;
24 else if(strcmp(*views, "slideshow") == 0)
25 vws |= DT_VIEW_SLIDESHOW;
26 else if(strcmp(*views, "map") == 0)
27 vws |= DT_VIEW_MAP;
28 else if(strcmp(*views, "tethering") == 0)
29 vws |= DT_VIEW_TETHERING;
30 else if(strcmp(*views, "*") == 0)
31 vws |= DT_VIEW_DARKROOM | DT_VIEW_LIGHTTABLE | DT_VIEW_TETHERING |
32 DT_VIEW_MAP | DT_VIEW_PRINT | DT_VIEW_SLIDESHOW;
33 views++;
34 }
35 }
36 break;
37 case DT_ACTION_TYPE_BLEND:
38 vws = DT_VIEW_DARKROOM;
39 break;
40 case DT_ACTION_TYPE_CATEGORY:
41 if(owner == &darktable.control->actions_fallbacks)
42 vws = 0;
43 else if(owner == &darktable.control->actions_lua)
44 vws = DT_VIEW_DARKROOM | DT_VIEW_LIGHTTABLE | DT_VIEW_TETHERING |
45 DT_VIEW_MAP | DT_VIEW_PRINT | DT_VIEW_SLIDESHOW;
46 else if(owner == &darktable.control->actions_thumb)
47 {
48 vws = DT_VIEW_DARKROOM | DT_VIEW_MAP | DT_VIEW_TETHERING | DT_VIEW_PRINT;
49 if(!strcmp(action->id,"rating") || !strcmp(action->id,"color label"))
50 vws |= DT_VIEW_LIGHTTABLE; // lighttable has copy/paste history shortcuts in separate lib
51 }
52 else
53 fprintf(stderr, "[find_views] views for category '%s' unknown
54, owner->id);
55 break;
56 case DT_ACTION_TYPE_GLOBAL:
57 vws = DT_VIEW_DARKROOM | DT_VIEW_LIGHTTABLE | DT_VIEW_TETHERING |
58 DT_VIEW_MAP | DT_VIEW_PRINT | DT_VIEW_SLIDESHOW;
59 break;
60 default:
61 break;
62 }
- El
switch
case
anidado del demonio, con cláusulas aditivas escondidas sutilmente (fuente ):
1case DT_ACTION_ELEMENT_ZOOM:
2 ;
3 switch(effect)
4 {
5 case DT_ACTION_EFFECT_POPUP:
6 dt_bauhaus_show_popup(widget);
7 break;
8 case DT_ACTION_EFFECT_RESET:
9 move_size = 0;
10 case DT_ACTION_EFFECT_DOWN:
11 move_size *= -1;
12 case DT_ACTION_EFFECT_UP:
13 _slider_zoom_range(bhw, move_size);
14 break;
15 case DT_ACTION_EFFECT_TOP:
16 case DT_ACTION_EFFECT_BOTTOM:
17 if((effect == DT_ACTION_EFFECT_TOP) ^ (d->factor < 0))
18 d->max = d->hard_max;
19 else
20 d->min = d->hard_min;
21 gtk_widget_queue_draw(widget);
22 break;
23 default:
24 fprintf(stderr, "[_action_process_slider] unknown shortcut effect (%d) for slider
25, effect);
26 break;
27 }
Los programadores entienden de qué estoy hablando; para los demás, solo sepan que no entiendo más que ustedes lo que hace esto: es un código de mierda, y si no hay varios errores ocultos allí, será pura suerte. Cazar errores en este agujero de mierda es arqueología del fondo de la cloaca, y más considerando que Darktable no tiene documentación de desarrollador y, en ausencia de comentarios significativos en el código, cualquier modificación del código mencionado anteriormente comenzará necesariamente con una fase de ingeniería inversa que se vuelve más difícil con el paso del tiempo.
El verdadero problema de este tipo de código es que no puedes mejorarlo sin reescribirlo más o menos por completo: para corregirlo, primero necesitas entenderlo, pero la razón por la cual necesita ser corregido es precisamente porque es incomprensible y peligroso a largo plazo. Llamamos a eso deuda técnica . En resumen, todo el trabajo invertido en esta función creará trabajo extra porque es irrazonable mantener ese tipo de código en medio de una base de código de varios cientos de miles de líneas y esperar que no explote en nuestra cara algún día.
Es aún más ridículo en el contexto de una aplicación de código abierto/libre donde la mayor parte del personal son programadores no entrenados. Los desarrolladores inteligentes escriben código comprensible para idiotas, y viceversa.
Filtros de colección
Hasta Darktable 3.8, los filtros de colección, en la parte superior de la tabla de luz, se usaban para restringir temporalmente la vista de una colección. La colección es una extracción de la base de datos de fotos basada en ciertos criterios, el más común siendo la extracción del contenido de una carpeta (que Darktable llama “rollo de película” para confundir a todos, porque un rollo de película es en realidad el contenido de una carpeta mostrado como una lista plana en lugar de un árbol - muchas personas erróneamente piensan que Darktable no tiene un gestor de archivos).
Habiendo sido usuario de Darktable durante más de una década, tengo una base de datos de más de 140.000 entradas. Extraer una colección entre estas 140.00 imágenes es una operación lenta. Pero mis carpetas rara vez contienen más de 300 imágenes. Filtrar, por ejemplo, las imágenes calificadas con 2 estrellas o más, en una colección de 300 archivos, es rápido porque es un subconjunto de 300 elementos. Y cambiar de un filtro a otro también es rápido. El filtro es solo una vista parcial o total de una colección, optimizada para un uso rápido y temporal de ir y venir.
Bajo el pretexto de refactorizar el código de filtrado, que totalizó en 550 líneas , el jefe Gaston Lagaffe hizo una vocación de romper este modelo para convertir los filtros de colección en colecciones básicas, mediante más de 6.000 líneas de código , sin contar los incontables parches de errores que solo sumaron más líneas2. Todo eso, como de costumbre, altamente configurable y redundante con el clásico módulo de colecciones , que permaneció allí, y servido por iconos tan crípticos que tuvieron que agregar descripciones de texto al pasar el mouse para aclarar lo que significan.
En este código de calidad, encontramos el interminable while
bajo el switch case
en el if
en el if
en el for
(fuente ):
1for(int k = 0; k < num_rules; k++)
2 {
3 const int n = sscanf(buf, "%d:%d:%d:%d:%399[^$]", &mode, &item, &off, &top, str);
4n if(n == 5)
5 {
6 if(k > 0)
7 {
8 c = g_strlcpy(out, "<i> ", outsize);
9 out += c;
10 outsize -= c;
11 switch(mode)
12 {
13 case DT_LIB_COLLECT_MODE_AND:
14 c = g_strlcpy(out, _("AND"), outsize);
15 out += c;
16 outsize -= c;
17 break;
18 case DT_LIB_COLLECT_MODE_OR:
19 c = g_strlcpy(out, _("OR"), outsize);
20 out += c;
21 outsize -= c;
22 break;
23 default: // case DT_LIB_COLLECT_MODE_AND_NOT:
24 c = g_strlcpy(out, _("BUT NOT"), outsize);
25 out += c;
26 outsize -= c;
27 break;
28 }
29 c = g_strlcpy(out, " </i>", outsize);
30 out += c;
31 outsize -= c;
32 }
33 int i = 0;
34 while(str[i] != '\0' && str[i] != '$') i++;
35 if(str[i] == '$') str[i] = '\0';
36n gchar *pretty = NULL;
37 if(item == DT_COLLECTION_PROP_COLORLABEL)
38 pretty = _colors_pretty_print(str);
39 else if(!g_strcmp0(str, "%"))
40 pretty = g_strdup(_("all"));
41 else
42 pretty = g_markup_escape_text(str, -1);
43n if(off)
44 {
45 c = snprintf(out, outsize, "<b>%s</b>%s %s",
46 item < DT_COLLECTION_PROP_LAST ? dt_collection_name(item) : "???", _(" (off)"), pretty);
47 }
48 else
49 {
50 c = snprintf(out, outsize, "<b>%s</b> %s",
51 item < DT_COLLECTION_PROP_LAST ? dt_collection_name(item) : "???", pretty);
52 }
53n g_free(pretty);
54 out += c;
55 outsize -= c;
56 }
57 while(buf[0] != '$' && buf[0] != '\0') buf++;
58 if(buf[0] == '$') buf++;
59 }
y otros if
anidado sobre 2 niveles dentro del switch case
necesarios para soportar los atajos de teclado (fuente ).
Esta última mierda fue la gota que colmó el vaso y me hizo bifurcar Ansel. Me niego a trabajar en una bomba de tiempo en un equipo que no ve el problema y juega con el código en su tiempo libre. Programar les puede divertir a ellos, no a mí. Y corregir la mierda hecha por niños irresponsables el doble de mi edad, especialmente cuando rompen cosas que limpié hace 3 o 4 años, me enfurece.
Tabla de luz
La tabla de luz tuvo 2 casi-reescrituras completas, la primera a principios de 2019 y la segunda a fines de 2019, lo que agregó muchas características discutibles como la vista de culling .
Rápidamente, el modo de culling se divide en 2 submodos: dinámico y estático, que manejan el número de imágenes de manera diferente. Muchos usuarios aún no han comprendido la diferencia 4 años después. Por lo tanto, tenemos la vista predeterminada (gestor de archivos), la tabla de luz ampliable (que nadie utiliza), el culling estático, el culling dinámico, y el modo de vista previa (una sola imagen en pantalla completa).
Luego, se agregan más opciones de visualización a las miniaturas en la tabla de luz, permitiendo definir superposiciones: superposiciones básicas permanentes, superposiciones EXIF permanentes extendidas, lo mismo pero solo al pasar el mouse, y finalmente las superposiciones temporizadas al pasar el ratón (con un temporizador configurable).
El código de interfaz de usuario que renderiza miniaturas y sus superposiciones debe tener en cuenta 5 vistas diferentes y 7 variantes de visualización , que son 35 combinaciones posibles. El código que asegura el tamaño adecuado de las miniaturas necesita por lo tanto un total de 220 líneas .
Pero no se detiene ahí, porque el código que renderiza la GUI de las miniaturas también se comparte con la barra inferior “filmstrip”, lo que en realidad hace 36 combinaciones posibles en el renderizado de miniaturas. Multiplicado por 3 temas de GUI de colores base diferentes, eso hace 108 conjuntos de instrucciones CSS para estilizar completamente la GUI… de los cuales muchos fueron olvidados en la renovación gráfica de Darktable 4.0 , y ¿cómo podría ser diferente?
En Darktable 2.6, teníamos 4193 líneas para el paquete, que solo tenía las vistas del gestor de archivos, la tabla de luz ampliable y las vistas de vista previa de pantalla completa, con solo 2 modos de superposiciones de miniaturas (siempre visibles o visibles al pasar el ratón):
- 2634 líneas views/lighttable.c para la tabla de luz y el renderizado de miniaturas,
- 1124 líneas en libs/tools/filmstrip.c para la barra inferior, que duplica parcialmente el código de la tabla de luz para el renderizado de miniaturas,
- 435 líneas en libs/tools/global_toolbox.c , para el menú de botones que permite habilitar o deshabilitar superposiciones de miniaturas.
Después de Darktable 3.0 y la adición de modos de culling, obtenemos 6731 líneas:
- 5149 líneas views/lighttable.c ,
- 1177 líneas libs/tools/filmstrip.c ,
- 405 líneas libs/tools/global_toolbox.c .
Después de Darktable 3.2 y la adición de las 7 variantes de superposiciones altamente configurables y algo de refactorización de código, obtenemos 8380 líneas:
- 1463 líneas en views/lighttable.c ,
- 1642 líneas en dtgtk/culling.c , donde se separaron las características de vista de culling,
- 2447 líneas en dtgtk/thumbtable.c , donde se gestionan los contenedores de miniaturas para la tabla de luz y la barra inferior,
- 1736 líneas en dtgtk/thumbnail.c , donde se gestionan las miniaturas mismas,
- 169 líneas en dtgtk/thumbnail_btn.c , donde se declaran los botones específicos de miniaturas,
- 115 líneas en libs/tools/filmstrip.c ,
- 808 líneas en libs/tools/global_toolbox.c .
En Darktable 4.2, después de la corrección de muchos errores, llegamos a un total de 9264 líneas:
- 1348 líneas en views/lighttable.c ,
- 1828 líneas en dtgtk/culling.c ,
- 2698 líneas en dtgtk/thumbtable.c ,
- 2093 líneas en dtgtk/thumbnail.c ,
- 166 líneas en dtgtk/thumbnail_btn.c ,
- 109 líneas en libs/tools/filmstrip.c ,
- 1022 líneas en libs/tools/global_toolbox.c .
El número de líneas (especialmente en el código que tiene un placer mal ubicado de ignorar las mejores prácticas de programación) es un indicador directo de la dificultad para depurar cualquier cosa allí, pero también un indicador indirecto (en el caso específico de código GUI) de la carga de CPU requerida para ejecutar el software.
De hecho, si inicias darktable -d sql
y pasas el mouse sobre una miniatura en la tabla de luz, obtendrás en la terminal:
1140.8252 [sql] darktable/src/common/image.c:311, function dt_image_film_roll(): prepare "SELECT folder FROM main.film_rolls WHERE id = ?1"
2140.8259 [sql] darktable/src/common/image.c:387, function dt_image_full_path(): prepare "SELECT folder || '/' || filename FROM main.images i, main.film_rolls f WHERE i.film_id = f.id and i.id = ?1"
3140.8271 [sql] darktable/src/common/metadata.c:487, function dt_metadata_get(): prepare "SELECT value FROM main.meta_data WHERE id = ?1 AND key = ?2 ORDER BY value"
4140.8273 [sql] darktable/src/common/metadata.c:487, function dt_metadata_get(): prepare "SELECT value FROM main.meta_data WHERE id = ?1 AND key = ?2 ORDER BY value"
5140.8275 [sql] darktable/src/common/metadata.c:487, function dt_metadata_get(): prepare "SELECT value FROM main.meta_data WHERE id = ?1 AND key = ?2 ORDER BY value"
6140.8277 [sql] darktable/src/common/metadata.c:487, function dt_metadata_get(): prepare "SELECT value FROM main.meta_data WHERE id = ?1 AND key = ?2 ORDER BY value"
7140.8279 [sql] darktable/src/common/metadata.c:487, function dt_metadata_get(): prepare "SELECT value FROM main.meta_data WHERE id = ?1 AND key = ?2 ORDER BY value"
8140.8280 [sql] darktable/src/common/metadata.c:487, function dt_metadata_get(): prepare "SELECT value FROM main.meta_data WHERE id = ?1 AND key = ?2 ORDER BY value"
9140.8282 [sql] darktable/src/common/metadata.c:487, function dt_metadata_get(): prepare "SELECT value FROM main.meta_data WHERE id = ?1 AND key = ?2 ORDER BY value"
10140.8284 [sql] darktable/src/common/tags.c:635, function dt_tag_get_attached(): prepare "SELECT DISTINCT I.tagid, T.name, T.flags, T.synonyms, COUNT(DISTINCT I.imgid) AS inb FROM main.tagged_images AS I JOIN data.tags AS T ON T.id = I.tagid WHERE I.imgid IN (104337) AND T.id NOT IN memory.darktable_tags GROUP BY I.tagid ORDER by T.name"
11140.8286 [sql] darktable/src/common/tags.c:635, function dt_tag_get_attached(): prepare "SELECT DISTINCT I.tagid, T.name, T.flags, T.synonyms, COUNT(DISTINCT I.imgid) AS inb FROM main.tagged_images AS I JOIN data.tags AS T ON T.id = I.tagid WHERE I.imgid IN (104337) AND T.id NOT IN memory.darktable_tags GROUP BY I.tagid ORDER by T.name"
12140.9512 [sql] darktable/src/common/act_on.c:156, function _cache_update(): prepare "SELECT imgid FROM main.selected_images WHERE imgid=104337"
13140.9547 [sql] darktable/src/common/act_on.c:156, function _cache_update(): prepare "SELECT imgid FROM main.selected_images WHERE imgid=104337"
14140.9550 [sql] darktable/src/common/act_on.c:288, function dt_act_on_get_query(): prepare "SELECT imgid FROM main.selected_images WHERE imgid =104337"
15140.9552 [sql] darktable/src/libs/metadata.c:263, function _update(): prepare "SELECT key, value, COUNT(id) AS ct FROM main.meta_data WHERE id IN (104337) GROUP BY key, value ORDER BY value"
16140.9555 [sql] darktable/src/common/collection.c:973, function dt_collection_get_selected_count(): prepare "SELECT COUNT(*) FROM main.selected_images"
17140.9556 [sql] darktable/src/libs/image.c:240, function _update(): prepare "SELECT COUNT(id) FROM main.images WHERE group_id = ?1 AND id != ?2"
18140.9558 [sql] darktable/src/common/tags.c:635, function dt_tag_get_attached(): prepare "SELECT DISTINCT I.tagid, T.name, T.flags, T.synonyms, COUNT(DISTINCT I.imgid) AS inb FROM main.tagged_images AS I JOIN data.tags AS T ON T.id = I.tagid WHERE I.imgid IN (104337) AND T.id NOT IN memory.darktable_tags GROUP BY I.tagid ORDER by T.name"
lo que significa que se realizan 18 solicitudes SQL contra la base de datos para obtener información de la imagen, y se ejecutan cada vez que el cursor pasa sobre una nueva miniatura, sin ninguna razón, ya que los metadatos no cambiaron desde el último desplazamiento.
En Ansel, eliminando la mayoría de las opciones, logré ahorrar 7 solicitudes, lo que aún no evita solicitudes duplicadas, pero aún mejora los tiempos en cierta medida (las marcas de tiempo son los números que comienzan cada línea):
112.614534 [sql] ansel/src/common/image.c:285, function dt_image_film_roll(): prepare "SELECT folder FROM main.film_rolls WHERE id = ?1"
212.615225 [sql] ansel/src/common/image.c:356, function dt_image_full_path(): prepare "SELECT folder || '/' || filename FROM main.images i, main.film_rolls f WHERE i.film_id = f.id and i.id = ?1"
312.616499 [sql] ansel/src/common/metadata.c:487, function dt_metadata_get(): prepare "SELECT value FROM main.meta_data WHERE id = ?1 AND key = ?2 ORDER BY value"
412.616636 [sql] ansel/src/common/metadata.c:487, function dt_metadata_get(): prepare "SELECT value FROM main.meta_data WHERE id = ?1 AND key = ?2 ORDER BY value"
512.616769 [sql] ansel/src/common/metadata.c:487, function dt_metadata_get(): prepare "SELECT value FROM main.meta_data WHERE id = ?1 AND key = ?2 ORDER BY value"
612.616853 [sql] ansel/src/common/metadata.c:487, function dt_metadata_get(): prepare "SELECT value FROM main.meta_data WHERE id = ?1 AND key = ?2 ORDER BY value"
712.616930 [sql] ansel/src/common/metadata.c:487, function dt_metadata_get(): prepare "SELECT value FROM main.meta_data WHERE id = ?1 AND key = ?2 ORDER BY value"
812.617007 [sql] ansel/src/common/metadata.c:487, function dt_metadata_get(): prepare "SELECT value FROM main.meta_data WHERE id = ?1 AND key = ?2 ORDER BY value"
912.617084 [sql] ansel/src/common/metadata.c:487, function dt_metadata_get(): prepare "SELECT value FROM main.meta_data WHERE id = ?1 AND key = ?2 ORDER BY value"
1012.617205 [sql] ansel/src/common/tags.c:635, function dt_tag_get_attached(): prepare "SELECT DISTINCT I.tagid, T.name, T.flags, T.synonyms, COUNT(DISTINCT I.imgid) AS inb FROM main.tagged_images AS I JOIN data.tags AS T ON T.id = I.tagid WHERE I.imgid IN (133727) AND T.id NOT IN memory.darktable_tags GROUP BY I.tagid ORDER by T.name"
1112.617565 [sql] ansel/src/common/tags.c:635, function dt_tag_get_attached(): prepare "SELECT DISTINCT I.tagid, T.name, T.flags, T.synonyms, COUNT(DISTINCT I.imgid) AS inb FROM main.tagged_images AS I JOIN data.tags AS T ON T.id = I.tagid WHERE I.imgid IN (133727) AND T.id NOT IN memory.darktable_tags GROUP BY I.tagid ORDER by T.name"
El problema es que el código fuente anida comandos SQL dentro de funciones que dibujan la GUI, y desenredar este lío a través de las diferentes capas heredadas del “refactorización” (supuestamente para simplificar el código, pero en realidad no) es nuevamente arqueología. Y si el problema se hubiera solucionado cuando el código tenía 6700 líneas en 3 archivos, no estaríamos buscando, 4 años después, las causas en 2500 líneas adicionales ahora distribuidas en 7 archivos diferentes (sin contar los archivos .h
).
Estamos en el caso ejemplar donde la ‘refactorización’ en realidad complicó el código y donde fusionar el código de las miniaturas entre filmstrip y lighttable solo agregó más if
internos (ramificaciones) anidado en varios niveles, lo que aún complica más la estructura, solo para seguir ciegamente el principio de reutilización de código , que aquí entra en conflicto con el principio de modularidad , que un desarrollador hábil habría solucionado con [herencia](https://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming) , porque incluso si no es fácil de hacer en C, es perfectamente posible (de hecho, Darktable usa este principio en el código de 2009-2010).
Los cosméticos toman el control sobre la estabilidad.
Darktable 4.2 introduce la vista previa de estilos en el cuarto oscuro. Eso sería genial si los estilos no estuvieran profundamente rotos, cuando se utilizan con un orden de tubería no predeterminado y múltiples instancias de módulos. El problema es que una solución limpia y a largo plazo implica la teoría de grafos dirigidos , y ahí es donde perdimos a nuestros queridos copiadores y pegadores de código.
En el mismo espíritu, tenemos grandes inconsistencias en el copiado y pegado del historial en modo sobrescribir cuando también se utilizan preajustes de usuario predeterminados (especialmente en el módulo de balance de blancos). Pero es mucho más divertido estropear la interfaz, así que permanecerá así por mucho tiempo.
Darktable 3.6 y 3.8 introdujeron muchas variantes del histograma: vectorscopio, forma de onda vertical, espacios de color avanzados y exóticos. Excepto que si lanzas darktable -d perf
en el terminal y abres el cuarto oscuro, verás mucho de
cada vez que mueves el cursor en la ventana (y ni siquiera sobre el histograma). Es el histograma el que se redibuja en cada interacción entre el cursor y la ventana. El mismo problema afecta a muchos widgets gráficos personalizados y su causa no está identificada. Nota que no afecta a Ansel, por lo que la causa debería estar oculta en algún lugar de las 23,000 líneas de código que eliminé.
Dos veces intenté refactorizar el espectáculo triste en que se convirtió esta funcionalidad , pero cada vez se impulsaba una funcionalidad nueva más urgente que invalidaba mi trabajo. Simplemente me rendí.
El estado de descomposición del histograma es tal que una reescritura completa tomaría menos tiempo que una refactorización, especialmente porque el histograma se muestrea demasiado tarde en la tubería, en el espacio de color de pantalla, lo que hace que la definición de un espacio de color de histograma sea nula dado que la gama se recorta en el espacio de color de pantalla sin importar qué. Pero adivina qué… Darktable 4.4 tendrá aún más opciones, con la capacidad de definir armonías de color (fundamentales para los geeks que pintan por números y editan histogramas).
Sin embargo, cada vez que mueves el cursor, comienza una gran cantidad de re-computaciones inútiles por nada. ¿Qué tan malo es? Tuve la idea de medir el uso de la CPU de mi sistema cuando está inactivo, con la herramienta de Linux powertop
. El protocolo es bastante simple: una laptop (CPU Intel Xeon Mobile de 6ª generación), funcionando con batería en modo de ahorro de energía, retroiluminación configurada al mínimo, abrir la aplicación y no tocar nada por 4 minutos, luego monitorear el consumo global de la CPU del sistema según lo reportado por powertop
durante el quinto minuto:
- Sistema base (sin aplicaciones abiertas excepto
powertop
ejecutándose en una terminal): 3.0 a 3,5 % CPU - Ansel:
- abierto en la mesa de luz: 2,9 a 3,4 % CPU,
- abierto en el cuarto oscuro: 3,8 a 4,5 % (antes de revertir grupos de módulos a Darktable 3.2),
- abierto en el cuarto oscuro: 3,0 a 3,5 % (después de revertir grupos de módulos),
- Darktable:
- abierto en la mesa de luz: 6,6 a 7,1 % CPU,
- abierto en el cuarto oscuro: 30,9 a 44,9 % CPU (no, no es un error de coma),
No entiendo qué calcula Darktable cuando lo dejamos abierto sin tocar la computadora, porque no hay nada que calcular. Darktable en la mesa de luz consume por sí solo tanto como todo el sistema (Fedora 37 + escritorio KDE + administrador de contraseñas y cliente de Nextcloud ejecutándose en segundo plano), y consume 10 veces más que todo el sistema cuando está abierto en el cuarto oscuro.
Todo esto apunta hacia un código de interfaz gráfica muy defectuoso. En Ansel, eliminé gran parte del código sucio, sin optimizar nada más, y estas cifras solo validan mi elección: el código sucio oculta problemas indetectables al leerlo, y simplemente no podemos continuar por este camino.
Aparentemente, soy el único que piensa que es inaceptable privar a la tubería de píxeles de un tercio a la mitad de la potencia de la CPU para pintar una interfaz estúpida. Como se mire, no hay razón válida para que un software dejado abierto sin tocarlo convierta la computadora en una tostadora, especialmente desde que ya no compramos gas ruso.
Trabajando contra nosotros mismos
Somos fotógrafos. El hecho de que necesitemos una computadora para hacer fotografía es una novedad (de 20 años), vinculada a la tecnología de imagen digital que reemplazó por todo tipo de razones (buenas y malas) una tecnología de 160 años, conocida y dominada. En el proceso, el hecho de que necesitemos una computadora y un software para producir imágenes es pura y simple sobrecarga. Forzar a personas que no entienden computadoras a usarlas para realizar tareas que podían manejar perfectamente antes, también es una forma de opresión, y ocultarlo como algún avance técnico es una forma de violencia psicológica.
El software implica desarrollo, mantenimiento, documentación y gestión de proyectos. Son varias capas de sobrecarga encima del anterior. Sin embargo, el hecho de que la mano de obra en proyectos de código abierto no pida compensación no debería obstaculizar el hecho de que el tiempo invertido (¿perdido?) en el software, su uso, su desarrollo, su mantenimiento, es en sí mismo un costo no reembolsable.
Los pocos ejemplos anteriores dan una visión general de la complejidad del código fuente, pero también de su degradación con el tiempo en términos de calidad, porque las características básicas y robustas son reemplazadas por código espagueti, confuso y astutamente con errores. Detrás de este problema de legibilidad, el verdadero problema está en hacer que la mantenibilidad a medio plazo sea más difícil, lo que promete un futuro sombrío para el proyecto, con la aprobación del mantenedor.
Desde hace 4 años que trabajo a tiempo completo en Darktable, 2022 es el primer año que me encuentro prácticamente incapaz de identificar la causa de la mayoría de los errores en la interfaz, porque la lógica de trabajo se ha vuelto muy ofuscada y el código incomprensible. El número de errores corregidos también está en disminución constante, tanto en valor absoluto como en proporción de las solicitudes de extracción fusionadas, mientras que el volumen de tráfico de código permanece aproximadamente constante (nota 1: los siguientes conteos de líneas de código solo incluyen archivos C/C++/OpenCL y archivos generativos XML y excluyen los comentarios 3) (nota 2: el número de problemas abiertos se cuenta durante la vida útil de la versión anterior):
- 3.0 (diciembre 2019, un año después de 2.6)
- 1049 problemas abiertos , 66 problemas cerrados / 553 solicitudes de extracción fusionadas (12 %),
- 398 archivos cambiados, 66 k inserciones, 22 k eliminaciones, (neto: +44 k líneas),
- 3.2 (agosto 2020)
- 1028 problemas abiertos , 92 problemas cerrados / 790 solicitudes de extracción fusionadas (12 %),
- 586 archivos cambiados, 54 k inserciones, 43 k eliminaciones (neto: +2 k líneas),
- 3.4 (diciembre 2020)
- 981 problemas abiertos , 116 problemas cerrados / 700 solicitudes de extracción fusionadas (17 %),
- 339 archivos cambiados, 46 k inserciones, 23 k eliminaciones (neto: +23 k líneas),
- 3.6 (junio 2021)
- 759 problemas abiertos , 290 problemas cerrados / 954 solicitudes de extracción fusionadas (30 %),
- 433 archivos cambiados, 53 k inserciones, 28 k eliminaciones (neto: +25 k líneas),
- 3.8 (diciembre 2021)
- 789 problemas abiertos , 265 problemas cerrados / 571 solicitudes de extracción fusionadas (46 %),
- 438 archivos cambiados, 41 k inserciones, 21 k eliminaciones (neto: +20 k líneas),
- 4.0 (junio 2022)
- 632 problemas abiertos , 123 problemas cerrados / 586 solicitudes de extracción fusionadas (21 %),
- 359 archivos cambiados, 30 k inserciones, 15 k eliminaciones (neto: +15 k líneas),
- 4.2 (diciembre 2022)
- 595 problemas abiertos , 60 problemas cerrados / 409 solicitudes de extracción fusionadas (15 %),
- 336 archivos cambiados, 14 k inserciones, 25 k eliminaciones (neto: -11 k líneas),
- (las eliminaciones se deben principalmente a la eliminación de la ruta SSE2 en el código de píxeles, penalizando el rendimiento de las CPU Intel i5/i7 típicas para el beneficio de las CPUs AMD Threadripper),
- 4.4 (junio 2023)
- 500 problemas abiertos , 97 problemas cerrados / 813 solicitudes de extracción fusionadas (12 %),
- 479 archivos cambiados, 57 k inserciones, 41 k eliminaciones (neto: +16 k líneas),
Para hacer las cosas más fáciles de comparar, vamos a anualizarlas:
- 2019: 1049 problemas nuevos, 66 cerrados, 88 k cambios, +44 k líneas,
- 2020: 2009 problemas nuevos, 208 cerrados, 166 k cambios, +25 k líneas,
- 2021: 1548 problemas nuevos, 555 cerrados, 143 k cambios, +45 k líneas,
- 2022: 1227 problemas nuevos, 183 cerrados, 84 k cambios, +4 k líneas.
Parece que no soy el único que encuentra que los errores de 2022 son mucho más difíciles de abordar, ya que muchos menos de ellos fueron corregidos comparados con 2021, y 2023 muestra la misma tendencia hasta ahora. Las proporciones de solicitudes de extracción (trabajo real realizado) frente a problemas cerrados (problemas reales resueltos) es simplemente ridícula.
Entre Darktable 3.0 y 4.0, el código de la interfaz creció un 53%, de 49 k a 75 k líneas4 (descartando comentarios y líneas en blanco), y alcanzó las 79 k líneas en 4.4. Dejando de lado la pobre calidad, realmente no estoy seguro de que haya mejorado la usabilidad del software en un 53 %. De hecho, estoy bastante convencido de lo contrario. En Ansel, hasta ahora he reducido el código de la interfaz a 53 k líneas mientras eliminaba poca funcionalidad.
Todo esto es simplemente demasiado rápido para un grupo de aficionados que trabaja por las noches y los fines de semana sin estructura y planificación. El equipo de Darktable trabaja contra sí mismo al intentar morder más de lo que puede masticar, soportando demasiadas opciones diferentes, produciendo código cuyo resultado depende de demasiadas variables de entorno, siendo capaz de interactuar de demasiadas maneras diferentes. Todo esto para evitar tomar decisiones de diseño que podrían ofender a algunos al limitar las características y opciones disponibles. En el lado del usuario final, esto se traduce en errores contextuales imposibles de reproducir en otros sistemas, por lo que son imposibles de corregir en absoluto.
Es simple: el trabajo realizado cuesta más y más trabajo, y el mantenimiento no está asegurado, como muestra el declive de los problemas cerrados, porque simplemente es demasiado. En una empresa, este es el momento en que necesitas detener la hemorragia antes de vaciar las arcas. Pero un equipo de amateurs obligado a no entregar resultados puede sostener una cantidad infinita de pérdidas. Solo queda que el trabajo creado por el trabajo es más tedioso, frustrante y difícil a medida que pasa el tiempo, y los usuarios finales son tomados como rehenes por un grupo de imbéciles egoístas y lo pagarán en términos de complejidad de la interfaz, carga innecesaria de la CPU, y necesidad de reaprender cómo lograr tareas básicas con el software al menos una vez al año.
De hecho, estoy esperando que el actual equipo de destrucción masiva convenga en tener cada vez menos tiempo libre para contribuir al proyecto a medida que se dan cuenta de que se atraparon en un camino sin retorno con un tráiler, dejando su porquería a los próximos. Pero cuanto antes se rindan, menos daño causarán.
La decadencia de opciones y preferencias, que es la estrategia de go-to de Darktable para (no) gestionar desacuerdos de diseño, crea casos de uso súper contextuales donde ningún usuario tiene las mismas opciones habilitadas y donde es imposible reproducir errores en un entorno diferente. Y pedir a los usuarios que adjunten el archivo de configuración darktablerc
a los informes de errores tampoco ayudaría ya que ese archivo tiene actualmente 1287 líneas prácticamente ilegibles.
Bugs extraños y difíciles de reproducir se acumulan, incluso en computadoras System 76 diseñadas específicamente para Linux, donde no podemos invocar problemas de controladores. Muchos errores inconsistentes y aleatorios que he presenciado mientras daba lecciones de edición no están listados en el rastreador de errores, y es bastante claro que se encuentran en algún lugar de las complejidades de la debauchery if
y switch case
que se están agregando a un ritmo alarmante desde 2020.
La corrección de estos errores extraños y contextuales solo puede hacerse simplificando el flujo de control del programa y, por lo tanto, limitando el número de parámetros del usuario. Pero el grupo de geeks agitando sus brazos sobre el proyecto no quiere oír hablar de esto y, peor aún, las “correcciones” de errores generalmente solo agregan más líneas para manejar casos patológicos individualmente.
De hecho, Darktable sufre varios problemas:
- Un núcleo duro de desarrolladores bastante mediocres que tienen mucho tiempo libre para hacer cosas aleatorias, impulsados por las mejores intenciones del mundo pero ajenos a los daños que hacen, (las personas mediocres siempre son las más disponibles)
- La complacencia del mantenedor, que deja pasar código sucio para ser agradable,
- Una falta crítica de habilidades en matemáticas puras, algorítmica, procesamiento de señales, ciencia del color y generalmente en el pensamiento abstracto, que se requieren más allá del código de procesamiento de píxeles para simplificar y factorizar características,
- Un hábito despreciable de “desarrollar” copiando y pegando código obtenido en otros lugares del proyecto o en otros proyectos FLOSS que pueden haber utilizado una arquitectura de tubería diferente pero sin adaptarlo en consecuencia (adaptarse implica entender, y eso es demasiado pedir…),
- Una negación justa y directa de podar características para dar paso a nuevas y mantener un cierto equilibrio,
- Un sesgo de muestreo, donde el único público que interactúa con el desarrollo a través de Github son programadores y angloparlantes. El hecho es que el público general no comprende qué es una forja de código y es difícil alentar a personas no programadoras a abrir una cuenta de Github para reportar errores. Estamos hablando de un muestreo de usuarios compuesto por más de un 44% de programadores y más de un 35% de personas con grado universitario (son respectivamente 6% y 15% en la población general).
- Un estilo de desarrollo a marchas forzadas, sin planificación o diálogo, donde cada usuario de Github puede contaminar debates con una opinión no informada sobre el trabajo actual. El hecho es que el procesamiento de imágenes parece fácil e inofensivo, tanto que cualquier persona capaz de calcular un logaritmo se siente competente. Pero los errores fundamentales en la cadena de colorimetría de Darkable están ahí para recordarnos todos los días de lo contrario.
- Falta de prioridades de proyecto en cuanto a qué funciones refactorizar, estabilizar o extender: todos los proyectos están abiertos al mismo tiempo, incluso si entran en conflicto entre sí.
- Una cantidad de actividad (correos electrónicos y notificaciones) imposible de seguir, entre comentarios, discusiones fuera de tema, errores que no lo son, propuestas de cambio de código, cambios de código que pueden impactar tu propio trabajo en curso, lo que significa que tienes que estar en todas partes todo el tiempo; hay mucho que leer, muy poco que mantener, las discusiones por el simple hecho de discutir obstaculizan la productividad y la falta de estructura de trabajo es la principal causa de todo eso,
- Errores no bloqueantes ocultos apresuradamente antes de que terminemos de entenderlos, en lugar de corregirlos realmente y abordarlos en su raíz, lo que mueve o incluso agrava los problemas a largo plazo sin dejar rastros en ningún tipo de documentación,
- Un calendario de lanzamientos que seguimos a cualquier precio, incluso cuando no es realista, mientras que nadie nos lo impone,
- Cambios de código que pueden suceder en cualquier momento y lugar, lo que significa que trabajamos sobre arenas movedizas y que tenemos que trabajar tan rápido y tan mal como los demás para no quedar atrás del volumen y frecuencia de los cambios (commits),
- Nuevas características que degradan la usabilidad y complican el uso sin resolver un problema definido, que sirve como proyectos recreativos para desarrolladores no entrenados en diseño/ingeniería.
Pero lo más exasperante es esta obstinación por reemplazar características simples y funcionales con horrores de sobreingeniería destinados a complacer usos desviados y marginales mientras complican la vida de todos con listas interminables de opciones sin reflexión. El mejor lugar para esconder un árbol es en medio del bosque, y muchos aún no lo han aprendido.
Confundir agitación con actividad
A cualquier elector le gusta criticar la desviación que, en política, consiste en emitir leyes circunstanciales, mal redactadas, para calmar la opinión pública después de un evento especial, para mostrar que actuamos, mientras que existen leyes similares pero no se aplican o no se aplican completamente por falta de medios. Llamamos a eso agitación: esto parece acción, suena a acción, tiene el costo de la acción, pero no lleva a nada tangible o práctico.
El equipo de aficionados sin gestión de proyectos que se agita sobre Darktable solo produce problemas futuros. En el pasado, Darktable se lanzaba una vez al año con alrededor de 1500 a 2000 commits más que la versión anterior. Ese es ahora el volumen de cambio logrado en 6 meses. Un volumen de “trabajo” que aumenta tan rápido sin conducir a métodos de trabajo en equipo, que incluyan prioridades claras para cada lanzamiento y distribución de tareas, y sin control de calidad del software basado en métricas objetivas (número de pasos o tiempo transcurrido para lograr una tarea en particular), solo son personas pisando los talones de los demás mientras empujan su propia agenda sin preocuparse por los demás, el proyecto o los usuarios.
Darktable se ha convertido en el club de informática del instituto, donde los geeks se divierten. En conjunto es un resumen de todas las peores historias de las empresas de TI, con la diferencia de que el proyecto no hace un centavo, lo que hace urgente preguntarnos por qué nos imponemos eso: no hay beneficios para compartir, pero todos comparten los costos. Es un ambiente de trabajo caótico y tóxico que solo fabricaría agotamiento si los aficionados a tiempo parcial estuvieran obligados a entregar resultados y tuvieran que trabajar a tiempo completo. Siendo el único chico a tiempo completo en ello, imagínate la cantidad de estrés y energía perdida para mantenerse al día con la cacofonía permanente, solo para estar seguro de no perder el 2 % realmente relevante para mí en la cantidad de ruido producido por discusiones no reguladas.
En el lado del usuario, alabamos la efervescencia del proyecto Darktable (sí, hay movimiento), sin darnos cuenta de que la carrera de commits no es actividad sino agitación, y en particular deuda técnica que tendrá que ser pagada no sabemos cuándo ni por quién. La belleza de un proyecto donde nadie tiene que asumir responsabilidad por su basura porque nadie es responsable de nada: está escrito en la licencia GNU/GPL. Por lo tanto, podemos arruinar el trabajo de los anteriores con total impunidad.
Tenemos el comienzo de un control de calidad, a través de pruebas de integración, que miden el error perceptual sobre procesamientos de imágenes de referencia, pero no provocan ninguna respuesta cuando vemos un error delta E promedio de 1.3 (pequeño) cuando la naturaleza del cambio debería tener un delta E estrictamente cero. Si la única prueba pasa (porque probamos una sola imagen SDR sobre una toma de estudio), no se pregunta si la teoría es sólida y robusta. Convertimos la prueba en una descarga, siempre y cuando la métrica se mantenga debajo del umbral de validación…
Con el lanzamiento de Darktable 4.0 —Geektabla—, vi en YouTube personas comenzando a quejarse de que este lanzamiento no era muy emocionante. Después de años de dosificación de personas con lanzamientos superlativos, llenos de nuevas características que no tenemos tiempo de probar adecuadamente (6-8 personas que orinan 38-50 k líneas cada 6 meses trabajando solo por las noches y los fines de semana, ¿todavía sueñas?), los hicimos adictos al Árbol de Navidad sobrecargado para asegurar que, el día que comencemos a ser responsables y a lanzar versiones estables (por lo tanto aburridas), eso se nos reprochará.
La razón de este ritmo frenético de lanzamientos es que las solicitudes de extracción de más de 3 meses están en conflicto con la rama principal, dado que esta es agitada cada mes para “pruebas generalizadas”. Pero los usuarios raros que construyen la rama principal no tienen idea de qué necesitan probar en particular, a menos que diseccionen el historial de commits de Git, lo que implica comprender tanto el lenguaje C como el impacto de los cambios en la práctica sobre el software.
Para limitar las ramas de larga duración, que inevitablemente terminarán en conflicto con el maestro, encontramos una solución brillante: lanzamos 2 versiones cada año, haciendo del desarrollo a marchas forzadas basado en código inacabado y apenas probado un modo de vida, sin darnos cuenta de que el problema principal es primero la falta de planificación, pero también que los colaboradores comienzan a codificar antes de haber definido el problema a resolver (cuando hay un problema real que resolver, no solo un tipo que se despertó como “sería genial si…”), trabajando en paralelo en ambas partes sin comunicación.
El polvo no tiene tiempo para asentarse que ya estamos sacudiendo la base de código de nuevo, sin imponer fases de estabilización donde solo limpiemos errores (y no estoy hablando del mes de congelación de características anterior al lanzamiento, sino de lanzamientos dedicados solo a la limpieza de código). El rastreador de errores implode en las 3 semanas siguientes a cada lanzamiento, porque una parte significativa de usuarios solo usa los paquetes preconstruidos, que coinciden con las vacaciones de Navidad y verano, donde personalmente tengo mejores cosas que hacer después del ya estresante sprint que es el mes anterior al lanzamiento.
Desde 2021, cuando actualizo mi repositorio de Git con los últimos cambios del maestro de Darktable, siempre me pregunto qué rompieron esta vez. Rompemos más rápido de lo que lo arreglamos, y la mayoría de las veces, las correcciones rompen algo más. Los únicos usuarios que encuentran estable a Darktable son realmente los que hacen un uso muy básico de él, lo cual es irónico para una aplicación cuyo punto de venta es ser avanzada.
Y luego, me presentan el hecho de que es trabajo gratuito como si fuera una excusa. Pero en realidad es una circunstancia agravante: ¿por qué nos imponemos tales condiciones de trabajo si ni siquiera es rentable??? Además del hecho de que este trabajo gratuito me da un agotamiento post-lanzamiento por año, cuesta cada vez más en mantenimiento y el mantenimiento es cada vez más despreciable de hacer. No es trabajo gratuito, es peor: es un trabajo que cuesta sin pagar.
De todos modos, el trabajo es proporcionado por personas que tienen tiempo y energía limitados. Si el recurso es limitado, corta la basura: estamos en las mismas restricciones de rentabilidad que un negocio, menos las contribuciones sociales, excepto que nuestra moneda de intercambio es el tiempo y no es reembolsable. Sin gestión de prioridades, nos veremos superados por una deuda técnica que no tendremos el recurso para mantener.
¿Qué estamos esperando para ser felices?
El hecho de que Darktable sea una aplanadora en modo de fuga y sin conductor, que genera una cantidad creciente de trabajo, es un mal olor para un proyecto de 15 años. Normalmente, un proyecto maduro se ralentiza porque está lo suficientemente completo como para ser utilizable y porque las personas que trabajan en él encontraron su velocidad de crucero y métodos de trabajo eficientes.
Estoy a tiempo completo en ello desde 2018, por un ingreso mensual entre 800 y 900 €, y es un eufemismo decir que está mal pagado para soportar las consecuencias desastrosas de aficionados desorganizados que intentan divertirse a expensas de la calidad del producto final y su usabilidad por muggles de computadoras. Además, los muggles también son despreciados, como principio.
Si me arrastro bajo una roca por un mes para desarrollar un espacio de color perceptual, cuando salgo, es para descubrir el nuevo sistema laberíntico violando un poco más el paradigma del modelo-vista-controlador y ser informado de que llegué demasiado tarde para oponerme. Si tomo 3 semanas de vacaciones en agosto, es para descubrir que el mantenedor eludió (una vez más) mi revisión sobre un cambio matemático sobre el mencionado espacio de color, que requiere sentarse tranquilamente y pensar, todo esto porque… ¿necesitábamos movernos rápido? ¿Para qué emergencia, exactamente?
Probablemente recibí la notificación en algún lugar entre los 2234 correos electrónicos que Github me envió entre enero y agosto de 2022 (en 2021, fueron 4044), sin mencionar los usuarios que me pingen por todas partes, en Youtube, Reddit, Matrix, Github, Telegram, directamente por correos electrónicos, y anteriormente en pixls.us (693 correos electrónicos en 2022, 948 en 2021). Todo esto para personas completamente fuera de lugar que no se dan cuenta de que hago esto toda la semana, que la fotografía puede ser su hobby pero es mi trabajo, y que solo apreciaría que la gente me dejara en paz durante los fines de semana y las vacaciones. Puedes adivinar que los más molestos no son los que apoyan financieramente mi trabajo. La gente respeta el trabajo solo si les cobran un precio alto por él.
No tengo tiempo para ser investigador, diseñador, además de secretario, mientras hago el cuidado técnico para un equipo de Gaston Lagaffe que necesita tanto ser entrenado como ser vigilado porque son incapaces de:
1. hacer una planificación de desarrollo con una lista de prioridades de nuevas características para trabajar, 2. proporcionar un libro de especificaciones de necesidades y problemas, con un caso de uso real, antes de apresurarse en su editor de código y hacer lo que sea para desarrollar una nueva característica en busca de un problema por resolver. 3. evaluar el costo de mantenimiento del cambio antes de inventar la bomba de tocino biónico que utiliza ósmosis inversa y que solo funciona en días pares si Júpiter está fuera de fase con Saturno, 4. limitar los gastos y reducir las pérdidas cuando se atrapan en un diseño de un solo sentido que introduce regresiones peores que los beneficios hipotéticos esperados, 5. asumir y retrasar un lanzamiento si el código obviamente no está listo (¿o no entendí nada y los accionistas pedirán nuestras cabezas si lanzamos tarde???).
La gestión (o gestión del equipo) es una sobrecarga que cuesta algo de trabajo, pero el equipo de Darktable ha alcanzado una escala donde la falta de gestión cuesta de hecho más trabajo, especialmente desde que ninguno de los fundadores del proyecto sigue en el equipo y los planos de diseño iniciales necesitan ser reingenieriados con grep
en el código cada vez que algo necesita cambiar. Eso era sostenible con un equipo reducido donde todos se conocían, pero Darktable se ha convertido en un proyecto de alto tráfico durante los confinamientos de Covid y esta forma de trabajar no es sostenible con el personal actual.
¡Ve a ver el código! Compara la rama darktable-2.6.x
with darktable-4.2.x
, ¡archivo por archivo, y disfruta!
All I have heard so far are canned sentences like “it’s like many other opensource projects” and “there is nothing we can do about it”. People are afraid by the amount of work that a fork is (I got emails trying to convince me it was dividing productivity), without realizing the amount of resources currently wasted by the Darktable project and the permanent stress of having to base your work on an unstable code base shaken up all the time. So far, Ansel cost me less fatigue and I solved a significant number of problems among which some were reported since 2016 without signs of interest from the bloody “community”.
Además, Ansel proporciona paquetes nocturnos compilados automáticamente para Linux (.AppImage) y Windows (.exe), para permitir pruebas reales generalizadas, incluso por personas incapaces de compilar el software por sí mismas. Lo pedí en 2019 , pero al parecer, los geeks tienen mejores cosas que hacer, y tuve que invertir 70 h yo mismo para hacer que sucediera. La operación ya es un éxito y permitió corregir en cuestión de días errores de Windows que habrían tomado semanas para detectar en Darktable. (Y Darktable tomó 3 semanas después mi script de compilación de AppImage sin dar créditos adecuados, pero eso es un detalle).
Hablando de productividad, recordemos que la mesa de luz se reescribió casi por completo dos veces desde 2018 (y la última versión no es mejor ni más rápida) y el gran cambio si los filtros de colección introducidos en abril de 2022 sobrescribieron otro cambio similar (pero solo usando 600 líneas en lugar de 6000) introducido en febrero de 2022 (la versión de febrero es la que está en Ansel). No podemos decente exhibir la palabra “productividad” cuando el trabajo de un colaborador borra literalmente el trabajo anterior de otro en un período de tiempo de un mes, por simple falta de gestión del proyecto. Eso se llama pisarse los unos a los otros.
¿Qué estamos haciendo para resolver el problema? ¿Sufriendo en silencio? ¿Viviendo en la negación? ¿Siguiendo arreglando cosas que otro romperá en el próximo año cuando no estemos mirando? ¿Siguiendo haciendo creer al Muggle, en foros de fotos, que el código abierto es tan bueno como el propietario, mientras se mantiene como comodín el hecho de que es gratis así que no tienes derecho a quejarte? ¿No es eso un poco demasiado fácil y deshonesto, este doble discurso?
¿No te gustaría dejar de hacer pasar hábitos por experiencia y confundir el fatalismo con la sabiduría, sino abordar el problema en su núcleo? ¿No crees que tú y yo merecemos algo mejor que un software diseñado por aficionados cuyo único talento es tener tiempo libre y poder permitirse trabajar gratis desde que se pasaron a la gestión y los niños se fueron a la universidad?
¿O me equivoqué desde el principio, y el código abierto es sobre dar herramientas sobre-complicadas a geeks que realmente no las necesitan, mientras tratamos de convencer al resto del mundo de que el código abierto no es un hiper-niño para desarrolladores?
Cuatro años de trabajo para llegar aquí
Después de 4 años trabajando a tiempo completo en Darktable por el 70 % del salario mínimo, y 2 años soportando la insatisfacción crónica de manchar mi nombre contribuyendo a porquería, bifurqué Ansel y no volveré atrás.
En 4 años, aporté a este software algo que le faltaba mucho: un flujo de trabajo unificado, basado en un conjunto de módulos diseñados para trabajar juntos, pero actuando cada uno en un aspecto distinto, donde los módulos de Darktable eran más bien una colección de plugins dispersos. Estamos hablando de:
- filmic,
- ecualizador de tonos,
- el módulo de desenfoques físicamente precisos,
- ambas versiones del balance de color,
- calibración de color, incluyendo la interfaz gráfica para perfilar con cuadros de color directamente en el cuarto oscuro y el balance de blancos usando estándares CIE,
- el módulo negadoctor para invertir negativos de película basado en Kodak Cineon,
- el módulo de difusión y enfoque para la adición y eliminación de desenfoque basado en difusión térmica,
- la reconstrucción laplaciana guiada de altas luces.
También desarrollé herramientas más fundamentales que proporcionan bases para los módulos anteriores:
- un solucionador de ecuaciones diferenciales parciales anisotrópicas de cuarto orden en el espacio de ondas para difusión y enfoque,
- una adaptación del anterior como el laplaciano guiado para la reconstrucción de señal RGB por propagación de gradientes,
- un modelo de apariencia de color perceptual que toma en cuenta el efecto Helmholtz-Kohlrausch en el cálculo de saturación, para limitar el efecto “fluo” que típicamente viene con configuraciones de saturación intensa, en balance de color,
- una ayuda teórica en el desarrollo del filtro guiado invariante a la exposición (EIGF), en ecualizador de tonos,
- un solucionador de ecuaciones vectoriales lineales por el método de Choleski,
- varios métodos de interpolación de orden 2, 3 y basados en radio.
En la GUI, notablemente hice:
- refactorizar la declaración de estilos, eliminando el estilo del código C para mapearlos al hoja de estilos CSS, permitiendo tener múltiples temas para la UI, incluidas las definidas por el usuario,
- introducir el modo de vista previa enfoque pico y modo de evaluación de color ISO 12 646 ,
- introducir el vocabulario de color en el selector de color global , permitiendo nombrar el color seleccionado a partir de sus coordenadas de cromaticidad, dirigiéndose a fotógrafos daltónicos.
Después de esto, escribí docenas de páginas de documentación en 2 idiomas, publiqué artículos y decenas de horas de video en YouTube para demostrar cómo usar módulos, en qué contexto y para qué beneficio, incluyendo ediciones rápidas usando solo 3 a 5 módulos para procesar el 75 al 80% de las fotos, sin importar su rango dinámico. En el mundo de código abierto, excepto quizás para proyectos respaldados por fundaciones (como Krita y Blender), este nivel de apoyo y documentación simplemente no existe, y no son los desarrolladores mismos quienes manejan este trabajo.
A pesar de todo esto, nunca tuve más de 240 donantes, para comparar con aproximadamente 1800 encuestados únicos que participaron en las encuestas de Darktable de 2020 y 2022 , y que declaran gastar entre 500 y 1000 €/año en fotografía.
No los veré destruir la usabilidad de este software mientras intento convencerme de que es progreso y no podemos hacer nada al respecto. En lugar de progreso, es la visión delirante de progreso por un grupo de aficionados de cincuenta y tantos. Han sido 2 años que me callé pacientemente, tratando de ser agradable, pero al ver la degradación de las características base, complejizadas para cumplir con las modas de los programadores locos, debería haber sido despreciable antes. Jugar bien no solucionó nada porque la tendencia no solo continuó, sino que se aceleró, y no habrá concienciación antes del punto de no retorno. No podemos esperar que aquellos que crearon los problemas sean los que los resuelvan.
Así que si tengo que trabajar para una “comunidad” que principalmente está por el aspecto de no suscripción del software, y que decidió que mi trabajo no vale el salario mínimo, bueno, lo haré bajo mis términos y con mis estándares.
En términos de características, Darktable ya tiene demasiadas y necesitamos podar. Llevo 10 años usándolo y ya está lleno de cosas mal diseñadas. El desafío ahora es presentar las características inteligentemente y corregir errores molestos antes de que esos idiotas introduzcan nuevos, o incluso los arreglen a su manera especial: ocultando el polvo debajo de la alfombra. Teniendo en cuenta que la canalización de Darktable tiene 15 años, y no podemos optimizarla mucho más que eso, dado que ya fue torturada mucho para evitar una reescritura completa (y una reescritura completa no ofrece beneficio si tenemos que mantener Gtk como back-end gráfico ya que es el principal cuello de botella de rendimiento).
Las soluciones necesarias para Darktable implican eliminar código y opciones, no agregar siempre más. La robustez está a ese precio. El equipo de Darktable hace exactamente lo opuesto sin aprender de sus errores.
Con Ansel, quiero una forma de terminar este trabajo pacíficamente para que los usuarios de Linux tengan una herramienta confiable, consistente y de alto rendimiento para su fotografía artística. Antes de cambiar a Vkdt porque el diseño actual muestra sus límites.
Translated from English by : ChatGPT. In case of conflict, inconsistency or error, the English version shall prevail.
By the way, I can no longer bare the attempt of being different for the sake of it by writing “Darktable”, proper noun, without initial capital. It’s childish, it’s neither funny or disruptive, and it makes a mess of freedesktop.org menus where the capital is anyway added to follow the standard. ↩︎
The fact that bugfixes systematically add more lines of code instead of modifying existing lines is a a concerning smell that the programming logic is bad and induces too many particular cases. Rigorous programmers always try to keep their code as generic as possible to avoid spaghetti code . ↩︎
You don’t need to trust me, the command to reproduce the stats is
git diff release-3.4.0..release-3.6.0 --shortstat -w -G'(^[^\*# /])|(^#\w)|(^\s+[^\*#/])' -- '*.c' '*.h' '*.cpp' '*.xml.in' '*.xsl' '*CMakeLists.txt' '*.cl'
↩︎git checkout release-3.0.0 & cloc $(git ls-files -- 'src/views' 'src/gui' 'src/bauhaus' 'src/dtgtk' 'src/libs')
↩︎