Co się dzieje, gdy grupka fotografów amatorów, przekwalifikowanych na programistów amatorów, w towarzystwie zgrai programistów back-endu, którzy piszą biblioteki dla programistów, postanawia pracować bez metody ani struktury nad przemysłowym oprogramowaniem dla użytkowników końcowych, którego kluczowa kompetencja (kolorymetria i psychofizyka) leży gdzieś pomiędzy licencjatem z fotografii a magisterium z nauk stosowanych, obiecując jednocześnie dostarczać 2 wersje rocznie bez zarządzania projektem? A wszystko to, rzecz jasna, w projekcie, w którym założyciele i pierwsze pokolenie programistów poszli dalej i uciekli?

Zgadnij!

Degradacja podstawowych funkcji

Lata 20. XXI wieku to 40 lat za późno, by na nowo wymyślać paradygmaty interakcji między użytkownikiem a komputerem, czy to sposób, w jaki używamy klawiatury i myszy do sterowania interfejsem, czy zachowanie przeglądarki plików. Od lat 80. XX wieku wszystkie urządzenia komputerowe przeznaczone dla szerokiego odbiorcy zbiegły się ku mniej lub bardziej ujednoliconej semantyce, gdzie klawisz escape zamyka bieżącą aplikację, podwójne kliknięcie otwiera pliki, a kółko myszy przewija bieżący widok. Darktable1 znajduje niestosowną przyjemność w ignorowaniu tego wszystkiego, a ostatnie zmiany pogarszają sprawę: teraz obowiązkowe jest przeczytanie dokumentacji, by wykonać zadania tak proste jak sortowanie plików czy przypisywanie skrótów klawiaturowych do akcji GUI.

Grupy modułów

Wszystko zaczyna się od przebudowy grup modułów  w 2020 roku, która ukrywa decyzję o niepodejmowaniu decyzji  w sprawie ujednoliconej kolejności modułów.

image

Od 2018 roku  walczę o uporządkowanie graficznego interfejsu Darktable, a w szczególności organizacji modułów. Interfejs graficzny powinien promować dobre praktyki poprzez rozmieszczenie narzędzi w typowej kolejności, w jakiej powinny być używane. Złe praktyki to te, które zwiększają ryzyko niespójności kolorymetrycznych lub edycji cyklicznej, gdzie trzeba się cofać, by przenieść zmiany dokonane później, nawet jeśli złe praktyki mogą działać w prostych przypadkach. W kontekście przetwarzania obrazu, wysoce technicznego zadania, w którym wiele rzeczy jest ukrytych przed użytkownikami końcowymi pod GUI, dobre praktyki pozwalają również słabo wykwalifikowanym osobom używać oprogramowania w sposób, który zmniejsza prawdopodobieństwo błędów.

Ta kolejność używania jest w większości podyktowana względami technicznymi, takimi jak kolejność stosowania modułów w sekwencji potoku (pipeline) oraz użycie masek rysowanych i parametrycznych, których efekt zależy od modułów wcześniej w potoku. Ignorowanie tych względów jest równoznaczne ze szukaniem kłopotów, nawet jeśli gorący trend lat 2010. i 2020. każe wierzyć, że technologie cyfrowe działają w oderwaniu od jakiejkolwiek materialnej rzeczywistości, wyłącznie dla szczęścia użytkownika.

Na przykład ograniczenie narzucone projektowi potoku, by umożliwić dowolną kolejność używania modułów, tworzy matematycznie nierozwiązywalne problemy  dotyczące obliczania współrzędnych węzłów masek, i żadne zaprogramowane rozwiązanie nie jest możliwe (poza rozluźnieniem tego ograniczenia), ponieważ matematyka powiedziała nie.

Tyle że znaczna część użytkowników-programistów kręcących się wokół projektu na Githubie i na deweloperskiej liście mailingowej pozostaje przekonana, że nie ma dobrego ani złego przepływu pracy, są tylko osobiste preferencje, co jest zapewne prawdą, gdy uprawiasz sztukę bez ograniczeń czasowych, budżetowych czy jakościowych. W związku z tym moduły powinny dać się dowolnie przestawiać, czy to w potoku, czy w przepływie pracy. Zamieszanie bierze się stąd, że edycję niedestrukcyjną błędnie postrzega się jako asynchroniczną (co prawie byłoby prawdą, gdybyśmy nie używali masek ani trybów mieszania), podczas gdy pikselowy potok jest sekwencyjny i bliższy logice warstw, jaką znajdujemy w Adobe Photoshop, Gimp, Krita itd.

Oddzielenie kolejności modułów w GUI od kolejności w potoku jest równoznaczne z umożliwieniem każdego użycia, nawet patologicznego, i zmusza do napisania stron i stron  dokumentacji, by ostrzec, wyjaśnić, co robić, jak i dlaczego; dokumentacji, której nikt nie przeczyta, by w efekcie w kółko zadawać te same pytania co tydzień na każdym forum.

W tej historii wszyscy tracą czas dzięki projektowi interfejsu, który stara się być tak elastyczny, że nie da się go domyślnie uczynić bezpiecznym i solidnym. W ciele ludzkim każdy staw ma pewną liczbę stopni swobody wzdłuż określonych osi; gdyby każdy staw mógł obracać się o 340° wokół każdej osi przestrzeni 3D, struktura byłaby niestabilna z powodu nadmiernej elastyczności i niezdolna do pracy przy dużych obciążeniach. Metafora sprawdza się w oprogramowaniu przemysłowym. Pływamy w FLOSS-owym kulcie cargo , gdzie ludzie uwielbiają mieć iluzję wyboru, czyli oferowania wielu opcji, z których większość jest bezużyteczna lub niebezpieczna, kosztem prostoty (KISS ), i gdzie większość użytkowników nie rozumie implikacji każdej opcji (i nie ma najmniejszej ochoty ich rozumieć).

Wobec braku konsensusu co do kolejności modułów w interfejsie, pod koniec 2020 roku wprowadzono skomplikowane, kruche i ociężałe narzędzie, pozwalające każdemu użytkownikowi konfigurować układ modułów w zakładkach. Oferuje ono wiele bezużytecznych opcji i przechowuje bieżący układ w bazie danych, używając przetłumaczonych nazw modułów, co oznacza, że zmiana języka interfejsu powoduje utratę ustawień. W pełni konfigurowalne, pozwala użytkownikowi zdecydować, jak zrobić sobie krzywdę, bez żadnego przewodnika po dobrych praktykach. To śmieciwo zakodowano w 4000 linii, wesoło mieszając zapytania SQL pośrodku kodu interfejsu GTK , a ustawienia tworzone są za pomocą nadmiarowych makr kompilatora , podczas gdy moduły od zawsze mają flagę binarną , pozwalającą ustawić ich domyślną grupę… modularnie.

Co ważniejsze, zastępuje ono prostą i wydajną funkcję, dostępną do Darktable 3.2:

image

Jedno kliknięcie na nazwę modułu włącza go, drugie dodaje go do kolumny ulubionych, trzecie ukrywa go z interfejsu. Wszystko to z możliwością tworzenia ustawień i przechowywania bieżącego układu w postaci prostego tekstu w pliku darktablerc. Prosta i solidna funkcja, zakodowana w 688 liniach  czytelnego i dobrze ustrukturyzowanego kodu, nie była zatem wystarczająco zabawna dla programisty dyletanta w średnim wieku i pilnie należało zastąpić ją labiryntowym systemem.

Skróty klawiaturowe

W 2021 roku dodano coś, co nazywam wielkim MIDI turducken . Celem jest rozszerzenie interfejsu skrótów klawiaturowych (już rozszerzonego w 2019 roku o obsługę „skrótów dynamicznych", pozwalających łączyć akcje myszy i klawiatury), by obsługiwać urządzenia MIDI oraz… kontrolery do gier wideo.

Pod koniec 2022 roku, czyli półtora roku po tej funkcji, w ankiecie, którą przeprowadziłem , mniej niż 10% użytkowników posiada urządzenie MIDI, a tylko 2% używa go z Darktable. Dla porównania z 45% użytkowników, którzy posiadają tablet graficzny (typu Wacom), którego obsługa w Darktable jest wciąż tak wadliwa, że tylko 6% z niego korzysta. Niezależnie od kiepskiego zarządzania priorytetami, czego tu nie toleruję, to efekty uboczne wprowadzone przez tę zmianę i globalny koszt, jaki miała, poczynając od tego, że nie importuje ona skrótów zdefiniowanych przez użytkownika z wersji wcześniejszych niż 3.2 i sprawia, że konfiguracja nowych skrótów jest potwornie skomplikowana.

Przed wielkim turducken tylko ograniczona lista akcji GUI mogła być mapowana na skróty klawiaturowe lub mieszane (klawiatura + mysz). Ta lista była ręcznie dobierana przez programistów. Wielki MIDI turducken pozwala mapować na skróty każdą akcję GUI, przedstawiając użytkownikom listę kilku tysięcy konfigurowalnych pozycji, w której trudno znaleźć te jedyne 3, których naprawdę potrzebujesz, a wyszukiwarka tekstowa jest zbyt podstawowa, by okazać się pomocna:

image

Zwróć uwagę na użycie „efektów", w kwestii których dokumentacja  w niczym nie pomaga. Dopiero przez dedukcję (bo kod też nie jest skomentowany ) w końcu zrozumiałem, że są to emulacje typowych interakcji z pulpitem (mysz i klawiatura) przeznaczone do użytku z urządzeniami MIDI i kontrolerami do gier (ale nadal musisz mi wyjaśnić, co Ctrl-Toggle, Right-activate czy Right-Toggle znaczą w kategoriach typowej interakcji z pulpitem).

Nie do zaakceptowania jest to, że użycie klawiatury numerycznej jest zepsute z założenia , zwłaszcza do przypisywania numerowanych ocen (gwiazdek) miniaturom na stole podświetlanym. Rzeczywiście, modyfikatory klawiszy (numlock i capslock) nie są poprawnie dekodowane przez to coś, a liczby są traktowane różnie w zależności od tego, czy są wprowadzane z typowej klawiatury „tekstowej", czy z klawiatury numerycznej. Tak więc 1 z klawiatury numerycznej jest dekodowane jako Keypad End, niezależnie od stanu numlocka. Właśnie tak musiałem skonfigurować skróty liczbowe na francuskiej klawiaturze BÉPO i zduplikować konfigurację dla klawiatury numerycznej:

image

Wystarczy tylko pamiętać, że Shift+" i Kp End oba oznaczają 1, i pamiętać, by zduplikować wszystkie skróty dla klawiatury numerycznej i reszty klawiatury. Krótko mówiąc, łamiemy podstawowe oczekiwanie użytkownika i posyłamy krytyków projektowania do diabła. Regresja jest wspominana na wszystkich forach Darktable, ale wydaje się nikomu nie przeszkadzać.

Poprawka tego błędu tej funkcji została wykonana w Ansel , a klawisze klawiatury numerycznej są przemapowane na standardowe klawisze bezpośrednio w kodzie , w sumie 100 linii kodu wliczając komentarze. Wykonanie tej poprawki było doprawdy bardzo trudne: przeczytałem dokumentację Gtk  i wziąłem ich przykład linia po linii. 2 lata spędzone na czekaniu na to…

Wisienką na torcie jest to, że po raz kolejny zastąpiliśmy 1306 linii jasnego i ustrukturyzowanego kodu  monstrualnością liczącą blisko 4400 linii , z takimi perełkami jak:

  1. pętla while śmierci (źródło ):
 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  }
  1. switch case zawierający if zagnieżdżone na 2 poziomach (źródło ):
 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;
 9
10      vws = view->view(view);
11    }
12    break;
13  case DT_ACTION_TYPE_LIB:
14    {
15      dt_lib_module_t *lib = (dt_lib_module_t *)owner;
16
17      const gchar **views = lib->views(lib);
18      while(*views)
19      {
20        if     (strcmp(*views, "lighttable") == 0)
21          vws |= DT_VIEW_LIGHTTABLE;
22        else if(strcmp(*views, "darkroom") == 0)
23          vws |= DT_VIEW_DARKROOM;
24        else if(strcmp(*views, "print") == 0)
25          vws |= DT_VIEW_PRINT;
26        else if(strcmp(*views, "slideshow") == 0)
27          vws |= DT_VIEW_SLIDESHOW;
28        else if(strcmp(*views, "map") == 0)
29          vws |= DT_VIEW_MAP;
30        else if(strcmp(*views, "tethering") == 0)
31          vws |= DT_VIEW_TETHERING;
32        else if(strcmp(*views, "*") == 0)
33          vws |= DT_VIEW_DARKROOM | DT_VIEW_LIGHTTABLE | DT_VIEW_TETHERING |
34                 DT_VIEW_MAP | DT_VIEW_PRINT | DT_VIEW_SLIDESHOW;
35        views++;
36      }
37    }
38    break;
39  case DT_ACTION_TYPE_BLEND:
40    vws = DT_VIEW_DARKROOM;
41    break;
42  case DT_ACTION_TYPE_CATEGORY:
43    if(owner == &darktable.control->actions_fallbacks)
44      vws = 0;
45    else if(owner == &darktable.control->actions_lua)
46      vws = DT_VIEW_DARKROOM | DT_VIEW_LIGHTTABLE | DT_VIEW_TETHERING |
47            DT_VIEW_MAP | DT_VIEW_PRINT | DT_VIEW_SLIDESHOW;
48    else if(owner == &darktable.control->actions_thumb)
49    {
50      vws = DT_VIEW_DARKROOM | DT_VIEW_MAP | DT_VIEW_TETHERING | DT_VIEW_PRINT;
51      if(!strcmp(action->id,"rating") || !strcmp(action->id,"color label"))
52        vws |= DT_VIEW_LIGHTTABLE; // lighttable has copy/paste history shortcuts in separate lib
53    }
54    else
55      fprintf(stderr, "[find_views] views for category '%s' unknown\n", owner->id);
56    break;
57  case DT_ACTION_TYPE_GLOBAL:
58    vws = DT_VIEW_DARKROOM | DT_VIEW_LIGHTTABLE | DT_VIEW_TETHERING |
59          DT_VIEW_MAP | DT_VIEW_PRINT | DT_VIEW_SLIDESHOW;
60    break;
61  default:
62    break;
63  }
  1. zagnieżdżony switch case demona, z przebiegle ukrytymi dodatkowymi klauzulami (źródło ):
 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\n", effect);
25      break;
26    }

Programiści rozumieją, o czym mówię; dla pozostałych, po prostu wiedzcie, że rozumiem z tego nie więcej niż wy: to gówniany kod, i jeśli nie kryje się w nim kilka błędów, będzie to czyste szczęście. Polowanie na błędy w tym gównianym dole to archeologia dna kanału ściekowego, tym bardziej że Darktable nie ma dokumentacji deweloperskiej i, wobec braku sensownych komentarzy w kodzie, każda modyfikacja wspomnianego kodu z konieczności zacznie się od fazy inżynierii wstecznej, coraz trudniejszej wraz z upływem czasu.

Prawdziwym problemem tego rodzaju kodu jest to, że nie da się go ulepszyć bez mniej lub bardziej całkowitego przepisania: żeby go naprawić, najpierw trzeba go zrozumieć, ale powodem, dla którego wymaga naprawy, jest właśnie to, że jest niezrozumiały i niebezpieczny w dłuższej perspektywie. Nazywamy to długiem technicznym . Krótko mówiąc, cała praca zainwestowana w tę funkcję stworzy dodatkową pracę, bo trzymanie tego rodzaju kodu pośrodku bazy kodu liczącej kilkaset tysięcy linii i oczekiwanie, że nie wybuchnie nam pewnego dnia w twarz, jest nierozsądne.

Jest to tym bardziej śmieszne w kontekście aplikacji open-source/wolnego oprogramowania, gdzie trzon załogi stanowią nieprzeszkoleni programiści. Sprytni programiści piszą kod zrozumiały dla idiotów, i na odwrót.

Filtry kolekcji

Do Darktable 3.8 filtry kolekcji, na górze stołu podświetlanego, służyły do tymczasowego ograniczania widoku na kolekcję. Kolekcja to wyciąg z bazy zdjęć oparty na pewnych kryteriach, przy czym najczęstszym jest wyodrębnienie zawartości folderu (który Darktable nazywa „rolką filmu", by wszystkich zmylić, bo rolka filmu to w rzeczywistości zawartość folderu wyświetlona jako płaska lista zamiast drzewa — wielu ludzi błędnie myśli, że Darktable nie ma menedżera plików).

Będąc użytkownikiem Darktable od ponad dekady, mam bazę danych liczącą ponad 140 000 pozycji. Wyodrębnienie kolekcji spośród tych 140 00 zdjęć to powolna operacja. Ale moje foldery rzadko zawierają więcej niż 300 zdjęć. Filtrowanie na przykład zdjęć ocenionych na 2 gwiazdki lub więcej w kolekcji 300 plików jest szybkie, bo jest to podzbiór 300 elementów. I przełączanie się z jednego filtra na drugi też jest szybkie. Filtr to tylko częściowy lub całkowity widok kolekcji, zoptymalizowany pod szybkie i tymczasowe użycie w trybie start-and-go.

Pod pretekstem refaktoryzacji kodu filtrowania, który liczył w sumie 550 linii , szef Gaston Lagaffe  uczynił swoim powołaniem złamanie tego modelu, by zamienić filtry kolekcji w podstawowe kolekcje, za pomocą ponad 6000 linii kodu , nie licząc niezliczonych poprawek błędów, które tylko dodawały kolejne linie2. A wszystko to, jak zwykle, wysoce konfigurowalne i nadmiarowe wobec klasycznego modułu kolekcji , który tam pozostał, obsługiwane ikonami tak zagadkowymi, że musiano dodać tekstowe podpowiedzi po najechaniu, by wyjaśnić, co znaczą..

W tym jakościowym kodzie znajdziemy niekończące się while pod switch case w if w if w for (źródło ):

 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);
 4
 5    if(n == 5)
 6    {
 7      if(k > 0)
 8      {
 9        c = g_strlcpy(out, "<i>   ", outsize);
10        out += c;
11        outsize -= c;
12        switch(mode)
13        {
14          case DT_LIB_COLLECT_MODE_AND:
15            c = g_strlcpy(out, _("AND"), outsize);
16            out += c;
17            outsize -= c;
18            break;
19          case DT_LIB_COLLECT_MODE_OR:
20            c = g_strlcpy(out, _("OR"), outsize);
21            out += c;
22            outsize -= c;
23            break;
24          default: // case DT_LIB_COLLECT_MODE_AND_NOT:
25            c = g_strlcpy(out, _("BUT NOT"), outsize);
26            out += c;
27            outsize -= c;
28            break;
29        }
30        c = g_strlcpy(out, "   </i>", outsize);
31        out += c;
32        outsize -= c;
33      }
34      int i = 0;
35      while(str[i] != '\0' && str[i] != '$') i++;
36      if(str[i] == '$') str[i] = '\0';
37
38      gchar *pretty = NULL;
39      if(item == DT_COLLECTION_PROP_COLORLABEL)
40        pretty = _colors_pretty_print(str);
41      else if(!g_strcmp0(str, "%"))
42        pretty = g_strdup(_("all"));
43      else
44        pretty = g_markup_escape_text(str, -1);
45
46      if(off)
47      {
48        c = snprintf(out, outsize, "<b>%s</b>%s %s",
49                     item < DT_COLLECTION_PROP_LAST ? dt_collection_name(item) : "???", _(" (off)"), pretty);
50      }
51      else
52      {
53        c = snprintf(out, outsize, "<b>%s</b> %s",
54                     item < DT_COLLECTION_PROP_LAST ? dt_collection_name(item) : "???", pretty);
55      }
56
57      g_free(pretty);
58      out += c;
59      outsize -= c;
60    }
61    while(buf[0] != '$' && buf[0] != '\0') buf++;
62    if(buf[0] == '$') buf++;
63  }

i inne if zagnieżdżone na 2 poziomach wewnątrz switch case, niezbędne do obsługi skrótów klawiaturowych (źródło ).

To ostatnie pieprzone gówno było kroplą, która przelała czarę, i skłoniło mnie do sforkowania Ansel. Odmawiam pracy nad tykającą bombą w zespole, który nie widzi problemu i bawi się kodem w wolnym czasie. Kodowanie może bawić ich, nie mnie. A naprawianie gówna zrobionego przez nieodpowiedzialne dzieciaki dwa razy starsze ode mnie, zwłaszcza gdy psują rzeczy, które uporządkowałem 3 czy 4 lata temu, doprowadza mnie do wściekłości.

Stół podświetlany

Stół podświetlany przeszedł 2 niemal całkowite przepisania, pierwsze na początku 2019 roku i drugie pod koniec 2019 roku, które dodało wiele dyskusyjnych funkcji, jak widok selekcji (culling) .

W skrócie, tryb selekcji (culling) dzieli się na 2 podtryby: dynamiczny i statyczny, które inaczej zarządzają liczbą obrazów. Wielu użytkowników 4 lata później wciąż nie zrozumiało różnicy. Mamy więc widok domyślny (menedżer plików), powiększalny stół podświetlany (którego nikt nie używa), statyczną selekcję, dynamiczną selekcję oraz tryb podglądu (pojedyncze zdjęcie na pełnym ekranie).

Następnie do miniatur na stole podświetlanym dodano więcej opcji wyświetlania, pozwalających definiować nakładki: podstawowe stałe nakładki, rozszerzone stałe nakładki EXIF, te same, ale tylko po najechaniu, i wreszcie czasowe nakładki po najechaniu (z konfigurowalnym licznikiem czasu).

Kod UI renderujący miniatury i ich nakładki musi zatem uwzględniać 5 różnych widoków i 7 wariantów wyświetlania , czyli 35 możliwych kombinacji. Kod zapewniający właściwe skalowanie miniatur potrzebuje zatem w sumie 220 linii .

Ale na tym się nie kończy, bo kod renderujący GUI miniatur jest współdzielony również z dolnym paskiem „taśmy filmowej" (filmstrip), co w rzeczywistości daje 36 możliwych kombinacji w renderowaniu miniatur. Pomnożone przez 3 motywy GUI o różnych barwach bazowych, daje to 108 zestawów instrukcji CSS, by w pełni ostylować GUI… z których wiele zapomniano w przebudowie graficznej Darktable 4.0 , i jak mogłoby być inaczej?

W Darktable 2.6 mieliśmy 4193 linii na cały pakiet, który miał tylko widoki menedżera plików, powiększalnego stołu podświetlanego i pełnoekranowego podglądu, z zaledwie 2 trybami nakładek miniatur (zawsze widoczne lub widoczne po najechaniu):

Po Darktable 3.0 i dodaniu trybów selekcji dochodzimy do 6731 linii:

Po Darktable 3.2 i dodaniu 7 wariantów wysoce konfigurowalnych nakładek oraz pewnej refaktoryzacji kodu dochodzimy do 8380 linii:

W Darktable 4.2, po poprawieniu wielu błędów, dochodzimy do sumy 9264 linii:

Liczba linii (zwłaszcza w kodzie znajdującym niestosowną przyjemność w ignorowaniu dobrych praktyk programistycznych) jest bezpośrednim wskaźnikiem trudności debugowania czegokolwiek w tym miejscu, ale także pośrednim wskaźnikiem (w konkretnym przypadku kodu GUI) obciążenia CPU wymaganego do uruchomienia oprogramowania.

Rzeczywiście, jeśli uruchomisz darktable -d sql i najedziesz na miniaturę na stole podświetlanym, otrzymasz w terminalu:

 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"

co oznacza, że przeciwko bazie danych wykonywanych jest 18 zapytań SQL, by pobrać informacje o obrazie, i uruchamianych za każdym razem, gdy kursor najeżdża na nową miniaturę, bez powodu, skoro metadane nie zmieniły się od poprzedniego najechania.

W Ansel, usuwając większość opcji, udało mi się zaoszczędzić 7 zapytań, co wciąż nie zapobiega zduplikowanym zapytaniom, ale mimo to nieco poprawia czasy (znaczniki czasu to liczby rozpoczynające każdą linię):

 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"

Problem w tym, że kod źródłowy zagnieżdża polecenia SQL wewnątrz funkcji rysujących GUI, a rozplątywanie tego bałaganu przez różne warstwy odziedziczone po „refaktoryzacji" (mającej rzekomo uprościć kod, ale w rzeczywistości nic z tego) to po raz kolejny archeologia. A gdyby problem naprawiono, gdy kod miał 6700 linii w 3 plikach, nie szukalibyśmy 4 lata później przyczyn w 2500 dodatkowych liniach rozsianych teraz po 7 różnych plikach (nie licząc plików .h).

Mamy tu podręcznikowy przypadek, w którym „refaktoryzacja" faktycznie skomplikowała kod i w którym scalenie kodu miniatur między taśmą filmową a stołem podświetlanym tylko dodało więcej wewnętrznych if (gałęzi) zagnieżdżonych na kilku poziomach, co jeszcze bardziej komplikuje strukturę, tylko po to, by ślepo podążać za zasadą ponownego użycia kodu , która wchodzi tu w konflikt z zasadą modularności , którą wprawny programista rozwiązałby przez [dziedziczenie](https://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming) , bo nawet jeśli nie jest to łatwe w C, jest jak najbardziej możliwe (właściwie Darktable używa tej zasady w kodzie z lat 2009–2010).

Kosmetyka bierze górę nad stabilnością

Darktable 4.2 wprowadza podgląd stylów w ciemni. Byłoby to wspaniałe, gdyby style nie były głęboko zepsute przy użyciu z niedomyślną kolejnością potoku i wieloma instancjami modułów. Problem w tym, że czyste i długoterminowe rozwiązanie wymaga teorii grafów skierowanych , i tu właśnie zgubiliśmy naszych ukochanych kopiuj-wklej-pisaczy kodu.

W tym samym duchu mamy duże niespójności przy kopiowaniu-wklejaniu historii w trybie nadpisywania, gdy używane są także domyślne ustawienia użytkownika (zwłaszcza w module balansu bieli). Ale znacznie zabawniej jest zasyfić interfejs, więc pozostanie to tam przez długi czas.

Darktable 3.6 i 3.8 wprowadziły wiele wariantów histogramu: wektroskop, pionową waveformę, zaawansowane i egzotyczne przestrzenie barwne. Tyle że jeśli uruchomisz darktable -d perf w terminalu i otworzysz ciemnię, zobaczysz mnóstwo

123.748084 [histogram] took 0.003 secs (0.000 CPU) scope draw
223.773753 [histogram] took 0.005 secs (0.004 CPU) scope draw
323.783284 [histogram] took 0.001 secs (0.000 CPU) scope draw

za każdym razem, gdy poruszysz kursorem w oknie (i to nawet nie nad histogramem). To histogram jest przerysowywany przy każdej interakcji między kursorem a oknem. Ten sam problem dotyka wiele niestandardowych widżetów graficznych , a jego przyczyna jest niezidentyfikowana. Zauważ, że nie dotyczy on Ansel, więc przyczyna powinna kryć się gdzieś w 23 000 linii kodu, które usunąłem.

Dwukrotnie próbowałem zrefaktoryzować ten cyrk, jakim stała się ta funkcja , ale za każdym razem wpychano nową, pilniejszą funkcję, która unieważniała moją pracę. Po prostu się poddałem.

Stan rozkładu histogramu jest taki, że pełne przepisanie zajęłoby mniej czasu niż refaktoryzacja, zwłaszcza że histogram jest próbkowany o wiele za późno w potoku, w przestrzeni barwnej ekranu, co czyni definicję przestrzeni barwnej histogramu bezcelową, skoro gamut jest i tak przycięty do przestrzeni barwnej ekranu, cokolwiek by robić. Ale zgadnij co… Darktable 4.4 będzie miał jeszcze więcej opcji, z możliwością definiowania harmonii barwnych (fundamentalnych dla geeków, którzy malują według numerków i edytują histogramy).

Pozostaje faktem, że za każdym razem, gdy poruszysz kursorem, uruchamiana jest ogromna liczba bezużytecznych ponownych obliczeń, na darmo. Jak źle jest? Wpadłem na pomysł zmierzenia użycia CPU mojego systemu w stanie bezczynności, za pomocą linuksowego narzędzia powertop. Protokół jest dość prosty: laptop (CPU Intel Xeon Mobile 6. generacji), pracujący na baterii w trybie oszczędzania energii, podświetlenie ustawione na minimum, otwórz aplikację i niczego nie dotykaj przez 4 min, a następnie monitoruj globalne zużycie CPU systemu raportowane przez powertop w 5. minucie:

  • System bazowy (żadna aplikacja nieotwarta poza powertop działającym w terminalu): 3,0 do 3,5% CPU
  • Ansel:
    • otwarty na stole podświetlanym: 2,9 do 3,4% CPU,
    • otwarty w ciemni: 3,8 do 4,5% (przed przywróceniem grup modułów do Darktable 3.2),
    • otwarty w ciemni: 3,0 do 3,5% (po przywróceniu grup modułów),
  • Darktable:
    • otwarty na stole podświetlanym: 6,6 do 7,1% CPU,
    • otwarty w ciemni: 30,9 do 44,9% CPU (nie, to nie błąd z przecinkiem),

Nie rozumiem, co Darktable oblicza, gdy zostawiamy go otwartego bez dotykania komputera, bo nie ma nic do obliczenia. Darktable na stole podświetlanym sam zużywa tyle, co cały system (Fedora 37 + pulpit KDE + menedżer haseł i klient Nextcloud działające w tle), a otwarty w ciemni zużywa 10 razy tyle, co cały system.

Wszystko to wskazuje na bardzo zabugowany kod interfejsu graficznego. W Ansel usunąłem sporą część brudnego kodu, nie optymalizując niczego innego, a te liczby jedynie potwierdzają mój wybór: brudny kod ukrywa problemy niewykrywalne przy jego czytaniu, i po prostu nie możemy dłużej podążać tą drogą.

Najwyraźniej jestem jedynym, który uważa za nie do przyjęcia pozbawianie pikselowego potoku od jednej trzeciej do połowy mocy CPU po to, by malować głupi interfejs. Jakkolwiek by na to patrzeć, nie ma żadnego uzasadnionego powodu, by oprogramowanie pozostawione otwarte bez dotykania zamieniało komputer w toster, zwłaszcza że nie kupujemy już rosyjskiego gazu.

Pracując przeciwko sobie

Jesteśmy fotografami. Fakt, że potrzebujemy komputera do uprawiania fotografii, to nowość (licząca 20 lat), związana z technologią obrazowania cyfrowego, która z wszelkiego rodzaju powodów (dobrych i złych) zastąpiła 160-letnią technologię, znaną i opanowaną. Przy okazji fakt, że potrzebujemy komputera i oprogramowania, by tworzyć obrazy, to czysty i zwykły narzut . Zmuszanie ludzi, którzy nie rozumieją komputerów, do ich używania w celu wykonywania zadań, z którymi wcześniej doskonale radzili sobie ręcznie, to także forma opresji, a ukrywanie tego pod postacią jakiegoś postępu technicznego to forma przemocy psychologicznej.

Oprogramowanie oznacza rozwój, utrzymanie, dokumentację i zarządzanie projektem. To kilka warstw narzutu ponad poprzednie. A jednak fakt, że siła robocza w projektach open-source nie domaga się wynagrodzenia, nie powinien przesłaniać faktu, że czas poświęcony (stracony?) na oprogramowanie, jego użycie, jego rozwój, jego utrzymanie, jest sam w sobie kosztem bezzwrotnym.

Kilka powyższych przykładów daje wgląd w komplikowanie kodu źródłowego, ale także w jego degradację w czasie pod względem jakości, bo podstawowe i solidne funkcje zostają zastąpione kodem spaghetti , mylącym i przebiegle zabugowanym. Za tą kwestią czytelności prawdziwym problemem jest utrudnienie utrzymania w średnim terminie, co obiecuje projektowi ponurą przyszłość, za aprobatą opiekuna.

Przez 4 lata, odkąd pracuję na pełny etat nad Darktable, 2022 to pierwszy rok, w którym stwierdzam, że praktycznie nie jestem w stanie zidentyfikować przyczyny większości błędów interfejsu, bo logika działania stała się bardzo zaciemniona, a kod niezrozumiały. Liczba naprawionych błędów jest też w ciągłym spadku, zarówno w wartości bezwzględnej, jak i w proporcji do scalonych pull requestów, podczas gdy wolumen ruchu w kodzie pozostaje mniej więcej stały (uwaga 1: poniższe zliczenia linii kodu obejmują tylko pliki C/C++/OpenCL i generatywne pliki XML oraz wykluczają komentarze 3) (uwaga 2: liczba otwartych zgłoszeń liczona jest za okres życia poprzedniej wersji):

3.0 (grudzień 2019, rok po 2.6)
  • 1049 otwartych zgłoszeń , 66 zgłoszeń zamkniętych / 553 pull requesty scalone (12%),
  • 398 zmienionych plików, 66 tys. wstawień, 22 tys. usunięć, (netto: +44 tys. linii),
3.2 (sierpień 2020)
  • 1028 otwartych zgłoszeń , 92 zgłoszenia zamknięte / 790 pull requestów scalonych (12%),
  • 586 zmienionych plików, 54 tys. wstawień, 43 tys. usunięć (netto: +2 tys. linii),
3.4 (grudzień 2020)
  • 981 otwartych zgłoszeń , 116 zgłoszeń zamkniętych / 700 pull requestów scalonych (17%),
  • 339 zmienionych plików, 46 tys. wstawień, 23 tys. usunięć (netto: +23 tys. linii),
3.6 (czerwiec 2021)
  • 759 otwartych zgłoszeń , 290 zgłoszeń zamkniętych / 954 pull requesty scalone (30%),
  • 433 zmienione pliki, 53 tys. wstawień, 28 tys. usunięć (netto: +25 tys. linii),
3.8 (grudzień 2021)
  • 789 otwartych zgłoszeń , 265 zgłoszeń zamkniętych / 571 pull requestów scalonych (46%),
  • 438 zmienionych plików, 41 tys. wstawień, 21 tys. usunięć (netto: +20 tys. linii),
4.0 (czerwiec 2022)
  • 632 otwarte zgłoszenia , 123 zgłoszenia zamknięte / 586 pull requestów scalonych (21%),
  • 359 zmienionych plików, 30 tys. wstawień, 15 tys. usunięć (netto: +15 tys. linii),
4.2 (grudzień 2022)
  • 595 otwartych zgłoszeń , 60 zgłoszeń zamkniętych / 409 pull requestów scalonych (15%),
  • 336 zmienionych plików, 14 tys. wstawień, 25 tys. usunięć (netto: -11 tys. linii),
  • (usunięcia wynikają głównie z usunięcia ścieżki SSE2 w kodzie pikselowym, obciążając wydajność typowych CPU Intel i5/i7 na korzyść CPU AMD Threadripper),
4.4 (czerwiec 2023)
  • 500 otwartych zgłoszeń , 97 zgłoszeń zamkniętych / 813 pull requestów scalonych (12%),
  • 479 zmienionych plików, 57 tys. wstawień, 41 tys. usunięć (netto: +16 tys. linii),

Aby ułatwić porównanie, przeliczmy je w skali roku:

  • 2019: 1049 nowych zgłoszeń, 66 zamkniętych, 88 tys. zmian, +44 tys. wierszy,
  • 2020: 2009 nowych zgłoszeń, 208 zamkniętych, 166 tys. zmian, +25 tys. wierszy,
  • 2021: 1548 nowych zgłoszeń, 555 zamkniętych, 143 tys. zmian, +45 tys. wierszy,
  • 2022: 1227 nowych zgłoszeń, 183 zamknięte, 84 tys. zmian, +4 tys. wierszy.

Wygląda na to, że nie tylko ja uważam błędy z 2022 roku za znacznie trudniejsze do usunięcia, bo naprawiono ich znacznie mniej niż w 2021 roku, a rok 2023 jak dotąd pokazuje ten sam trend. Stosunek liczby pull requestów (faktycznie wykonana praca) do liczby zamkniętych zgłoszeń (faktycznie rozwiązane problemy) jest po prostu śmieszny.

Między Darktable 3.0 a 4.0 kod interfejsu graficznego urósł o 53%, z 49 tys. do 75 tys. wierszy4 (pomijając komentarze i puste wiersze), a w wersji 4.4 osiągnął 79 tys. wierszy. Pomijając jego niską jakość, wcale nie jestem pewien, czy poprawiło to użyteczność oprogramowania o 53%. Wręcz przeciwnie, jestem raczej przekonany, że jest odwrotnie. W Ansel zredukowałem jak dotąd kod interfejsu graficznego do 53 tys. wierszy, usuwając niewiele funkcji.

To wszystko jest po prostu zbyt dużo i zbyt szybko jak na grupkę hobbystów pracujących wieczorami i w weekendy bez struktury i planowania. Zespół Darktable działa na własną szkodę, próbując odgryźć więcej, niż jest w stanie przełknąć, obsługując zbyt wiele różnych opcji, tworząc kod, którego rezultat zależy od zbyt wielu zmiennych środowiskowych, dający się obsługiwać na zbyt wiele różnych sposobów. A wszystko po to, by unikać decyzji projektowych, które mogłyby urazić kogoś przez ograniczenie funkcji i dostępnych opcji. Po stronie użytkownika końcowego skutkuje to kontekstowymi błędami niemożliwymi do odtworzenia na innych systemach, a więc w ogóle niemożliwymi do naprawienia.

To proste: wykonana praca kosztuje coraz więcej pracy, a utrzymanie nie jest zapewnione, jak pokazuje spadek liczby zamkniętych zgłoszeń, bo tego jest po prostu za dużo. W firmie to byłby moment, w którym trzeba zatamować krwotok, zanim opróżni się skarbiec. Ale zespół amatorów, którego nic nie zobowiązuje do dostarczenia wyników, może wytrzymać nieskończoną ilość strat. Tyle że praca tworzona przez pracę jest z czasem coraz bardziej żmudna, frustrująca i trudna, a użytkownicy końcowi są brani jako zakładnicy przez gang samolubnych dupków i zapłacą za to złożonością interfejsu, niepotrzebnym obciążeniem procesora oraz koniecznością uczenia się na nowo, jak wykonać w tym oprogramowaniu podstawowe zadania, przynajmniej raz w roku.

Właściwie spodziewam się, że obecny zespół masowej destrukcji wygodnie znajdzie coraz mniej wolnego czasu, by wnosić wkład w projekt, gdy zdadzą sobie sprawę, że wpakowali się w jednokierunkową ulicę z ciężarówką z naczepą, zostawiając swoje gówno następnym. Ale im wcześniej się poddadzą, tym mniej szkód wyrządzą.

Rozpasanie opcji i ustawień, które jest ulubioną strategią Darktable do (nie)zarządzania rozbieżnościami projektowymi, tworzy super-kontekstowe przypadki użycia, w których żaden użytkownik nie ma włączonych tych samych opcji i w których niemożliwe jest odtworzenie błędów w innym środowisku. A proszenie użytkowników o dołączanie pliku konfiguracyjnego darktablerc do zgłoszeń błędów też by nie pomogło, skoro ten plik ma obecnie 1287 praktycznie nieczytelnych wierszy.

Dziwne i trudne do odtworzenia błędy piętrzą się nawet na komputerach System 76 , zaprojektowanych specjalnie pod Linuksa, gdzie nie da się zrzucić winy na sterowniki. Wiele niespójnych i losowych błędów, które zaobserwowałem podczas prowadzenia lekcji edycji, nie jest wymienionych w systemie śledzenia błędów, i jest raczej jasne, że tkwią gdzieś w zawiłościach rozpasania if i switch case, dodawanych w alarmującym tempie od 2020 roku.

Naprawienie tych dziwnych i kontekstowych błędów jest możliwe tylko przez uproszczenie przepływu sterowania programu, a więc przez ograniczenie liczby parametrów użytkownika. Ale sfora maniaków wymachujących ramionami na projekcie nie chce o tym słyszeć, a co gorsza, „poprawki" błędów zwykle jedynie dodają kolejne wiersze, by radzić sobie z patologicznymi przypadkami pojedynczo.

W istocie Darktable cierpi na kilka problemów:

  1. Twardy trzon dość miernych deweloperów, którzy mają mnóstwo wolnego czasu na robienie przypadkowych rzeczy, kierowani najlepszymi na świecie intencjami, ale nieświadomi wyrządzanych szkód (mierni ludzie zawsze są bardziej dostępni),
  2. Pobłażliwość opiekuna, który przepuszcza brudny kod, żeby być miłym,
  3. Krytyczny brak umiejętności w czystej matematyce, algorytmice, przetwarzaniu sygnałów, nauce o kolorze i ogólnie w myśleniu abstrakcyjnym, które są wymagane poza kodem przetwarzania pikseli, by upraszczać i faktoryzować funkcje,
  4. Godny pogardy nawyk „programowania" przez kopiowanie i wklejanie kodu ściągniętego skądinąd w projekcie lub z innych projektów FLOSS, które mogły używać innej architektury potoku (pipeline), ale bez odpowiedniego dostosowania go (dostosowanie zakłada zrozumienie, a to zbyt wiele, by o to prosić…),
  5. Uczciwa i wprost odmowa okrawania funkcji, by zrobić miejsce nowym i zachować pewną równowagę,
  6. Błąd doboru próby, w którym jedynymi użytkownikami wchodzącymi w interakcję z rozwojem przez Github są programiści i osoby anglojęzyczne. Faktem jest, że ogół odbiorców nie rozumie, czym jest kuźnia kodu, i trudno zachęcić osoby nieprogramujące, by założyły konto na Github w celu zgłaszania błędów. Mówimy o próbie użytkowników złożonej w ponad 44% z programistów  i w ponad 35% z osób z wyższym wykształceniem (stanowią oni odpowiednio 6% i 15% w ogólnej populacji).
  7. Styl rozwoju na przyspieszonym marszu, bez planowania i dialogu, w którym każdy użytkownik Github może zaśmiecać dyskusje niewykształconą opinią na temat bieżących prac. Faktem jest, że przetwarzanie obrazu wygląda na łatwe i jest nieszkodliwe, do tego stopnia, że każda osoba potrafiąca obliczyć logarytm czuje się kompetentna. Ale fundamentalne błędy w łańcuchu kolorymetrycznym Darktable są tam, by codziennie przypominać nam o czymś przeciwnym.
  8. Brak ogólnoprojektowych priorytetów dotyczących tego, które funkcje refaktoryzować, stabilizować czy rozszerzać: wszystkie projekty są otwarte jednocześnie, nawet jeśli kolidują ze sobą.
  9. Ilość aktywności (e-maile i powiadomienia) niemożliwa do śledzenia, między komentarzami, dyskusjami nie na temat, błędami, które nimi nie są, propozycjami zmian w kodzie, faktycznymi zmianami w kodzie, które mogą wpłynąć na Twoją własną pracę w toku, co oznacza, że musisz być cały czas wszędzie naraz; jest dużo do przeczytania, bardzo mało do zachowania, dyskusja dla samej dyskusji utrudnia produktywność, a brak struktury pracy jest główną przyczyną tego wszystkiego,
  10. Nieblokujące błędy pospiesznie ukrywane, zanim skończymy je rozumieć, zamiast naprawiać je naprawdę i rozwiązywać u źródła, co długoterminowo przesuwa, a nawet pogłębia problemy, nie pozostawiając śladów w jakiejkolwiek dokumentacji,
  11. Harmonogram wydań, którego trzymamy się bez względu na cenę, nawet gdy jest nierealistyczny, mimo że nikt nam go nie narzuca poza nami samymi,
  12. Zmiany w kodzie, które mogą nastąpić w każdej chwili i w każdym miejscu, co oznacza, że pracujemy na ruchomych piaskach i że musimy pracować tak szybko i tak źle jak inni, by nie zostać w tyle za ilością i częstotliwością zmian (commitów),
  13. Nowe funkcje, które pogarszają użyteczność i komplikują obsługę bez rozwiązywania konkretnego problemu, służące jako rozrywkowe projekty dla deweloperów nieprzeszkolonych w projektowaniu/inżynierii.

Ale najbardziej wkurzający jest ten upór, by zastępować proste i działające funkcje potworkami nadmiernej inżynierii, przeznaczonymi do zadowolenia dewiacyjnych i marginalnych zastosowań, przy jednoczesnym utrudnianiu życia wszystkim niekończącymi się listami bezmyślnych opcji. Najlepszym miejscem, by ukryć drzewo, jest środek lasu, a wielu wciąż tego nie pojęło.

Mylenie krzątaniny z aktywnością

Każdy wyborca lubi krytykować patologię polegającą w polityce na wydawaniu okolicznościowych ustaw, źle napisanych, by uspokoić opinię publiczną po jakimś szczególnym wydarzeniu, żeby pokazać, że działamy, podczas gdy podobne ustawy już istnieją i nie są, lub nie w pełni, stosowane z braku środków. Nazywamy to krzątaniną: to wygląda jak działanie, brzmi jak działanie, ma koszt działania, ale nie prowadzi do niczego namacalnego czy praktycznego.

Zespół amatorów bez zarządzania projektem, krzątający się wokół Darktable, produkuje jedynie przyszłe problemy. W przeszłości Darktable było wydawane raz w roku z około 1500 do 2000 commitów  przewagi nad poprzednią wersją. To teraz ilość zmian osiągana w 6 miesięcy. „Praca", której ilość rośnie tak szybko, nie prowadząc do metod pracy zespołowej, w tym jasnych priorytetów dla każdego wydania i podziału zadań, i bez kontroli jakości oprogramowania opartej na obiektywnych metrykach (liczba kroków lub czas potrzebny do wykonania konkretnego zadania), to tylko goście depczący sobie nawzajem po palcach, forsując własną agendę bez troski o innych, o projekt czy o użytkowników.

Darktable stało się licealnym klubem informatycznym, gdzie maniacy dobrze się bawią. To globalnie podsumowanie wszystkich najgorszych historii z firm IT, z tą różnicą, że projekt nie zarabia ani grosza, co czyni pilnym pytanie samych siebie, dlaczego to sobie narzucamy: nie ma zysków do podziału, ale wszyscy dzielą koszty. To chaotyczne i toksyczne środowisko pracy, które produkowałoby jedynie wypalenie, gdyby amatorzy na pół etatu byli zobowiązani do dostarczania wyników i musieli pracować na pełen etat. Będąc jedynym gościem pracującym nad tym na pełny etat, pozostawiam Twojej wyobraźni ilość stresu i utraconej energii, by być na bieżąco z permanentną kakofonią, tylko po to, by mieć pewność, że nie przegapię tych 2% faktycznie istotnych dla mnie w ilości szumu produkowanego przez nieuregulowane dyskusje.

Po stronie użytkownika wychwala się wrzenie projektu Darktable (owszem, jest ruch), nie zdając sobie sprawy, że rozpędzone commity to nie aktywność, lecz krzątanina, a w szczególności dług techniczny, który trzeba będzie spłacić nie-wiadomo-kiedy przez nie-wiadomo-kogo. Piękno projektu, w którym nikt nie musi brać odpowiedzialności za swoje bzdury, bo nikt za nic nie odpowiada: tak jest napisane w licencji GNU/GPL. Możemy więc spieprzyć pracę poprzedników z całkowitą bezkarnością.

Mamy zalążek kontroli jakości, poprzez testy integracyjne, które mierzą błąd percepcyjny na referencyjnych przetworzeniach obrazu, ale nie wywołują one żadnej reakcji, gdy widzimy średni błąd delta E rzędu 1,3 (mały), podczas gdy natura zmiany powinna dawać ściśle zerowy delta E. Jeśli jedyny test przechodzi (bo testujemy pojedynczy obraz SDR na zdjęciu studyjnym), nie zadaje się pytania, czy teoria jest solidna i odporna. Zamieniliśmy test w zwolnienie z odpowiedzialności, dopóki metryka utrzymuje się poniżej progu walidacji…

Wraz z wydaniem Darktable 4.0 — Geektable — zobaczyłem na YouTube ludzi zaczynających narzekać, że to wydanie nie jest zbyt ekscytujące. Po latach faszerowania ludzi wydaniami w superlatywach, wypchanymi nowymi funkcjami, których nie mamy czasu porządnie przetestować (6-8 gości, którzy sikają 38-50 tys. wierszy co 6 miesięcy, pracując tylko wieczorami i w weekendy, ty wciąż śnisz?), uzależniliśmy ich od przeładowanej choinki, by zapewnić sobie, że w dniu, w którym zaczniemy być odpowiedzialni i wydawać stabilne wersje (a więc nudne), zostanie to użyte przeciwko nam.

Powodem tego frenetycznego tempa wydań jest to, że pull requesty starsze niż 3 miesiące są systematycznie w konflikcie z gałęzią master, biorąc pod uwagę, że tą trzęsie się co miesiąc na potrzeby „uogólnionych testów". Ale nieliczni użytkownicy, którzy budują gałąź master, nie mają pojęcia, co konkretnie mają testować, chyba że przeanalizowali historię commitów Git, co zakłada zrozumienie zarówno języka C, jak i wpływu zmian w praktyce na oprogramowanie.

By ograniczyć długożyjące gałęzie, które nieuchronnie skończą w konflikcie z master, znaleźliśmy genialne rozwiązanie: wydajemy 2 wersje rocznie, czyniąc rozwój na przyspieszonym marszu, oparty na niedokończonym i ledwo przetestowanym kodzie, sposobem na życie, nigdy nie zdając sobie sprawy, że podstawowym problemem jest przede wszystkim brak planowania, ale też to, że współtwórcy zaczynają kodować, zanim skończą definiować problem do rozwiązania (gdy jest jakiś prawdziwy problem do rozwiązania, a nie tylko gość, który obudził się w stylu „fajnie by było, gdyby…"), pracując równolegle nad obiema częściami bez komunikacji.

Kurz nie zdąży opaść, a już znowu trzęsiemy bazą kodu, nie wymuszając faz stabilizacji, w których tylko czyścimy błędy (i nie mówię o miesiącu zamrożenia funkcji przed wydaniem, ale o wydaniach poświęconych wyłącznie czyszczeniu kodu). System śledzenia błędów implodują w ciągu 3 tygodni po każdym wydaniu, bo znacząca część użytkowników używa tylko gotowych paczek, które zbiegają się z Bożym Narodzeniem i wakacjami letnimi, kiedy osobiście mam lepsze rzeczy do roboty po już-stresującym sprincie, jakim jest miesiąc poprzedzający wydanie.

Od 2021 roku, gdy aktualizuję swoje repozytorium Git o ostatnie zmiany z Darktable master, zawsze zadaję sobie pytanie, co zepsuli tym razem. Psujemy szybciej, niż naprawiamy, a przez większość czasu poprawki psują coś innego. Jedynymi użytkownikami uważającymi Darktable za stabilne są w istocie ci, którzy używają go w bardzo podstawowy sposób, co jest ironiczne jak na aplikację, której głównym atutem jest bycie zaawansowaną.

A do tego serwuje mi się fakt, że to darmowa praca, jakby to była wymówka. Ale to w istocie okoliczność obciążająca: dlaczego narzucamy sobie takie warunki pracy, jeśli nie jest to nawet opłacalne??? Poza tym, że ta darmowa praca funduje mi jedno powydaniowe wypalenie rocznie, kosztuje coraz więcej w utrzymaniu, a utrzymanie jest coraz bardziej godne pogardy do wykonania. To nie darmowa praca, to gorzej: to praca, która kosztuje, nie płacąc.

Tak czy inaczej, pracę zapewniają ludzie mający ograniczony czas i energię. Jeśli zasób jest ograniczony, dość pierdolenia: jesteśmy w tych samych ograniczeniach rentowności co przedsiębiorstwo, minus składki społeczne, tyle że naszą walutą wymiany jest czas i jest on nie do zwrócenia. Bez zarządzania priorytetami zostaniemy prześcignięci przez dług techniczny, którego nie będziemy mieli zasobów utrzymać.

Na co czekamy, żeby być szczęśliwymi?

Fakt, że Darktable jest walcem drogowym w trybie rozpędzonym i bez kierowcy, generującym rosnącą ilość pracy, to zły znak dla 15-letniego projektu. Normalnie dojrzały projekt zwalnia, bo jest wystarczająco kompletny, by być używalnym, i bo ludzie nad nim pracujący znaleźli swoją prędkość przelotową i wydajne metody pracy.

Jestem na pełen etat od 2018 roku, za miesięczny dochód między 800 a 900 €, i to niedopowiedzenie stwierdzić, że jest to źle płatne jak na znoszenie katastrofalnych konsekwencji zdezorganizowanych amatorów próbujących się bawić kosztem jakości produktu końcowego i jego użyteczności dla komputerowych mugoli. Zresztą mugolami też się gardzi, dla zasady.

Jeśli przez miesiąc siedzę pod kamieniem, by opracować percepcyjną przestrzeń barwną, to gdy stamtąd wychodzę, odkrywam nowy labiryntowy system naruszający jeszcze bardziej paradygmat widok-model-kontroler, i słyszę, że przychodzę za późno, by się mu sprzeciwić. Jeśli biorę 3 tygodnie urlopu w sierpniu, to po to, by odkryć, że opiekun obszedł (kolejny raz) moją recenzję zmiany matematycznej w wyżej wspomnianej przestrzeni barwnej, która wymaga, by usiąść spokojnie i pomyśleć — a wszystko to dlatego, że… musieliśmy działać szybko? Z powodu jakiego dokładnie nagłego przypadku?

Powiadomienie prawdopodobnie dostałem gdzieś pośród 2234 e-maili, które Github wysłał mi między styczniem a sierpniem 2022 roku (w 2021 roku było to 4044), nie wspominając o użytkownikach, którzy pingują mnie wszędzie, na YouTube, Reddit, Matrix, Github, Telegram, bezpośrednio przez e-maile, a wcześniej na pixls.us (693 e-maile w 2022, 948 w 2021). A wszystko to od ludzi kompletnie oderwanych od rzeczywistości, którzy nie zdają sobie sprawy, że robię to przez cały tydzień, że fotografia może być ich hobby, ale jest moją pracą, i że po prostu doceniłbym, gdyby ludzie dali mi spokój w weekendy i wakacje. Możesz się domyślić, że najbardziej dokuczliwi nie są tymi, którzy finansowo wspierają moją pracę. Ludzie szanują pracę tylko wtedy, gdy wystawiono im za nią wysoki rachunek.

Nie mam czasu być badaczem, projektantem, w dodatku sekretarką, jednocześnie zajmując się technicznym niańczeniem zespołu Gastonów Lagaffe’ów, których trzeba zarazem szkolić i pilnować, bo nie są w stanie:

  1. sporządzić planu rozwoju z listą priorytetów nowych funkcji do opracowania,
  2. dostarczyć specyfikacji potrzeb i problemów, z prawdziwym przypadkiem użycia, zanim rzucą się na swój edytor kodu i zrobią cokolwiek, by opracować nową funkcję w poszukiwaniu problemu do rozwiązania.
  3. oszacować koszt utrzymania zmiany przed wymyśleniem bionicznej pompy do bekonu z odwróconą osmozą, która działa tylko w dni parzyste, jeśli Jowisz jest w przeciwfazie z Saturnem,
  4. ograniczyć wydatki i uciąć straty, gdy wpakują się w projektowe uliczki jednokierunkowe wprowadzające regresje gorsze niż hipotetyczne oczekiwane korzyści,
  5. wziąć na siebie i opóźnić wydanie, jeśli kod ewidentnie nie jest gotowy (albo niczego nie zrozumiałem i akcjonariusze zażądają naszych głów, jeśli wydamy z opóźnieniem???).

Zarządzanie (czy kierowanie zespołem) to narzut, który kosztuje trochę pracy, ale zespół Darktable osiągnął skalę, przy której brak zarządzania kosztuje w istocie więcej pracy, zwłaszcza że żaden z założycieli projektu nie jest już w zespole, a początkowe schematy projektowe trzeba za każdym razem odtwarzać metodą inżynierii wstecznej za pomocą grep w kodzie, gdy coś wymaga zmiany. To było do wytrzymania w zredukowanym zespole, w którym wszyscy się znali, ale Darktable stało się projektem o dużym ruchu podczas lockdownów covidowych i ten sposób pracy nie jest zrównoważony przy obecnym personelu.

Po prostu idź i zobacz kod! Porównaj gałąź darktable-2.6.x z darktable-4.2.x, plik po pliku, i baw się dobrze!

Wszystko, co dotąd usłyszałem, to wyświechtane zdania w stylu „to jak wiele innych projektów open source" i „nic nie możemy na to poradzić". Ludzie boją się ilości pracy, jaką jest fork (dostawałem e-maile próbujące przekonać mnie, że to dzieli produktywność), nie zdając sobie sprawy z ilości zasobów obecnie marnowanych przez projekt Darktable i permanentnego stresu wynikającego z konieczności opierania swojej pracy na niestabilnej bazie kodu trzęsionej cały czas. Jak dotąd Ansel kosztował mnie mniej zmęczenia i rozwiązałem znaczną liczbę problemów, wśród których niektóre były zgłaszane od 2016 roku bez oznak zainteresowania ze strony cholernej „społeczności".

Poza tym Ansel dostarcza automatycznie budowane paczki nocne dla Linuksa (.AppImage) i Windowsa (.exe), tak by umożliwić prawdziwe uogólnione testy, w tym przez osoby niepotrafiące samodzielnie zbudować oprogramowania. Prosiłem o to jeszcze w 2019 roku , ale najwyraźniej maniacy mają lepsze rzeczy do roboty i sam musiałem zainwestować 70 h, by to zrealizować. Operacja jest już sukcesem i pozwoliła naprawić w ciągu kilku dni błędy Windowsa, których wykrycie w Darktable zajęłoby tygodnie. (A Darktable zgarnęło 3 tygodnie później mój skrypt budujący AppImage bez należytego uznania autorstwa, ale to szczegół).

Skoro mowa o produktywności, przypomnijmy, że stół podświetlany został przepisany niemal w całości dwukrotnie od 2018 roku (a ostatnia wersja nie jest ani lepsza, ani szybsza), a wielka zmiana filtrów kolekcji wprowadzona w kwietniu 2022  nadpisała inną podobną zmianę (ale używającą tylko 600 wierszy zamiast 6000) wprowadzoną w lutym 2022  (wersja lutowa jest tą w Ansel). Nie możemy przyzwoicie wypowiadać słowa „produktywność", gdy praca jednego współtwórcy dosłownie wymazuje wcześniejszą pracę innego w ciągu jednego miesiąca, po prostu z braku zarządzania projektem. To się nazywa deptaniem sobie nawzajem po palcach.

Więc co robimy, żeby rozwiązać problem? Cierpimy w milczeniu? Żyjemy w zaprzeczeniu? Wciąż naprawiamy rzeczy, które ktoś inny zepsuje w przyszłym roku, gdy nie będziemy patrzeć? Wciąż wmawiamy mugolowi, na łamach forów fotograficznych, że open source jest równie dobre jak oprogramowanie własnościowe, trzymając jako jokera fakt, że jest darmowe, więc nie masz prawa narzekać? Czyż nie jest to trochę zbyt łatwe i nieuczciwe, ta podwójna mowa?

Czy nie chciałbyś przestać podawać nawyki za doświadczenie i mylić fatalizmu z mądrością, a raczej zająć się problemem u jego rdzenia? Nie sądzisz, że ty i ja zasługujemy na coś lepszego niż oprogramowanie zaprojektowane przez amatorów, których jedynym talentem jest posiadanie wolnego czasu i którzy mogą sobie pozwolić na darmową pracę, odkąd przeszli do zarządzania, a dzieci wyfrunęły na studia?

Albo myliłem się od samego początku, i open source polega na dawaniu przekombinowanych narzędzi maniakom, którzy tak naprawdę ich nie potrzebują, przy jednoczesnym próbowaniu przekonać resztę świata, że open source to nie hiper-nisza dla deweloperów?

Cztery lata pracy, żeby tu dojść

Po 4 latach pracy nad Darktable na pełen etat za 70% płacy minimalnej i 2 latach znoszenia chronicznego niezadowolenia z plamienia własnego nazwiska poprzez wnoszenie wkładu w gówno, sforkowałem Ansel i nie wrócę.

W 4 lata wniosłem do tego oprogramowania coś, czego dotkliwie brakowało: ujednolicony przepływ pracy, oparty na zestawie modułów zaprojektowanych do wspólnego działania, ale działających każdy na odrębnym aspekcie, podczas gdy moduły Darktable były raczej zbiorem rozmaitych wtyczek. Mowa o:

  • filmic,
  • korektor tonalny (tone equalizer),
  • moduł fizycznie dokładnych rozmyć,
  • obie wersje balansu kolorów,
  • kalibracja kolorów, w tym interfejs do profilowania za pomocą wzorników kolorów bezpośrednio w ciemni oraz balansowanie bieli z użyciem standardów CIE,
  • moduł negadoctor do odwracania negatywów filmowych w oparciu o Kodak Cineon,
  • moduł rozpraszania i wyostrzania (diffuse and sharpen) do dodawania i usuwania rozmycia w oparciu o dyfuzję termiczną,
  • rekonstrukcja świateł metodą guided laplacian.

Opracowałem także bardziej fundamentalne narzędzia dostarczające podstaw dla poprzednich modułów:

  • solver anizotropowych równań różniczkowych cząstkowych 4. rzędu w przestrzeni falek dla rozpraszania i wyostrzania,
  • adaptacja poprzedniego jako guided laplacian do rekonstrukcji sygnału RGB przez propagację gradientów,
  • percepcyjny model wyglądu koloru uwzględniający efekt Helmholtza-Kohlrauscha w obliczaniu nasycenia, by ograniczyć efekt „fluo", który zwykle towarzyszy intensywnym ustawieniom nasycenia, w balansie kolorów,
  • pomoc teoretyczna w opracowaniu ekspozycyjnie niezmienniczego filtru sterowanego (EIGF), w korektorze tonalnym,
  • solver liniowych równań wektorowych metodą Choleskiego,
  • różne metody interpolacji rzędu 2, 3 oraz oparte na funkcjach radialnych.

W interfejsie graficznym w szczególności zrobiłem:

  • refaktoryzację deklaracji stylów, usuwając stylizowanie z kodu C, by odwzorować je w arkuszu stylów CSS, co pozwala mieć wiele motywów interfejsu, w tym zdefiniowanych przez użytkownika,
  • wprowadzenie trybów podglądu focus-peaking  oraz trybu oceny kolorów ISO 12 646 ,
  • wprowadzenie słownictwa kolorów w globalnym próbniku kolorów , pozwalającego nazwać pobrany kolor na podstawie jego współrzędnych chromatyczności, z myślą o fotografach z zaburzeniami widzenia barw.

Po tym napisałem dziesiątki stron dokumentacji w 2 językach, opublikowałem artykuły i dziesiątki godzin wideo na YouTube, by pokazać, jak używać modułów, w jakim kontekście i dla jakiej korzyści, w tym szybkie edycje z użyciem zaledwie 3 do 5 modułów, by przetworzyć 75 do 80% zdjęć, niezależnie od ich rozpiętości tonalnej. W świecie open source, może z wyjątkiem projektów wspieranych przez fundacje (jak Krita i Blender), taki poziom wsparcia i dokumentacji po prostu nie istnieje, i nie zajmują się tą pracą sami deweloperzy.

Mimo tego wszystkiego nigdy nie miałem więcej niż 240 darczyńców, w porównaniu z około 1800 unikalnych respondentów, którzy wzięli udział w ankietach Darktable z 2020 i 2022 roku  i którzy deklarują wydawanie między 500 a 1000 €/rok  na fotografię.

Nie będę patrzył, jak niszczą użyteczność tego oprogramowania, próbując wmówić sobie, że to postęp i nic nie da się z tym zrobić. Zamiast postępu jest to urojona wizja postępu przez grupkę pięćdziesięciokilkuletnich dyletantów. Od 2 lat cierpliwie milczę, próbując być miłym, ale patrząc na degradację podstawowych funkcji, komplikowanych, by zaspokoić kaprysy szalonych programistów, powinienem był być godny pogardy wcześniej. Granie w miłego niczego nie rozwiązało, bo trend nie tylko trwał, ale przyspieszył, i nie będzie żadnego otrzeźwienia przed punktem bez powrotu. Nie możemy oczekiwać od tych, którzy stworzyli problemy, że będą tymi, którzy je rozwiążą.

Więc jeśli mam pracować dla „społeczności", której w większości chodzi o wolny od abonamentu aspekt oprogramowania, i która zdecydowała, że moja praca nie jest warta płacy minimalnej, cóż, zrobię to na moich warunkach i według moich standardów.

Pod względem funkcji Darktable ma już ich za dużo i musimy okrawać. Używam go od 10 lat i jest już wypchane źle zaprojektowanymi rzeczami. Wyzwaniem jest teraz sprytne przedstawienie funkcji i naprawienie irytujących błędów zanim ci idioci wprowadzą nowe, albo wręcz naprawią je na swój szczególny sposób: zamiatając kurz pod dywan. Pamiętając, że potok (pipeline) Darktable ma 15 lat i nie możemy go zoptymalizować dużo bardziej, biorąc pod uwagę, że był już mocno torturowany, by uniknąć pełnego przepisania (a pełne przepisanie nie oferuje żadnej korzyści, jeśli musimy zachować Gtk jako backend graficzny, skoro jest on głównym wąskim gardłem wydajności).

Rozwiązania potrzebne Darktable zakładają usuwanie kodu i opcji, a nie dodawanie coraz więcej. Solidność jest w tej cenie. Zespół Darktable robi dokładnie odwrotnie, nie ucząc się na swoich błędach.

Za pomocą Ansel chcę mieć sposób, by dokończyć tę pracę spokojnie, aby użytkownicy Linuksa mieli niezawodne, spójne i wydajne narzędzie do swojej artystycznej fotografii. Zanim przesiądę się na Vkdt , bo obecny projekt pokazuje swoje granice.


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

  1.  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. ↩︎

  2. 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 ↩︎

  3.  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' ↩︎

  4. git checkout release-3.0.0 & cloc $(git ls-files -- 'src/views' 'src/gui' 'src/bauhaus' 'src/dtgtk' 'src/libs') ↩︎