Przejdź do treści

INW019 diagram-missing-dependency

Co robi

Opt-in, a po włączeniu ostrzeżenie. Czyta oznaczone diagramy w Mermaid i PlantUML z plików, które wymienia klucz diagrams, tak jak INW017 i INW018, i porównuje je z importami w kodzie. Zgłasza import z jednej warstwy albo jednego kontekstu, który diagram rysuje, do innego, który też rysuje, gdy diagram nie ma dla niego strzałki. Diagramy sprawdza uruchomienie dla całego projektu; sprawdzenie wybranych ścieżek i hook po każdej edycji ich nie sprawdzają.

Co liczy się jako narysowane:

  • W diagramie %% inwards: layers ścieżka ciągłych strzałek od warstwy, która importuje, do warstwy importowanej. api --> application --> domain rysuje też import z api do domain, bo warstwa może importować każdą warstwę pod sobą, a diagram zwykle rysuje tylko kolejne kroki.
  • W diagramie %% inwards: contexts ciągła strzałka z kontekstu, który importuje, do kontekstu importowanego, z dowolnego węzła wewnątrz pierwszego do dowolnego węzła wewnątrz drugiego. depends-on nie jest przechodnie, więc diagram też nie.

Każda brakująca para jest zgłaszana raz na diagram, w linii, w której warstwa albo kontekst, który importuje, jest zapisany po raz pierwszy, a komunikat podaje pierwszy import, który za nią stoi: plik, linię i to, co importuje.

Pomijane: warstwa albo kontekst, którego diagram nie rysuje albo rysuje tylko jako węzeł z klasą external; import wewnątrz jednej warstwy albo jednego kontekstu; import czegoś spoza wszystkich warstw i kontekstów (biblioteki standardowej, pakietu zewnętrznego).

Dlaczego to źle

Diagram, który pokazuje mniej strzałek, niż ma kod, mówi każdemu czytelnikowi i każdemu agentowi, który czyta dokumentację przed pisaniem kodu, że kod jest mniej powiązany, niż jest naprawdę. Kto ufa rysunkowi, przenosi moduł albo usuwa warstwę i psuje import, którego nikt nie narysował. INW018 łapie strzałkę, której zabrania konfiguracja; INW019 łapie strzałkę, o której diagram zapomniał.

[tool.inwards] pozostaje jedynym źródłem prawdy (ADR-045): diagram jest porównywany z kodem, a nigdy nie jest czytany jako konfiguracja.

Przykład

pyproject.toml
[tool.inwards]
diagrams = ["docs/*.mmd"]
layers = [
  { name = "domain", modules = ["shop.domain"] },
  { name = "application", modules = ["shop.application"] },
  { name = "infrastructure", modules = ["shop.infrastructure"] },
]

[tool.inwards.rules]
extend-select = ["INW019"]
shop/domain/order.py
class Order:
    pass
shop/application/place_order.py
from shop.domain.order import Order
shop/infrastructure/db.py
from shop.application.place_order import Order

Diagram, który rysuje powiązanie infrastruktury z aplikacją jako przerywane, jakby nie było importem:

docs/layers.mmd
%% inwards: layers
flowchart LR
  application --> domain
  infrastructure -.-> application
inwards check
docs/layers.mmd:4:3: INW019 (warning) Layer "infrastructure" imports layer "application" in the code (shop/infrastructure/db.py:1 imports shop.application.place_order.Order), and the diagram draws no arrow or path of arrows from "infrastructure" to "application".
  fix: Draw the import in the diagram: "infrastructure --> application".
    1. Arrows point from the importing layer to the imported one, and solid arrows only: a dotted one (-.->) stands for something other than an import.
    2. If the code shouldn't import "application" from "infrastructure", remove the import instead; INW001 reports it when [tool.inwards] forbids it.
  docs: https://sircypkowskyy.github.io/inwards/rules/INW019/

Po poprawce strzałka jest ciągła. Cały projekt przechodzi:

docs/layers.mmd
%% inwards: layers
flowchart LR
  infrastructure --> application --> domain
inwards check

W diagramie kontekstów, gdzie sales importuje shop.catalog.api, strzałka z węzła checkout do węzła api rysuje ten import; bez niej INW019 zgłasza sales w linii 3:

pyproject.toml
[[tool.inwards.contexts]]
name = "sales"
modules = ["shop.sales"]
depends-on = ["catalog"]

[[tool.inwards.contexts]]
name = "catalog"
modules = ["shop.catalog"]
public = ["shop.catalog.api"]
docs/contexts.mmd
%% inwards: contexts
flowchart LR
  subgraph sales["shop.sales"]
    checkout["shop.sales.checkout"]
  end
  subgraph catalog["shop.catalog"]
    api["shop.catalog.api"]:::public
  end
  checkout --> api

Jak działa

Diagram jest czytany raz dla INW017, INW018 i INW019 (czytnik opisuje strona INW017). Importy to te, które uruchomienie dla całego projektu zbiera dla INW004, z dwiema różnicami:

  • Import pod if TYPE_CHECKING: się liczy. INW004 go pomija, bo nigdy się nie wykonuje, więc nie może zamknąć cyklu w czasie działania. INW001, INW002 i INW003 oceniają go jak każdy inny import, a strzałka znaczy „może importować” w ich sensie, więc diagram, który pomija import tylko dla typów, rysuje mniej, niż wynosi zależność kodu.
  • Import trafia do warstwy albo kontekstu, do którego należy jego cel, według tego samego dopasowania najdłuższego prefiksu, którego używają INW001 i INW002, niezależnie od tego, czy cel jest sprawdzanym modułem.

Warstwa albo kontekst jest narysowany, gdy w diagramie jest węzeł, który go nazywa (identyfikatorem albo, dla nazwy, która nie może być identyfikatorem Mermaid, etykietą w cudzysłowie), i nie jest external; w diagramie kontekstów węzeł wewnątrz subgraphu oznacza kontekst tego subgraphu. Strzałki to te ciągłe albo pogrubione w jedną stronę, które czyta INW018; krawędzie przerywane, bez grotu i dwukierunkowe nie rysują importu.

Szkielet importów może wziąć linię import wewnątrz napisu za import. Plik, na którym stoi takie zgłoszenie, jest czytany ponownie pełnym parsowaniem, a diagramy są oceniane jeszcze raz, tak jak INW004 potwierdza cykl, więc zgłoszenie zawsze stoi na prawdziwym imporcie.

Jak naprawić

  1. Narysuj strzałkę, którą podaje poprawka, od węzła, który importuje, do węzła importowanego, jako ciągłą.
  2. W diagramie warstw wystarczy strzałka do warstwy pośredniej, gdy ta ma już ścieżkę do warstwy importowanej.
  3. Jeśli import nie powinien istnieć, usuń go z kodu. Gdy [tool.inwards] go zabrania, zgłasza go też INW001 (warstwy) albo INW002 (konteksty).
  4. Jeśli diagram celowo pomija warstwę, nie rysuj jej albo oznacz jej węzeł jako :::external: INW019 pomija wtedy każdy import do niej i z niej.

Bezpieczeństwo poprawki

Bez automatycznej poprawki (autofix: false). Dodanie strzałki zmienia tylko dokumentację, ale tylko człowiek wie, czy błędny jest rysunek, czy kod.

Konfiguracja

Poziom: ostrzeżenie, więc rozjazd dokumentacji domyślnie nie blokuje Stop gate. Opt-in: włącz ją przez extend-select, zwykle razem z INW017 i INW018, i wymień pliki w diagrams:

pyproject.toml
[tool.inwards]
diagrams = ["README.md", "docs/**/*.md"]

[tool.inwards.rules]
extend-select = ["INW017", "INW018", "INW019"]
severity = { INW019 = "error" }      # optional: block on drift

Reguła nie ma własnych opcji; [tool.inwards.rules.diagram-missing-dependency] przyjmuje tylko modules, które nic nie zmienia, bo diagram nie jest modułem. Zgłoszenia nie da się wyciszyć w linii, ani w diagramie, ani w pliku Pythona: narysuj strzałkę albo oznacz węzeł jako :::external. inwards import-diagram włącza INW017 i INW018, ale nie INW019, bo diagram zaimportowany przed napisaniem kodu nie może rysować importów, których jeszcze nie ma.

Znane ograniczenia

  • Tylko uruchomienia dla całego projektu. Sprawdzenie wybranych ścieżek nie wie, jakie importy wykonuje reszta projektu.
  • Warstwy i konteksty, nie moduły. Strzałka między dwoma węzłami modułów wewnątrz jednego kontekstu nie jest porównywana z importami między tymi modułami.
  • Komunikat podaje jeden import. Para, za którą stoi wiele importów, pokazuje pierwszy według ścieżki pliku i kolejności w źródle; popraw diagram, nie ten jeden plik.
  • Te same ograniczenia co INW017: podzbiory flowchartów Mermaid i diagramów komponentów PlantUML, linia po linii, a globy pomijają ukryte katalogi.

Źródła