Parti da un solo file
Un programma Go può vivere interamente in un solo main.go, e i piccoli strumenti dovrebbero farlo. Questo programma completo analizza l'input, fa il suo lavoro e stampa un report, in un solo package:
Quando cresce, dividilo in più file nella stessa cartella e nello stesso package (parse.go, report.go, main.go). I file di un package condividono ogni identificatore, quindi non serve esportare né importare niente. Spesso è tutta la struttura di cui ha bisogno un progetto di qualche migliaio di righe.
Ricorda che un package main su più file va eseguito come package: go run ., non go run main.go. Altrimenti i file vengono compilati da soli e ottieni errori undefined per i nomi definiti negli altri file.
Non esiste una struttura obbligatoria
Go non richiede nessuna struttura di cartelle oltre a "un package per cartella". La guida ufficiale è la pagina Organizing a Go module su go.dev, e descrive alcune forme, non delle regole.
Il repository GitHub golang-standards/project-layout è molto copiato e molto spesso scambiato per uno standard. È una raccolta della comunità di convenzioni prese da grandi progetti, e Russ Cox, allora tech lead di Go, ha aperto lì una issue per dichiarare che non è uno standard di Go. La maggior parte delle sue cartelle (pkg/, api/, build/, deployments/) ha senso solo per codebase grandi. Copiare l'intero scheletro in un nuovo progetto crea cartelle vuote e percorsi di import profondi senza alcun beneficio.
Le regole imposte dal tool sono poche:
- Una cartella è un package. Tutti i file
.goal suo interno (tranne i file_test.goche usano il suffisso_test) condividono il nome del package. - Il percorso di import è il percorso del modulo più il percorso della cartella.
- Una cartella chiamata
internallimita chi può importare ciò che contiene. - Le cartelle chiamate
testdata, e quelle che iniziano con.o_, vengono ignorate dal tool go.
Le forme comuni
Un singolo comando
todo/
├── go.mod module github.com/you/todo
├── main.go
├── parse.go
├── report.go
└── parse_test.go
Piatto, tutto in package main. go install github.com/you/todo@latest funziona e il binario si chiama todo.
Una libreria
slug/
├── go.mod module github.com/you/slug
├── slug.go package slug
├── slug_test.go
├── example_test.go
└── internal/
└── table/ helpers the public package uses, hidden from users
Il package sta nella radice del modulo, quindi chi lo usa importa github.com/you/slug e chiama slug.Make(...). Tutto ciò che non vuoi supportare come API pubblica va sotto internal/.
Un servizio o più binari
shop/
├── go.mod module github.com/you/shop
├── cmd/
│ ├── shop-api/
│ │ └── main.go package main: flags, config, wiring
│ └── shop-worker/
│ └── main.go
├── internal/
│ ├── order/ package order: domain types and logic
│ │ ├── order.go
│ │ └── order_test.go
│ ├── store/ package store: database access
│ └── httpapi/ package httpapi: handlers, routing
├── migrations/
└── README.md
cmd/<name>/main.go contiene una cartella per ogni binario, e il nome della cartella diventa il nome del binario (go build ./cmd/shop-api produce shop-api). Ogni main resta snello: legge la configurazione, costruisce le dipendenze, avvia il server. Il codice vero vive nei package sotto internal/, dove entrambi i binari possono usarlo e nessun altro modulo può farlo.
È la struttura verso cui convergono la maggior parte dei servizi Go. Usala quando hai un secondo binario o un vero motivo per dividere i package, non dal primo giorno.
internal/ è imposto dal comando go
Un package sotto internal/ può essere importato solo dal codice dell'albero che ha radice nel genitore di internal. Da un altro modulo, la build fallisce:
package example.com/b
main.go:6:2: use of internal package example.com/a/internal/secret not allowed
Questo fa di internal/ lo strumento per tenere piccola la superficie della tua API. Il codice lì dentro può cambiare liberamente, perché sai che ogni chiamante è nel tuo repository. Per un'applicazione, mettere quasi tutto in internal/ è ragionevole. Per una libreria, separa ciò che prometti di mantenere stabile da ciò che non prometti.
internal funziona a qualsiasi profondità: shop/internal/order è visibile a tutto shop/, mentre shop/internal/order/internal/pricing è visibile solo dentro shop/internal/order/.
Dare un nome ai package
Il nome del package fa parte di ogni punto di chiamata, quindi conta più dell'albero delle cartelle.
- Breve, in minuscolo, una parola:
order,store,httpapi. Niente underscore e niente mixedCaps. - Chiamalo per ciò che fornisce, non per ciò che contiene.
util,common,helpers,miscemodelsnon dicono nulla e diventano discariche; le linee guida di stile del team di Go li sconsigliano esplicitamente. Metti una funzione di supporto nel package che la usa, o in un package che prende il nome dal suo scopo (slug,retry). - Evita le ripetizioni. Chi chiama scrive
order.Ordereorder.New, quindi non chiamare le coseorder.OrderServiceoorder.NewOrder. La libreria standard lo fa in modo coerente:http.Server, nonhttp.HTTPServer. - Il nome della cartella e il nome del package dovrebbero coincidere, a parte
package mainnelle cartelle dei comandi. Quando differiscono, chi legge deve aprire un file per scoprire quale identificatore introduce l'import.
Dividi i package per responsabilità, non per tipologia. Una divisione in models/, controllers/, services/ (comune in altri ecosistemi) costringe ogni funzionalità a toccare tre package e tende a creare cicli di import, che Go vieta. I package organizzati attorno a un concetto di dominio (order, payment, user) tengono insieme ciascuno i propri tipi e la propria logica.
Dove vanno i test
I test vivono accanto al codice nella stessa cartella, in file chiamati *_test.go. Non c'è un albero tests/.
package orderinorder_test.go: un test interno, che può usare identificatori non esportati.package order_testnella stessa cartella: un test esterno, che vede solo l'API esportata, come la vedrebbe un chiamante. Utile per gli esempi e per evitare cicli di import nei test.- I file di fixture vanno in una cartella
testdata/accanto ai test. Il tool go la ignora, e i test girano con la cartella del package come cartella di lavoro, quindi percorsi relativi cometestdata/big.jsonfunzionano.
Altri file nella radice
| File o cartella | Scopo |
|---|---|
go.mod, go.sum | definizione del modulo e checksum delle dipendenze, sempre nella radice |
README.md, LICENSE | come in qualsiasi progetto |
Makefile o Taskfile.yml | scorciatoie di build facoltative |
Dockerfile | di solito nella radice per i servizi |
migrations/, web/, docs/ | risorse non Go, con il nome di ciò che contengono |
tools.go | vecchio modo per fissare le dipendenze degli strumenti; Go 1.24 lo sostituisce con le righe tool in go.mod (go get -tool) |
go.work | un workspace per sviluppare più moduli insieme in locale; di solito non si fa commit nei progetti con un solo modulo |
Errori comuni
- Copiare un template grande per un progetto piccolo. Parti piatto e aggiungi cartelle quando compare un secondo binario o un confine reale.
- Package chiamati
utilsocommon. Dai ai package il nome di ciò che fanno. - Un package per file o per tipo. I package Go sono pensati come unità più grandi delle classi Java. Un package con dieci file è normale.
- Cicli di import. Due package non possono importarsi a vicenda. Di solito significa che vanno insieme, che un tipo condiviso dovrebbe spostarsi in un package di livello più basso, o che una delle due parti dovrebbe dipendere da una piccola interface invece che dall'altro package.
pkg/per riflesso. Aggiunge un segmento a ogni percorso di import senza aggiungere significato.- Test in una cartella separata. Lì non possono vedere il codice non esportato e gli strumenti non se li aspettano.
Domande frequenti
Esiste una struttura ufficiale per i progetti Go?
Non una obbligatoria. Il team di Go pubblica delle indicazioni su go.dev/doc/modules/layout, che descrivono alcune forme comuni (un singolo package, un comando, più comandi con internal/). Il popolare repository golang-standards/project-layout è un progetto della comunità, non uno standard di Go, e il team di Go lo ha detto pubblicamente.
Cos'è la cartella internal in Go?
Un package il cui percorso di import contiene un elemento internal può essere importato solo dal codice che ha radice nel genitore di quella cartella internal. example.com/app/internal/store si può importare ovunque sotto example.com/app/, e il comando go rifiuta gli import da qualsiasi altro modulo con use of internal package ... not allowed.
Dovrei usare una cartella pkg in Go?
È facoltativa e aggiunge un segmento al percorso senza aggiungere significato. Alcuni grandi progetti usano pkg/ per separare il codice di libreria pubblico dal resto, ma la libreria standard di Go e la maggior parte dei progetti moderni non lo fanno. Metti i package importabili nella radice del modulo o in cartelle con un nome, e tutto ciò che è privato in internal/.
Dove vanno i file di test in un progetto Go?
Nella stessa cartella del codice che testano, con nomi *_test.go. Go non ha un albero separato per i test. I file di fixture vanno in una cartella testdata accanto ai test, che il tool go ignora quando cerca i package.