W grudniu 2019 roku poprosiłem, aby ktoś zajął się dostarczaniem pakietów AppImage  dla Darktable. Oczywistą korzyścią byłoby umożliwienie wczesnego testowania, przed wydaniem, przez osoby, które nie potrafią samodzielnie zbudować kodu źródłowego, tak by z nadzieją uzyskać wczesne opinie i pomóc w usuwaniu błędów przed wydaniem. Nigdy nie było to priorytetem, co oznacza, że można było mieć pośpiech z naprawą błędów zarówno przed wydaniem, jak i po nim.

Nie żartowałem, gdy mówiłem, że Darktable to fabryka wypalenia zawodowego, zarządzana jak najgorsze start-upy, tyle że nikt nie zrobi na tym exitu i jest to w zasadzie czysta strata dla wszystkich zaangażowanych. Tym bardziej jestem na to zły, że mnóstwo roboczogodzin zmarnowano na zaśmiecanie interfejsu kosmetyką, podczas gdy nocne kompilacje do ogólnego użytku poprawiłyby zarówno jakość oprogramowania, jak i komfort oraz jakość życia jego opiekunów. Te cele najwyraźniej nie miały znaczenia.

Skoro więc nikt nie obsłuży cię lepiej niż twoje własne błędy, musiałem zrobić to sam , i w połowie grudnia 2022 roku Ansel dostał skrypty do nocnych kompilacji AppImage, które Darktable ponownie wykorzystał miesiąc później. Wobec zastępów informatyków „bez umiejętności matematyki pikselowej" krążących wokół szumu wokół Darktable i pytających, jak mogliby pomóc, oczywiście najlepszym możliwym zarządzaniem zasobami było odciągnięcie roboczogodzin jednego z bardzo nielicznych dostępnych gości z głową do matematyki do czysto informatycznego zadania. Zajęło mi to tylko jakieś 50 godzin, ponieważ posiadanie skryptu działającego lokalnie to naprawdę nie to samo, co szeptanie skryptów YAML  do ucha Github Action, a musiałem się tego uczyć w trakcie robienia. Ponieważ zbudowanie Ansela (lub jego niepowodzenie) na instancjach Github Action zajmuje od 20 do 30 minut, twój dzień pracy to długa, urywana sesja niewydajnego wielozadaniowego działania, podczas gdy czekasz, aż zmiany w skrypcie przyniosą zdalny wynik. Nienawidzę programowania i radzę sobie z nim dla matematyki i fizyki, ale tego rodzaju czarna robota przy konfiguracji serwera to naprawdę najgorszy rodzaj programowania, jaki istnieje, o tyle że nie jest ani twórczy, ani stanowiący wyzwanie poznawcze, jest tylko czasochłonnym sposobem obchodzenia ograniczeń projektowych API osób trzecich.

Tak więc ostatecznie Ansel był na wpół zorganizowanym projektem programistycznym, zdolnym objąć bazę użytkowników szerszą niż tylko zatwardziali geekowie, którzy patrzą z góry na każdego, kto nie potrafi doprowadzić GCC i CMake do stanu zdatnego do kompilacji, z dużym przyciskiem „download" na stronie głównej, który zawsze wskazywałby najnowszą kompilację, bez konieczności zgadywania przez użytkowników, która jest najnowsza. Ponieważ program do edycji zdjęć nie powinien odsiewać użytkowników na podstawie ich biegłości komputerowej, niezależnie od tego, czy wywodzi się to ze świata Linuksa (co naprawdę nie jest usprawiedliwieniem), czy też jest jakieś memo od Fundacji wolnego oprogramowania, którego nie dostałem.

Ale ostatnim poważnym problemem był brak dokumentacji dla programistów. W 2018 roku, gdy zacząłem współtworzyć Darktable, miałem ogromne trudności z pojęciem, jak połączone są wewnętrzne mechanizmy, na podstawie samego czytania kodu. 7 lat później, nawet z całym moim doświadczeniem, wciąż muszę przedzierać się przez kod metodą inżynierii wstecznej, używając zarówno grep, jak i intuicji, ponieważ kod nie jest modularny, mało gdzie używa się API, brakuje komentarzy, a Git blame  wiersz po wierszu nie zawsze pozwala cofnąć się w historii zmian projektowych, gdy ktoś zacommitował (bezużyteczne) zmiany w formatowaniu kodu.

Ten brak dokumentacji dla programistów doprowadził do tego, że wiele funkcji zaimplementowano więcej niż raz, w kilku miejscach, przez kilku programistów (a czasem nawet przez tego samego, na przestrzeni lat). W chwili pisania tych słów w Anselu wciąż istnieją 4 lub 5 różnych sposobów zapisu historii edycji zdjęcia z bazy danych biblioteki do pliku pobocznego XMP. Niektóre z tych sposobów są rzadko używane, więc ledwie testowane, a błędy w nich mogłyby pozostać niezauważone przez lata, aż ktoś zgłosi super-osobliwy błąd ukryty w gąszczu opcji. Wtedy dla opiekuna zajmującego się tym jest to głupia gra w rozgryzanie, dlaczego XMP zawodzi tylko w pewnych okolicznościach, co wymaga archeologii w bazie kodu, aby ustalić, że nie używa on głównej metody zapisu XMP. XMP to konkretny i wciąż aktualny przykład, ale było wiele innych. Rozumiesz, o co chodzi.

Od kilku lat zauważałem, że (stare) pliki nagłówkowe C w Darktable miały praktycznie wszędzie docstringi Doxygen . To bardzo leniwy sposób tworzenia dokumentacji: uruchom polecenie doxygen -g <config-file> na katalogu z kodem źródłowym, a Doxygen zbuduje ci statyczną stronę HTML wszystkich API. Następnie możesz zrzucić wszystkie pliki HTML do katalogu serwera WWW i nazwać to swoją dokumentacją deweloperską: jedyną żmudną częścią jest napisanie pliku konfiguracyjnego, co robisz tylko raz. Więc tak zrobiłem: dev.ansel.photos. API, które przepisałem (jak selection.h), są dokumentowane w miarę postępów. Wyżej poziomowe opisy architektury oprogramowania są w drodze.

Wspaniałą rzeczą w Doxygenie jest to, że tworzy on także grafy zależności „modułów" i API. I to właśnie tam można faktycznie zobaczyć, dlaczego nazywam kod Darktable spaghetti. Oto graf zależności accelerators.h, zaplecza skrótów klawiaturowych i MIDI:

image

To pokazuje, że zaplecze akceleratorów absolutnie nie jest modularne: dziedziczy całe oprogramowanie. Więc jakakolwiek zmiana gdzie indziej może mieć nieprzewidziane skutki w tym miejscu, i odwrotnie. Co jest tym bardziej niepokojące, że plik ten ma najwyższą złożoność cyklomatyczną w całym oprogramowaniu , co czyni go najtrudniejszym plikiem do utrzymania (nie śmiem nawet powiedzieć rozszerzania, na tym etapie byłoby to nieodpowiedzialne). Ale jest też całkowicie pokręcony pod względem kierunku dołączeń: skróty to podstawowy blok, który powinien być dołączany (czyli dziedziczony) w miejscach GUI, które będą implementować skróty (widoki ciemni/stołu podświetlanego, suwaki i pola wyboru, moduły). Zamiast tego graf (i nagłówek #include pliku) pokazuje, że skróty dołączają (dziedziczą) także swoje „dzieci", więc mamy podwójną zależność i jest to najgorszy możliwy sposób robienia tego.

To jak budowanie domu: dom powinien być świadomy swoich ścian, ściany powinny być świadome swoich cegieł. Dlaczego ? Ponieważ dom jest zbudowany ze ścian, a ściany są zbudowane z cegieł, więc każdy podkomponent określa naturę i zachowanie całości, dlatego całość musi znać swoje bezpośrednie komponenty. Nie czynisz cegieł świadomymi domu, ponieważ nie zmienią one swojej natury w zależności od całości, do której należą, a byłby to fatalnie wadliwy projekt. Podobnie jak zbędnym poziomem mikrozarządzania jest czynienie domu świadomym cegieł: gdy jest już świadomy swoich ścian, to do ścian należy uwzględnienie zachowania ich cegieł, a być może przekazanie istotnych informacji do domu. Języki obiektowe mają wbudowane (i obowiązkowe) sposoby czystego radzenia sobie z tym wszystkim. Ale to jest C, więc możesz robić, co ci się kurwa podoba. Nie znaczy to, że to dobry pomysł, nie znaczy, że powinieneś. I cóż, to, że C nie jest wewnętrznie językiem obiektowym, nie stanowi przeszkody w używaniu wzorców obiektowych, dziedziczenia i modularności. Chodzi tylko o to, że programista nie dostanie w tym żadnej pomocy od składni języka.

Oto ten sam plik po moim całkowitym przepisaniu zaplecza skrótów:

image

To wyraźnie pokazuje, że nowy moduł obsługi skrótów jest cienką nakładką na natywne skróty Gtk, nie zna reszty oprogramowania i nie obchodzi go ona. Możemy przyjrzeć się także plikowi accelerators.c:

image

Otóż jest tam wiele dołączeń z darktable.h, który używany jest tylko po to, by uzyskać pomocnicze funkcje debugowania (powinny zostać wyrefaktoryzowane poza ten plik), a dołącza też mnóstwo niepotrzebnego badziewia. Tak czy inaczej, accelerators.c jest świadomy jedynie Gtk/Gdk, co oznacza, że jest prawdziwym modułem: jest całkowicie odizolowany od reszty oprogramowania. Widżety GUI, które implementują skróty, będą deklarować swoją ścieżkę skrótu, jak Ansel/Global/Menu/File/Import, swoje domyślne klawisze oraz odniesienie (wskaźnik) do siebie samych. Akcje bez widżetu, które mają skróty, będą deklarować funkcję zwrotną i dane wejściowe do modułu obsługi skrótów, zamiast wskaźnika do widżetu.

Zapisujemy/przywracamy powiązania ścieżek i klawiszy do/z pliku keyboardrc i to wszystko. Na ten moment nie ma okna do definiowania skrótów w GUI, ale jego utworzenie będzie wymagać jedynie wypisania (przejścia w pętli) znanych ścieżek i powiązanych z nimi klawiszy. Gdy moduł obsługi skrótów wychwyci znaną kombinację klawiszy:

  • dla akcji przypisanych do widżetu wyśle sygnał activate Gtk do odpowiedniego widżetu, a ten widżet zrobi swoje poprzez funkcję zwrotną (która jest taka sama jak funkcja zwrotna obsługująca kliknięcia, więc kod jest jednolity między kliknięciami a aktywacją klawiaturą),
  • dla akcji bez widżetu bezpośrednio wywoła zadeklarowaną funkcję zwrotną na zadeklarowanych danych.

W obu przypadkach moduł accelerators komunikuje się z resztą aplikacji poprzez interfejs wymieniający 4 pola danych, w całkowicie nieprzejrzysty sposób. Dopóki interfejs się nie zmienia, zmiany można wprowadzać wszędzie: pozostaną zamknięte w obrębie swojego modułu. Przy odpowiednich umiejętnościach logikę tę można było rozszerzyć na obsługę urządzeń MIDI. Ale to właśnie jest różnica między inżynierią a prototypowaniem dowodów koncepcji, które nigdy nie powinny trafić do produkcji.

Jest to niestety znak firmowy sposobu robienia rzeczy w Darktable: kod GUI jest wszędzie powtykany, nawet w kodzie SQL. I odwrotnie, kod SQL znajduje się w wielu miejscach GUI (stół podświetlany, zarządzanie historią, tagowanie zdjęć itd.). Grafy zależności, wbudowane w Doxygen, są bardzo miłym efektem ubocznym, który pokazuje głębię problemu i sprawia, że spaghetti staje się bardzo oczywiste, ponieważ najwyraźniej problemy nie istnieją, dopóki sam ich rzeczywiście nie zobaczysz.

Ale na tym się nie kończy. Gdy pisałem własny plik konfiguracyjny Doxygen, dostałem błąd: Doxygen zgłosił 2 pliki konfiguracyjne i był zdezorientowany. Okazuje się, że Darktable miał wszystko przygotowane do automatycznego generowania dokumentacji dla programistów od 2010 roku . To, dlaczego nigdy nie wdrożono tego do produkcji ani faktycznie nie umieszczono na jakimś serwerze, to nie tylko błędne priorytety: to zaniedbanie.

Oczywiście nie pomaga fakt, że gość będący właścicielem domeny darktable.org to nie ten sam, który zarządza serwerem, gdzie jest ona faktycznie hostowana. A gość mający rzeczywiste prawa do commitów w witrynie darktable.org to jeszcze kto inny. Najpilniejszą rzeczą do zrobienia, gdy jest się projektem open-source zarabiającym zero dolarów przychodu, jest odtworzenie wszystkich błędów świata korporacji, od presji na wydawanie niedopieczonego gówna z nieodpowiedzialną częstotliwością, po rozproszenie odpowiedzialności między „serwisami", które tak naprawdę nie komunikują się ze sobą (lub z wielkimi opóźnieniami). Ponieważ w oprogramowaniu (czy open-source, czy nie) błędy są po to, by je odtwarzać. A skoro oprogramowanie przejęło władzę nad światem, nawet w miejscach, które go nie potrzebowały, wiele to mówi o świecie, w którym żyjemy.

Jedyną rzeczą, której wciąż naprawdę nie pojmuję, jest jednak: skąd ten pośpiech ? Po co dalej pracować tak źle, skoro nie ma ku temu żadnej presji ani bodźca finansowego ? Open-source jest (mógł być) tym jednym miejscem, gdzie moglibyśmy faktycznie pracować porządnie i poświęcić niezbędny czas na wytwarzanie długoterminowej jakości. A nawet tam krótkowzroczność kapitalizmu przejęła władzę.

Gdyby tylko użytkownicy wiedzieli, w jak głębokim gównie znalazł się ten projekt i jak wszystkie zainwestowane w niego roboczogodziny aktywnie pogarszają sprawę…


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