Detekce nepoužívaných funkcí v Go monorepu

Go, Kvalita kódu, Tipy & triky

Detekce nepoužívaných funkcí v Go je jednoduchá, pokud se jedná o unexported funkce. Exportované lze již detekovat pomocí deadcode. Ten ale není dostatečný pro monorepo. A tak vznikl deadmono.

Detekce nepoužívaných funkcí v Go monorepu

Article in English can be found on dev.to/arxeiss/dead-code-detection-in-go-monorepos-with-deadmono-3043

Na projektu máme velké Go monorepo, kde více servis sdíli interní balíčky. Po čase se může jednoduše stát, že exportované funkce se již nikde nepoužívají. A tak jsem hledal způsob, jak je detekovat a poté smazat.

Pro Go existuje několik linterů jako unused v GolangCI nebo unusedfunc v GoPLS. Ty ale dokážou detekovat pouze unexported funkce v rámci balíčku, které se nepoužívají. Go tým vytvořil skvělý nástroj nazvaný deadcode, který umí detekovat veškerý nepoužívaný kód. Jenže deadcode začíná analýzu z jediné main funkce, což je pro monorepo nedostatečné.

Problém s jednoduchým průnikem množin

Zkusil jsem napsat skript, který spustí deadcode pro každou servisu zvlášť. Pak provedu průnik množin a bude hotovo. Jenže výsledek byl, že žádná nepoužívaná funkce neexistuje. A to bylo divné.

Struktura monorepa:

├── services/
│   ├── service-a/     (importuje pkg/cache, pkg/logging)
│   ├── service-b/     (importuje pkg/cache, pkg/logging)
│   └── service-c/     (importuje pouze pkg/logging)
└── pkg/
    ├── cache/
    │   ├── Get()      ✓ Používá service-a
    │   ├── Set()      ✓ Používá service-b
    │   └── Delete()   ✗ dead code
    └── logging/
        └── Info()

Výstup deadcode pro každou servisu:

service-a: Reportuje cache.Set(), cache.Delete() jako nepoužívané
service-b: Reportuje cache.Get(), cache.Delete() jako nepoužívané
service-c: Nereportuje NIC o pkg/cache (vůbec ho neimportuje!)

Průnik těchto 3 množin je PRÁZDNÁ MNOŽINA

Proč? service-c vůbec neimportuje pkg/cache, takže i v reportu vůbec není zmíněn. Výsledný průnik je tedy prázdná množina, i když cache.Delete() skutečně není používána.

Řešení: Průnik pro každý balíček zvlášť

Vytvořil jsem tedy deadmono utilitku, která tento problém řeší pomocí průniku na úrovni balíčků:

  1. Detekuje každou servisu zvlášť a které balíčky importuje
  2. Vytváří průnik výsledků pro každý balíček (pouze pokud jej importuje)
  3. Hlásí skutečně nepoužívané funkce

Instalace

# Instalace deadmono
go install github.com/arxeiss/deadmono/cmd/deadmono@latest

# Instalace požadovaného nástroje deadcode
go install golang.org/x/tools/cmd/deadcode@latest

Použití

deadmono services/authn/main.go services/config/main.go services/healthcheck/main.go

Jak to funguje na výše uvedeném příkladu

Pro pkg/cache (importován service-a, service-b):
  service-a hlásí: cache.Set(), cache.Delete() jako nepoužívané
  service-b hlásí: cache.Get(), cache.Delete() jako nepoužívané
  service-c: IGNOROVÁNO (neimportuje pkg/cache)

  Průnik (service-a ∩ service-b):
    ✓ cache.Delete() - nepoužívané v OBOU servisách, které ho importují

Pro pkg/logging (importován všemi servisami):
  Všechny servisy používají logging.Info()
  Průnik: (prázdný - žádné nepoužívané funkce)

Finální výsledek:
  pkg/cache/cache.go:15:1: unreachable func: Delete

Užitečné flagy

Více Go modulů

Ve výchozím nastavení musí být všechny main funkce ve stejném modulu. Pro analýzu napříč moduly musí být specifikován flag -filter:

deadmono -filter "github.com/myorg/.*" \
  module1/services/api/main.go \
  module2/services/worker/main.go

Přidat komentář

Položky označené * jsou povinné. Email nebude zveřejněn

Buď první, kdo přidá komentář. Zatím zde nic není