Reference · Glosariusz

Glosariusz: codebase-design (deep modules)

Słownictwo skilla codebase-design i wszystkiego, co z niego korzysta (improve-codebase-architecture). Terminy używane dokładnie w tym znaczeniu — nie zamiennie z "komponent", "serwis", "API" czy "granica". Spójność języka jest tu celem samym w sobie.

Podstawowe pojęcia

Module (moduł)

Cokolwiek z interfejsem i implementacją. Celowo niezależne od skali — funkcja, klasa, pakiet, albo warstwa przecinająca cały system. Unikaj: unit, component, service.

Interface (interfejs)

Wszystko, co musi wiedzieć wywołujący, żeby poprawnie użyć modułu: sygnatura typów, ale też niezmienniki, ograniczenia kolejności, tryby błędów, wymagana konfiguracja, charakterystyka wydajności. Unikaj: API, signature (zbyt wąskie — dotyczą tylko powierzchni na poziomie typów).

Implementation (implementacja) vs Adapter

Implementacja to co jest w środku modułu. Adapter to konkretna rzecz spełniająca interfejs przy seamie — opisuje rolę (jaki slot wypełnia), nie treść. Mały adapter może mieć dużą implementację (repo Postgresa); duży adapter może mieć małą implementację (fake w pamięci).

Depth (głębokość)

Dźwignia przy interfejsie: ile zachowania wywołujący (albo test) może uruchomić za jedną jednostkę interfejsu, którego musi się nauczyć. Moduł jest głęboki, gdy duża ilość zachowania siedzi za małym interfejsem; płytki, gdy interfejs jest niemal tak skomplikowany jak implementacja.

Seam (Michael Feathers)

Miejsce, w którym można zmienić zachowanie bez edytowania w tym miejscu — lokalizacja, w której żyje interfejs modułu. Gdzie postawić seam to osobna decyzja projektowa, różna od tego, co za nim stoi. Unikaj: boundary (przeciążone znaczeniem bounded context z DDD).

Leverage (dźwignia) i Locality (lokalność)

Leverage to co dostają wywołujący dzięki głębokości: więcej możliwości za jednostkę nauczonego interfejsu — jedna implementacja spłaca się w N miejscach wywołania i M testach. Locality to co dostają utrzymujący kod: zmiana, błędy, wiedza i weryfikacja koncentrują się w jednym miejscu zamiast rozlewać się po wywołujących. Napraw raz, naprawione wszędzie.

Głęboki kontra płytki moduł

Głęboki moduł = mały interfejs + dużo implementacji:
┌─────────────────────┐
│   Small Interface   │  ← Mało metod, proste parametry
├─────────────────────┤
│  Deep Implementation │  ← Złożona logika ukryta
└─────────────────────┘

Płytki moduł = duży interfejs + mało implementacji (unikaj):
┌─────────────────────────────────┐
│       Large Interface           │  ← Dużo metod, złożone parametry
├─────────────────────────────────┤
│  Thin Implementation            │  ← Tylko przepuszcza dalej
└─────────────────────────────────┘

Cztery zasady

  1. Głębokość jest cechą interfejsu, nie implementacji. Głęboki moduł może być wewnątrz złożony z małych, mockowalnych, wymiennych części — po prostu nie są częścią interfejsu. Moduł może mieć wewnętrzne seamy (prywatne dla własnej implementacji, używane przez własne testy) obok zewnętrznego seamu przy interfejsie.
  2. Test usunięcia (deletion test). Wyobraź sobie usunięcie modułu. Jeśli złożoność znika — był zwykłym przepustem. Jeśli złożoność pojawia się z powrotem u N wywołujących — zarabiał na swoje utrzymanie.
  3. Interfejs jest powierzchnią testową. Wywołujący i testy przekraczają ten sam seam. Jeśli chcesz testować poza interfejsem, moduł prawdopodobnie ma zły kształt.
  4. Jeden adapter to hipotetyczny seam. Dwa adaptery to prawdziwy. Nie wprowadzaj seamu, dopóki coś naprawdę się przy nim nie różni.

Odrzucone ramy pojęciowe

Głębokość jako stosunek linii implementacji do linii interfejsu (Ousterhout) — nagradza rozdmuchiwanie implementacji; tu głębokość = dźwignia. "Interface" to nie słowo kluczowe interface z TypeScript ani same publiczne metody klasy — za wąskie. "Boundary" nie jest tu używane — przeciążone znaczeniem z DDD; mów "seam" albo "interface".

← Lekcja 17: tdd Lekcja 18: codebase-design →