cat swift/struttura-package-swift.md
Struttura di un package Swift
Sources, Tests, target, prodotti e nomi: organizzare un package in modo leggibile evita caos quando il codice cresce.
Un package Swift ben strutturato comunica il progetto prima ancora di leggere il codice.
La struttura minima generata da SwiftPM è semplice, ma appena il progetto cresce devi prendere decisioni:
- quanti target?
- quali prodotti?
- dove metto il dominio?
- dove metto networking?
- dove metto test fake e fixture?
- cosa deve essere pubblico?
Struttura base
CouponKit/
Package.swift
Sources/
CouponKit/
Coupon.swift
Tests/
CouponKitTests/
CouponTests.swift
Il nome della cartella dentro Sources corrisponde di solito al nome del target.
Il nome della cartella dentro Tests corrisponde al nome del test target.
Package.swift
Un manifest essenziale:
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "CouponKit",
platforms: [
.macOS(.v14),
.iOS(.v17)
],
products: [
.library(name: "CouponKit", targets: ["CouponKit"])
],
targets: [
.target(name: "CouponKit"),
.testTarget(name: "CouponKitTests", dependencies: ["CouponKit"])
]
)
La riga swift-tools-version indica la versione degli strumenti SwiftPM richiesta per leggere il manifest.
Target singolo o più target?
All’inizio basta un target:
Sources/CouponKit/
Ma se il package cresce, puoi separare:
Sources/
CouponCore/
CouponHTTP/
CouponCLI/
Esempio:
CouponCore: dominio puro;CouponHTTP: client API;CouponCLI: eseguibile demo.
Questa separazione riduce dipendenze inutili. Il dominio non deve dipendere da HTTP.
Prodotti
Un product è ciò che il package espone.
products: [
.library(name: "CouponCore", targets: ["CouponCore"]),
.library(name: "CouponHTTP", targets: ["CouponHTTP"]),
.executable(name: "coupon-cli", targets: ["CouponCLI"])
]
Non tutti i target devono diventare product pubblici. Alcuni possono essere solo dettagli interni.
Dipendenze tra target
targets: [
.target(name: "CouponCore"),
.target(
name: "CouponHTTP",
dependencies: ["CouponCore"]
),
.executableTarget(
name: "CouponCLI",
dependencies: ["CouponCore", "CouponHTTP"]
)
]
La direzione conta:
CouponHTTP può dipendere da CouponCore.
CouponCore non dovrebbe dipendere da CouponHTTP.
Layout dei file
Evita file enormi come:
CouponKit.swift
Meglio:
Sources/CouponCore/
Coupon.swift
Sconto.swift
RegolaCoupon.swift
CouponEngine.swift
ErroreCoupon.swift
Il file system deve aiutare a navigare il dominio.
Test layout
Tests/CouponCoreTests/
CouponEngineTests.swift
ScontoTests.swift
Fixtures/
I test dovrebbero seguire la struttura del codice. Se hai più target, crea test target separati quando ha senso.
Fixtures
Per dati di test puoi usare helper Swift:
extension Coupon {
static let welcome10 = Coupon(...)
}
Oppure file JSON in Resources, se stai testando decoding.
Non mettere fixture casuali dentro i test senza nome: rendono difficile capire l’intenzione.
Resources
SwiftPM supporta risorse nei target:
.testTarget(
name: "CouponHTTPTests",
dependencies: ["CouponHTTP"],
resources: [
.process("Fixtures")
]
)
Utile per JSON, immagini, file campione.
Checklist
Per una struttura sana:
- il dominio vive in un target senza dipendenze inutili;
- networking e UI non contaminano il core;
- i nomi dei target sono chiari;
- i product esposti sono intenzionali;
- i test seguono i target;
- i file sono piccoli e nominati per concetto;
- le fixture sono organizzate.
Esercizio
Riorganizza CouponKit in:
Sources/CouponCore
Sources/CouponHTTP
Sources/CouponCLI
Tests/CouponCoreTests
Tests/CouponHTTPTests
Poi aggiorna Package.swift con:
- una library
CouponCore; - una library
CouponHTTP; - un executable
coupon-cli; - test target per core e HTTP.