cat swift/traccia-library-authoring-versioning-docc.md
Traccia library authoring: API, versioning e DocC
Progettare una libreria Swift pubblicabile: surface API, access control, SemVer, package layout, compatibilità e documentazione DocC.
Scrivere una libreria è diverso da scrivere codice per te stesso.
Una libreria ha utenti che non sono nella tua testa. Ogni tipo pubblico diventa una promessa.
Surface API
La surface API è tutto ciò che esponi pubblicamente:
- tipi
public; - initializer
public; - metodi
public; - protocolli;
- enum e casi;
- errori;
- conformità;
- documentazione.
Esempio:
public struct Money: Equatable, Hashable, Sendable {
public let cents: Int
public let currency: Currency
public init(cents: Int, currency: Currency) throws {
guard cents >= 0 else {
throw MoneyError.negativeAmount
}
self.cents = cents
self.currency = currency
}
}
Se rendi pubblico cents, stai promettendo che quel dettaglio resterà comprensibile è utile.
Public solo quando serve
Access control è design.
public struct ProductClient {
private let repository: ProductRepository
public init(configuration: ProductClientConfiguration) {
self.repository = HTTPProductRepository(configuration: configuration)
}
}
Non tutto deve essere pubblico.
Regola pratica:
publicper ciò che l’utente deve usare;internalper implementazione del package;privateper dettagli locali;opensolo se vuoi permettere subclassing fuori modulo.
Initializer stabili
Un initializer pubblico è un contratto.
Fragile:
public init(baseURL: URL, timeout: TimeInterval, retryCount: Int, decoder: JSONDecoder)
Più evolvibile:
public struct ProductClientConfiguration: Sendable {
public var baseURL: URL
public var timeout: Duration
public var retryPolicy: RetryPolicy
public init(
baseURL: URL,
timeout: Duration = .seconds(15),
retryPolicy: RetryPolicy = .default
) {
self.baseURL = baseURL
self.timeout = timeout
self.retryPolicy = retryPolicy
}
}
Una configuration struct permette di aggiungere opzioni con meno rotture.
SemVer
Semantic Versioning:
MAJOR.MINOR.PATCH
1.2.3
In pratica:
PATCH: bug fix compatibile;MINOR: nuove API compatibili;MAJOR: breaking change.
Esempi di breaking change:
- rinominare un metodo pubblico;
- rimuovere un enum case pubblico;
- cambiare tipo di ritorno;
- rendere throwing un metodo che prima non lo era;
- cambiare comportamento documentato;
- restringere un requisito di protocollo.
Package layout
Sources/
TaskCore/
TaskItem.swift
TaskDraft.swift
TaskError.swift
TaskClient/
TaskClient.swift
TaskClientConfiguration.swift
HTTPTaskRepository.swift
Tests/
TaskCoreTests/
TaskClientTests/
Sources/TaskClient/TaskClient.docc/
TaskClient.md
Tutorials/
Separare core e client può aiutare utenti che vogliono solo il dominio.
Package manifest
let package = Package(
name: "TaskKit",
platforms: [
.iOS(.v17),
.macOS(.v14)
],
products: [
.library(name: "TaskCore", targets: ["TaskCore"]),
.library(name: "TaskClient", targets: ["TaskClient"])
],
targets: [
.target(name: "TaskCore"),
.target(
name: "TaskClient",
dependencies: ["TaskCore"]
),
.testTarget(
name: "TaskCoreTests",
dependencies: ["TaskCore"]
),
.testTarget(
name: "TaskClientTests",
dependencies: ["TaskClient"]
)
]
)
Le piattaforme minime sono parte della promessa.
API stability
Prima di pubblicare chiedi:
- posso cambiare questo nome senza rompere utenti?
- questo tipo deve davvero essere
public? - questo enum dovrà crescere?
- questo protocollo è troppo grande?
- gli errori sono abbastanza espressivi?
- sto esponendo DTO interni?
- sto vincolando gli utenti a una implementazione?
Una libreria buona dice poco, ma bene.
Doc comments
/// Creates a task from validated user input.
///
/// Use `TaskDraft` when accepting data from UI, HTTP requests or CLI arguments.
/// The initializer trims whitespace and rejects empty titles.
public struct TaskDraft: Equatable, Sendable {
...
}
La documentazione deve spiegare intenzione, contratti e casi limite, non ripetere il nome.
DocC
DocC permette di costruire documentazione da commenti, articoli e tutorial.
Articolo introduttivo:
# TaskKit
Model tasks, validate input and build clients around a shared Swift domain.
## Overview
Use ``TaskDraft`` to validate input before creating a ``TaskItem``.
DocC è particolarmente utile per:
- overview;
- tutorial;
- esempi progressivi;
- collegamenti tra simboli;
- documentazione pubblicabile.
Deprecation
Non rimuovere subito se puoi deprecare.
@available(*, deprecated, renamed: "execute(id:)")
public func load(id: TaskItem.ID) async throws -> TaskItem {
try await execute(id: id)
}
Questo accompagna gli utenti nella migrazione.
Checklist
Una libreria Swift seria:
- ha una surface API piccola;
- usa access control con intenzione;
- rispetta SemVer;
- ha test sui contratti pubblici;
- evita DTO pubblici inutili;
- documenta errori e comportamento;
- offre configuration evolvibili;
- usa DocC per overview e tutorial;
- depreca prima di rompere quando possibile.
Esercizio
Trasforma TaskCore in una libreria pubblicabile:
- scegli quali tipi sono
public; - rendi interni i dettagli;
- aggiungi una configuration struct se serve;
- scrivi commenti DocC per tre simboli;
- aggiungi un articolo
TaskKit.md; - definisci una policy SemVer per le prossime modifiche.
Obiettivo: un altro sviluppatore deve capire cosa usare senza leggere tutta l’implementazione.