Ansel is designed, not hacked. The difference is a process: hacking optimizes for the pleasure of writing code, design optimizes for the problem being solved — and for what the solution will cost to maintain (technical debt ) long after the fun is gone. This page is that process. Pull requests that bypass it will be refused, whatever their technical quality: it is the pact applied to code.

Czym jest projektowanie?

Projektowanie to proces, w którym rozwijasz metodologię, aby dostarczyć techniczne rozwiązanie ludzkiego problemu. Proces projektowania ma na celu zbieżność ku najbardziej odpowiedniemu rozwiązaniu, przy jednoczesnym zwalczaniu naturalnej pokusy rzucenia się na pierwszy lub najwygodniejszy pomysł.

Bez wymagań i projektu programowanie jest sztuką dodawania błędów do pustego pliku tekstowego. — Louis Srygley
  1. Projektowanie zaczyna się od przypadku użycia: zdefiniowanego zadania do wykonania (na zdjęciu), przez zdefiniowanego użytkownika, w zdefiniowanym czasie. Jeśli nie ma przypadku użycia, to nie ma problemu do rozwiązania, więc trzymaj się z dala od swojego edytora kodu.
  2. Projektowanie wymaga znajomości docelowego użytkownika: wykształcenia/wyszkolenia, poziomu rzemiosła/mistrzostwa itd.
  3. Projektowanie wymaga zrozumienia potrzeb: w kontekście Ansela często będzie to wymagać pewnej wiedzy z historii sztuki i fotografii ciemniowej,
  4. Gdy problem i użytkownik są zrozumiani, projektowanie wymaga określenia:
    • oczekiwanych funkcjonalności rozwiązania,
    • zakresu rozwiązania (w którym miejscu cyklu życia obrazu znajduje się to rozwiązanie?),
    • ograniczeń i wymagań rozwiązania (obsługa jakiegoś standardu, umożliwienie przetwarzania n obrazów na jednostkę czasu itd.),
    • serii testów do przeprowadzenia, które potwierdziłyby jakość rozwiązania, aby ograniczyć nieproduktywne opinie, uprzedzenia i subiektywność w procesie walidacji.
  5. Dopiero wtedy mogą rozpocząć się makiety i burza mózgów, a następnie prototypy.

Czym projektowanie nie jest?

Projektowanie nie zajmuje się:

  • niejasnymi wymaganiami niezdefiniowanego, przyszłego lub wyfantazjowanego użytkownika,
  • „byłoby fajnie, gdyby…" (tak właśnie tworzy się niespójne kolekcje wtyczek),
  • tym, co ludzie lubią (dla wszystkiego, co ktoś lubi, znajdziesz kogoś, kto tego nienawidzi),
  • tym, co ludzie myślą, że chcą (często nie jest to to, czego potrzebują),
  • magicznymi technologicznymi hasłami-wytrychami, które „są przyszłością" i jako takie muszą być wpychane wszędzie, niezależnie od ich zasadności czy wykonalności (tak, mówię o AI, NFT, blockchainie itd.)

Czym jest dobre projektowanie

Dobre projektowanie jest:

  • minimalistyczne,
  • solidne,
  • odporne na przyszłość,
  • generyczne i uogólnione,
  • utrzymywalne przy ograniczonych zasobach,
  • oparte na nauce,
  • kompatybilne/interoperacyjne ze standardami branżowymi.

Ponieważ Ansel jest aplikacją opartą na przepływie pracy, dobre projektowanie uwzględnia też przepływ pracy jako całość oraz to, gdzie problem/rozwiązanie się w nim znajduje.

Jak wykonuje się dobre projektowanie?

Aby wspomóc proces projektowania, komunikacja powinna pozostawać zwięzła, skupiona na temacie, a osoby biorące udział w tym procesie powinny zadbać o właściwe zrozumienie teorii i tła technicznego związanego z zakresem problemu/rozwiązania.

Należy podkreślić, że choć projekt jest napędzany oprogramowaniem, nie wszystkie rozwiązania wymagają kodowania. Czasami (często?) wystarczy lepsza edukacja lub lepsza dokumentacja.

Celem zdrowego procesu projektowania jest uniknięcie zbyt wczesnego obciążania rozwiązań czyjąś ulubioną koncepcją projektową/technologią oraz uniknięcie zagubienia się w szczegółach technicznych, a zamiast tego stałe powracanie do podstawowych fundamentów i zasad tego, co robimy: obróbki potencjalnie dużych partii obrazów raw dla wszelkiego rodzaju mediów wyjściowych.

Potwierdza to fakt, że użytkownicy rzadko znają swoje własne potrzeby, a raczej potrzeby, które wyrażają, rzadko są źródłem tego, czego naprawdę chcą. Trudnym zadaniem projektowania jest przebicie się przez gałęzie, by dotrzeć do korzenia, ponieważ rozwiązanie problemu u korzenia zazwyczaj kończy się bardziej eleganckimi, generycznymi i minimalistycznymi rozwiązaniami.

Problemy są pierwsze

The first step of Ansel design process is to open a thread in GitHub Discussions  — since the community forum was retired, this is the venue, imperfect as it is for non-programmers; write in the language you can (code of conduct) and state a problem, per the problem-solving culture.

Taka propozycja funkcji skupi się na problemie do rozwiązania i powstrzyma od proponowania jakiegokolwiek rozwiązania. Problem zostanie zdefiniowany w kategoriach zadań do wykonania w przepływie pracy fotografa lub oczekiwanego wizualnego efektu przetworzonego obrazu, czyli w kategoriach celu końcowego do osiągnięcia, a nie w kategoriach narzędzi czy szczegółów technicznych uważanych za potrzebne. Może to prowadzić do dyskusji, aby dokopać się do korzeni problemu, które zwykle są dobrze ukryte pod tym, co użytkownik uważa za swój problem .

Na tym etapie nie przyjmuje się żadnej propozycji rozwiązania.

Rozwiązania są drugie

Gdy definicja i zakres problemu zostaną uzgodnione między osobami zaangażowanymi w dyskusję, można proponować rozwiązania. Może być potrzebna dalsza dyskusja, aby ocenić wady i zalety każdego rozwiązania, prowadząca do przyjęcia najlepszego rozwiązania z zasady. Rozwiązania są definiowane przez swoje funkcjonalności (czyli to, co powinny robić), a nie przez swoją technologię czy środki (jak powinny to robić).

Na tym etapie nie przyjmuje się żadnej propozycji prototypu.

Discussions close. When the same arguments start repeating, the decision is made with what is known, recorded with its reasons, and the thread is closed — a decision debated forever is a decision made by fatigue. The counterpart of closing is reversibility: a decision stands until real use proves it wrong, and evidence reopens what repetition cannot. If validation later shows the adopted solution fails its own acceptance criteria, it goes back to this stage — the sunk cost  of the prototype buys it nothing.

Adopted solutions will lead to a new issue getting triaged in the project management Kanban board .

Mogą one być uwarunkowane zbadaniem aspektów teoretycznych i technicznych w celu oceny ich wykonalności, w którym to przypadku zostaną poddane selekcji do kolumny To research tablicy Kanban. Wyniki badań będą dodawane do pierwotnego zgłoszenia, dopóki wykonalność rozwiązania nie zostanie udowodniona. Gdy to nastąpi, zgłoszenie zostanie przeniesione do kolumny „To do" tablicy Kanban.

Przyjęte rozwiązania mogą zostać poddane selekcji bezpośrednio do kolumny To do, jeśli wymagają jedynie dobrze znanych narzędzi i technologii.

Idealnie punkty do przetestowania oraz procedura testowa walidująca prototyp powinny zostać spisane jeszcze przed posiadaniem działającego prototypu. Co najmniej testy powinny zapewnić, że nie wystąpiła regresja w powiązanych funkcjach i narzędziach.

Prototypy są trzecie

Only the issues triaged in the “To do” column of the project management Kanban board  will be worked on, by myself or by anyone willing to tackle them.

Prototyp rozwiązania zostanie zaproponowany w pull requeście gałęzi tematycznej powiązanej z pierwotnym zgłoszeniem. Gałęzie tematyczne muszą być rebasowane na gałęzi master, np. git rebase ustream master lub, jeśli aktualizujesz swoją gałąź lokalnie o nowe commity z mastera, wykonaj git pull upstream master --rebase albo globalnie skonfiguruj git , aby pobierał przez rebase zamiast merge. Zapewnia to utrzymanie czystej historii Twojej gałęzi przy minimalnym wysiłku i utrzymuje czystą również historię master, gdy Twój PR zostanie scalony.

A prototype is judged on three questions, in order: does it fit the specifications of the adopted solution; does it fit the code quality standards; and what will it cost to maintain — what must be opened to repair it, how far it reaches into the rest of the application. A feature that cannot be enclosed will be refused even if it works, even though it is already written: acceptance is where maintainability is decided, not repair time. When the prototype passes all three, it gets approved and automatically triaged to the “To test/validating” column of the project management Kanban board .

Walidacja jest czwarta

Zatwierdzone pull requesty będą scalane wcześnie w gałęzi candidate lub dev do testów, w zależności od tego, czy mogą zepsuć historie edycji obrazów (poprzez dodanie nowych parametrów modułów lub zmianę schematu bazy danych). Ta gałąź będzie zawsze gałęzią master ze wszystkimi pull requestami oczekującymi na walidację na wierzchu. Ma to pomóc w testowaniu osobom, które niekoniecznie są zaznajomione z ręcznym scalaniem gałęzi git. W przeciwieństwie do gałęzi dev, candidate nie powinna psuć Twoich edycji.

If no bug or breakage is reported after some time and the prototype fulfils its initial purpose correctly, it will get merged in master and the related issue will be closed and moved to the “Done” column of the project management Kanban board .

Jeśli prototyp okaże się niezadowalający, może zostać odrzucony i trzeba będzie opracować kolejny.

Wskazówki od doświadczonego projektanta

Nie wszystkie problemy oprogramowania są problemami kodowania

Wiele problemów nie wymaga więcej narzędzi (ani zabawek) ani więcej kodu. Więcej kodu i tak jest zawsze złe i należy go unikać, kiedy tylko możliwe. Bardzo często problemem użytkownika jest to, że nie widzą, jak nagiąć istniejące funkcje, by zaspokoić swoje potrzeby. Rozwiązuje się to edukacją, czyli lepszą dokumentacją i większą liczbą samouczków, a czasami lepszym interfejsem użytkownika.

Słuchaj, ale nie słuchaj użytkowników

Użytkownicy wyrażają to, czego chcą i co lubią, nigdy tego, czego potrzebują. I nie musisz ich słuchać, żeby wiedzieć, co to będzie:

  1. będą chcieć tego samego co ich sąsiad właśnie dostał,
  2. będą lubić to, do czego są przyzwyczajeni.

A potem, dla wszystkiego, co jeden lubi, znajdziesz innego, który tego nie lubi. Tak więc darktable’owy sposób rozwiązywania konfliktu polega na tym, by konfliktu nie rozwiązywać, lecz dać każdemu opcję, tryb, preferencję pozwalającą włączyć tę konkretną rzecz, którą lubi, tak jak ją lubi. Oznacza to więcej case w Twoim switch, więcej zagnieżdżonych if, więcej ścieżek kodu, które będziesz musiał teraz testować, debugować i utrzymywać w przyszłości, a następnie więcej preferencji przesłaniających inne w oknie preferencji. Zanim się obejrzysz, kod jest guzem, którego nikt już nie rozumie, a jego naprawianie tylko go komplikuje.

Gdy podrapiesz pod powierzchnią, odkryjesz, że to, czego ludzie naprawdę potrzebują, jest znacznie bliższe potrzebom innych ludzi niż to, co mówią, że chcą. Możesz więc pogodzić potrzeby znacznie łatwiej niż pragnienia i bez kompromisów. Ale wtedy musisz prześledzić korzenne potrzeby leżące pod wolą, a to wymaga umiejętności abstrakcyjnego myślenia i psychologii.

The GUI cannot be designed apart from the backend

Everybody who only sees, focuses and cares about the UI is designing blind. If your GUI is complicated, it means a lot more than just a “complicated GUI” : it means that the complexity of your backend has reached your frontend. I have found the hard way that GUI complexity is never separate, and can’t be solved separately, from backend complexity and overall application architecture. GUI is not parallel to backend architecture, it’s the termination of it.

Problemem projektantów interfejsów jest to, że zazwyczaj nie kodują, a jeśli już, to są słabi w programowaniu niskopoziomowym i architekturze oprogramowania. Skupiają się więc na tej odrobinie, którą widzą i rozumieją (typowy efekt latarni ) i produkują jedynie niewykonalne projekty, które kolidują z tym, czego oprogramowanie faktycznie potrzebuje do działania. Ponieważ ten interfejs graficzny tylko łączy dane wejściowe użytkownika z zapleczem, a jeśli potrzebujemy aż tylu widżetów, to dlatego, że architektura potrzebuje aż tylu danych wejściowych. Nie da się od tego uciec: aby usunąć widżety, musisz usunąć dane wejściowe, co oznacza, że Twoja architektura będzie musiała najpierw pracować z mniejszą liczbą stopni swobody. Zaczyna się to od uproszczenia zaplecza, co oznacza cuchnący refaktoring zakurzonego starego kodu, którego nikt już nie rozumie.

Problemów interfejsu graficznego nie rozwiązuje się rysunkami i makietami, problemy interfejsu graficznego rozwiązuje się rozwiązując problemy zaplecza. Ale wtedy potrzebujesz ludzi, którzy rozumieją oba poziomy, a oni mogą być dla Ciebie zbyt drodzy.

Zadawaj sobie 36 razy dziennie pytanie, jaki problem próbowałeś rozwiązać

Bardzo łatwo zgubić się w szczegółach technicznych, programując w języku niskopoziomowym i walcząc z zewnętrznymi bibliotekami czy API, ale czasami rozwiązanie jest proste i eleganckie, a Ty dałeś się ponieść zbyt daleko we wskaźniki i blokady wątków. Zawsze wracaj do początkowego problemu, który masz przed sobą — to Twoja lina ratunkowa do prostoty.

Jaki jest problem? Kto się z nim mierzy? Kiedy? Jak często? Robiąc co?

Najlepsza droga to najprostsza droga do Twojego rozwiązania: proste technologie, mało kodu, niewiele warstw.

Udokumentuj swój kiepski projekt

Wiele razy całkowicie przerabiałem projekt podczas jego dokumentowania, ponieważ to właśnie wtedy, gdy próbujesz go wyjaśnić, uświadamiasz sobie, że jest zbyt skomplikowany, by go wyjaśnić, co oznacza, że jest zbyt skomplikowany, by go zrozumieć. Jeśli nie potrafisz wyjaśnić swojego projektu w kilku akapitach albo Twoja dokumentacja ma zbyt wiele „jeśli to, to tamto", zazwyczaj są dwa powody:

  1. Twój interfejs graficzny nie eksponuje istotnych informacji tam, gdzie użytkownik ich potrzebuje, więc musisz linkować połowę dokumentacji w swoim wyjaśnieniu, aby przekierować użytkowników do wszystkiego, co muszą wiedzieć lub sprawdzić przed użyciem tej jednej rzeczy, którą dokumentowałeś. Rozwiązaniem jest przywrócenie istotnych informacji tam, gdzie są potrzebne.
  2. Twój interfejs graficzny ma zbyt wiele tacek, zwijanych elementów, zachowań kontekstowych, przypadków użycia lub ukrytych preferencji, a pokrycie wszystkich przypadków zmusza Cię do napisania powieści. Rozwiązaniem jest zlinearyzowanie przepływu pracy, może usunięcie opcji lub podzielenie funkcji.

Interfejs graficzny to sposób, w jaki użytkownicy kontrolują zaplecze, ale to również miejsce, gdzie dowiadują się o istniejących funkcjach i o tym, co robią. Dokumentacja powinna dostarczać kontekstu, wytycznych i odniesień dotyczących tego, jak robimy różne rzeczy, ale interfejs graficzny powinien sam wyjaśniać, co robi.

Oczywiście istnieje tu oczywiste ograniczenie: w aplikacji fotograficznej użytkownicy muszą rozumieć fotografię i jej język, który obejmuje takie rzeczy jak zakres dynamiczny, gamut barwny, mapowanie tonalne itd. Interfejs graficzny powinien być samoobjaśniający się w kwestii tego, jak ma być używany, a nie usuwać potrzeby nauczenia się rzemiosła (co powinno być zrobione i jak).

Projektowanie jest procesem iteracyjnym

Aplikacja to wirtualny świat, w którym jedna mała zmiana może na nowo uporządkować to, jak reszta ekosystemu dostosowuje się wokół niej. Dlatego każda zmiana projektowa może wywołać potrzebę zmiany innych rzeczy wokół (refaktoryzacji narzędzi, przesunięcia widżetów, przycięcia funkcji). Co następnie może wywołać potrzebę ponownego skorygowania początkowej zmiany. To proces krok po kroku, w którym głupotą jest choćby próbować zrobić wszystko idealnie na każdym kroku; liczy się to, by każdy krok ulepszał środowisko względem poprzedniego.

Czasami (prze)projektowanie nie może być zrobione małymi krokami, lecz dużymi skokami: to wtedy, gdy przerabiasz architekturę. Te skoki popsują wiele rzeczy wokół siebie, co jest w porządku, jeśli nowsza architektura jest ogólnie prostsza i solidniejsza, oraz jeśli dasz jej trochę czasu na dojście do siebie, zanim znów sięgniesz po młot. Ale stworzy to stan przejściowy, w którym nowy projekt będzie wydawał się gorszy od poprzedniego. Mówi nam to, że to, jak projekt jest postrzegany, nie jest prawidłowym kryterium: jakość projektu należy oceniać względem jego celów i ewaluować za pomocą obiektywnych miar, a nie odczuć i szybkich testów.

A czasami niektóre kroki to błędy i należy je cofnąć. Błąd utopionych kosztów  nie powinien być używany do uzasadniania, że jakieś przeprojektowanie należy zachować, bo jego wykonanie kosztowało wiele pracy. Należy oczekiwać, że nie wszystkie badania i rozwój trafiają do produkcji.

Sesje w stylu „uderz kreta" oznaczają, że Twoja architektura wyczerpała swoje możliwości

Czy to ciągle tworzysz nowe błędy podczas naprawiania starych, czy ciągle tworzysz przypadki brzegowe rozszerzając jakąś funkcję, wszystko to wskazuje w tym samym kierunku: Twojej architektury nie da się już bardziej nagiąć, ponieważ przerosła swoje wymagania projektowe. Może być tak, że zaplecze stało się zbyt zawiłe, albo tak, że istniejąca architektura naprawdę nie była zaplanowana do tego, co próbujesz nią osiągnąć, ale w obu przypadkach będziesz musiał przerobić architekturę i przestać kombinować na miejscu. W przeciwnym razie tylko dokładasz dług techniczny.

Ale wtedy koszt rozwoju zmienia skalę i ten sobotnio-popołudniowy projekt może stać się projektem na cały miesiąc.

Najlepsze praktyki to wytyczne, a nie reguły

Najlepsze praktyki pomagają wyrobić zdrowe nawyki i pisać czysty kod, chyba że nie rozumiesz problemu, który próbowały rozwiązać, i używasz ich poza ich zakresem ważności. W takim przypadku stają się kultem cargo: próbą naśladowania skutków w nadziei, że magicznie naprawi to również przyczyny.

Pierwsze, co przychodzi na myśl, to ponowne wykorzystanie/współdzielenie kodu. Jeśli ponowne wykorzystanie tego samego kodu dla (pozornie podobnych) funkcji prowadzi do zbyt wielu wewnętrznych rozgałęzień (zagnieżdżonych if, switch/case itd.), aby obsłużyć wszystkie możliwe ścieżki, to co zyskujesz na objętości kodu, tracisz na złożoności cyklomatycznej, a przy okazji Twoje funkcje nie są tak podobne, jak myślałeś.

Ponadto duplikowanie kodu może być punktem wyjścia do lokalnej optymalizacji duplikatu później: gdy masz przed sobą całą procedurę, możesz dostrzec kroki, które można zbuforować lub sfaktoryzować. Podczas gdy jeśli procedura to tylko nieprzejrzyste, wysokopoziomowe, wielokrotnego użytku metody API, to tracisz zdolność dostrzegania i usuwania zbędnych obliczeń. Istnieje więc zasada ponownego wykorzystania/współdzielenia danych (czyli buforowania obliczonych danych, które zostaną użyte później bez zmian, aby oszczędzić cykle procesora), którą ponowne wykorzystanie/współdzielenie kodu może uczynić niemożliwą, ponieważ zaciemnia i abstrahuje cykl życia danych.

Staje się to krytyczne w pętlach pikselowych: chcesz zwinąć wszystkie operacje pikselowe do tej samej pętli, aby zapłacić cenę wejścia/wyjścia pamięci tylko raz. Oznacza to, że będziesz musiał ponownie zaimplementować tę samą korektę afiniczną ($y = a * x + b$) w każdej używającej jej pętli, zamiast mieć metodę wielokrotnego użytku, która robi dokładnie to w swojej własnej pętli.

Nie usztywniaj się

Jeśli czujesz się przytłoczony jakimiś tajemniczymi i losowymi błędami, które ciągle się pojawiają i których nie potrafisz zrozumieć, nie walcz dalej na oślep, tylko zrób krok wstecz. Następnie wyposaż kod w pomocnicze narzędzia debugujące lub wyższego poziomu menedżery, które śledzą stany wewnętrzne i dają Ci mapę wartości danych oprogramowania w każdym istotnym punkcie jego cyklu życia. Jest to szczególnie krytyczne w konfiguracjach asynchronicznych, gdzie kilka wątków tworzy, uzyskuje dostęp lub oblicza rzeczy równolegle na różnych osiach czasu, a rzeczywista kolejność sekwencji zależy od kontekstu wykonawczego.


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