DA MASER A MONFUMO | MOTO GUZZI V85TT | PURE SOUND POV 4K

🏍️ Il mio nuovo canale YouTube: giri in moto in POV, solo audio, tra le Dolomiti in 4K. Niente musica, niente parole — solo il motore e le Alpi. Vieni a fare un giro!

Iscriviti

Dopo più di un decennio a scrivere software, ho letto molte opinioni su come scrivere buon software. A tutti piace citare regole e principi, quindi lo farò anch'io. Scrivere codice leggibile significa rendere la vita più facile alle persone che dovranno occuparsene in seguito — non si tratta di sfoggiare la propria abilità o seguire tendenze che sembrano intelligenti sui social network.

Questa è solo una lista di pensieri che ho accumulato nel corso degli anni, messi insieme senza un ordine particolare. Non sono una guida definitiva; di quelle online ce ne sono in abbondanza. Fatene quello che volete — concordate, dissentite, o alzate gli occhi al cielo. Ma se cercate consigli pratici e senza fronzoli (con un pizzico di scetticismo per le cose inutili che tutti dobbiamo sopportare), siete nel posto giusto. Scrivere codice leggibile non è poi così difficile — una volta che smettete di complicarlo inutilmente. Ho incluso alcuni esempi in Go. Uso Go principalmente, quindi abbiate pazienza.

Evitate i Package Piccoli

Dividere il codice in un milione di package minuscoli potrebbe sembrare un modo elegante di organizzare le cose, ma in pratica è solo fastidioso. Ogni package è un ulteriore livello di overhead cognitivo. Costringete chiunque lavori sul codice a saltare tra file come un "pollo senza testa" solo per capire una singola funzionalità. Non è "modulare"; è una tortura progettata da una mente malata.

Chiedetevi: "Queste cose appartengono davvero insieme?". Un package dovrebbe raggruppare funzionalità correlate, non servire da deposito per il vostro bisogno ossessivo di compartimentare.

Esempio: Raggruppare la Logica Correlata

Sbagliato:

bash
/myproject
  /validators
    validator.go
  /parsers
    parser.go
  /services
    service.go

Giusto:

bash
/myproject
  /processing
    validation.go
    parsing.go
    service.go

Vedete la differenza? Smettete di sparpagliare la logica tra le directory solo per farlo. La coesione è l'obiettivo — non la proliferazione di package.

Evitate i Package util (e Forse Anche le Utility)

Ah, il famigerato package util. Ogni codebase ne ha uno, ed è sempre un casino. Diciamoci la verità: un package util è solo un nome elegante per "non sapevo dove mettere questa cosa, quindi eccola qui". Col tempo, diventa un deposito di funzioni casuali che non hanno motivo di esistere in primo luogo.

Ecco un'idea radicale: avete davvero bisogno di quella funzione di utilità? Prima di scrivere l'ennesimo "helper", verificate se una libreria open-source o la libreria standard non lo fa già. È probabile che qualcun altro abbia già risolto il problema meglio di quanto fareste voi in 10 minuti di hacking.

Esempio: Non Reinventare l'Utility

Sbagliato:

go
func Contains(slice []string, item string) bool {
    for _, s := range slice {
        if s == item {
            return true
        }
    }
    return false
}

Giusto:

go
import "slices"
 
func main() {
    if slices.Contains([]string{"a", "b"}, "a") {
        fmt.Println("Found")
    }
}

Il package slices di Go esiste. Usatelo. Smettetela di fare gli eroi.

Rendetelo Semplicemente Testabile

Se il vostro codice è difficile da testare, complimenti: lo state facendo male. Scrivere codice testabile non riguarda solo i test — riguarda una buona architettura. Se il codice è modulare e disaccoppiato, sarà testabile e più facile da mantenere. Se è un casino di spaghetti strettamente accoppiati, nessuna quantità di mock vi salverà.

Ed è praticamente tutto quello che ho da dire sul buon design del software.

Quanto Coverage è Sufficiente?

Ah, la domanda eterna: "Qual è la percentuale ideale di test coverage?". Quanta parte del vostro codice gira in produzione? Tanto coverage volete. Ma non sprecate tempo a testare getter e setter privi di significato, o codice morto. Concentratevi sulle cose che contano:

  • Logica di business critica
  • Casi limite
  • Percorsi di fallimento

Esempio: La Testabilità Significa Flessibilità

Sbagliato:

go
func FetchData() string {
    return externalServiceCall()
}

Giusto:

go
type Fetcher interface {
    Fetch() string
}
 
func ProcessData(f Fetcher) string {
    return strings.ToUpper(f.Fetch())
}

Fare il mock di Fetcher nei test rende questo codice infinitamente più facile da validare. Il codice difficile da testare è un segnale d'allarme che il vostro design ha bisogno di lavoro.

Ripetetevi (un Po') e Minimizzate i Salti

"Don't Repeat Yourself" (DRY) è ottimo — finché non lo è. L'astrazione di livello cosmico per evitare la ripetizione spesso porta a codice impossibile da seguire. Ogni volta che uno sviluppatore deve saltare a un altro file o funzione helper, aggiungete attrito.

Un po' di duplicazione non è un crimine se rende il codice più facile da leggere. Mantenete la logica correlata insieme e smettete di sparpagliare funzionalità banali tra decine di helper.

Esempio: Evitare Astrazioni Inutili

Sbagliato:

go
func Process() {
    start()
    // more code here
}
 
func start() {
    setup()
    // more code here
}
 
func setup() {
    prepare()
    // more code here
}
 
func prepare() {
    fmt.Println("Processing...")
    // more code here
}

Giusto:

go
func Process() {
    fmt.Println("Processing...")
}

Se una nuova funzione aggiunge poco valore, non createla. Meno salti significa debug più facile. E se siete preoccupati per la duplicazione, concentratevi sulla duplicazione che conta: la logica di business che richiede consistenza su più passi. Tutto il resto è solo rumore.

Si può argomentare a favore del caso in cui le funzioni sostituiscono i commenti. Per esempio, una funzione usata in un'istruzione if per rendere il codice più leggibile. Questo è un buon uso di una funzione. Esempio:

go
func main() {
    if isUserAllowed(user) {
        fmt.Println("User is allowed")
    }
}
 
func isUserAllowed(user) bool {
    permissions := getPermissions()
    hasPermission := false
    for _, permission := range permissions {
        if permission == "admin" {
            hasPermission = true
        }
    }
    return user.Enabled
        && hasPermission
        && user.Domain == "example.com"
}

Un esempio forzato, devo ammettere, ma capite il punto.

Non Scrivete Documentazione Inutile

La documentazione è importante, ma non facciamo finta che tutta la documentazione sia utile. Copiare la documentazione di un progetto di terze parti nel vostro repo perché forse potreste aver bisogno di alcune informazioni a un certo punto? Inutile. Scrivere commenti inline verbosi che ripetono ciò che il codice già dice? Ugualmente inutile. Informazioni importanti sparse su decine di README, wiki e Google Docs? Pura malvagità.

Scrivete documentazione utile:

  • Un README solido per far partire le persone.
  • Spiegazioni chiare delle decisioni di design non ovvie.
  • Guide per la risoluzione dei problemi comuni.

Se la vostra documentazione non aiuta attivamente qualcuno, è rumore.

Non Reinventate la Ruota

Non c'è quasi mai una buona ragione per costruire qualcosa da zero che è già stato costruito. Il vostro codice non è speciale. Il mondo non ha bisogno del vostro router API personalizzato, del vostro framework di logging, o della vostra implementazione di coda. Le librerie consolidate esistono per un motivo — sono affidabili, ben testate e familiari ai vostri colleghi.

Ora, so cosa state pensando: "Ma aggiungere dipendenze è male!!!11!1!!!" Certo, le dipendenze comportano qualche rischio, ma la vostra soluzione personalizzata è quasi sempre una responsabilità maggiore. La vostra implementazione one-off non è testata dalla comunità, manca di anni di iterazioni e correzioni di bug, e vincola il vostro team a mantenerla indefinitamente. Questo è un rischio molto più grande che fare affidamento su una libreria supportata da centinaia (o migliaia) di sviluppatori.

Esempi di Reinventare la Ruota:

  • Scrivere il proprio router API invece di usare gorilla/mux o chi.
  • Costruire la propria coda di task invece di usare Redis o RabbitMQ.
  • Creare una libreria di logging personalizzata invece di adottare zap.
  • Qualsiasi cosa che esista già, in realtà.

Anche se la vostra soluzione fosse tecnicamente migliore (e siate onesti, probabilmente non lo è), aggiunge complessità inutile, isola il vostro team e rende più difficile per gli altri lavorare con il vostro codice. Usate gli strumenti che la comunità ha validato, e concentrare le energie sulle parti della vostra applicazione che sono davvero uniche. Il vostro router personalizzato non lo è. Smettetela, andate a prendere una boccata d'aria.

I Problemi Tecnici Richiedono Concentrazione Tecnica

Nessuna quantità di riunioni, roadmap o ticket risolverà il vostro debito tecnico. Processo e gestione possono aiutare a organizzare il lavoro, ma il lavoro stesso è intrinsecamente tecnico. Non potete fare team-building per uscire da una codebase ingarbugliata o da una pipeline di deployment piena di bug.

Parliamo ora di business value. Sì, è importante — fondamentale, anzi — ma non dimentichiamo il nostro ruolo di ingegneri. Tagliare gli angoli tecnici per guadagni a breve termine potrebbe sembrare conveniente per un po', ma la storia è costellata di esempi monitori. Gli aerei della Boeing hanno generato molto valore per il business… finché non hanno cominciato a cadere dal cielo perché qualcuno ha deciso che il rigore ingegneristico poteva essere sacrificato per una consegna più veloce. Come ingegneri, abbiamo la responsabilità di bilanciare le esigenze aziendali con la salute tecnica a lungo termine. Il business value non viene dalla distruzione dei sistemi — viene dal costruire cose che durano.

La soluzione? Rimanete concentrati sulla risoluzione dei problemi tecnici con soluzioni tecniche. Questo significa dare priorità al tempo per il lavoro reale: rifactoring di moduli disordinati, miglioramento del coverage dei test e pulizia degli hack che avevate promesso di rivisitare "dopo", per esempio. Discussioni, riunioni e processi dovrebbero esistere solo per far avanzare il lavoro — non come piattaforme per l'auto-promozione o la deliberazione infinita. Se una riunione non fornisce spunti concreti o non aiuta a risolvere un problema, è rumore.

Meno rumore, più concentrazione tecnica e impegno per la qualità ingegneristica garantiscono che il progresso avvenga dove conta di più: nel codice — e in modi che non compromettono il futuro per il bene del presente.

Imparate lo Strumento

Uno dei modi più rapidi per scrivere codice illeggibile e non manutenibile è immergersi in un nuovo linguaggio o framework senza preoccuparsi di impararne gli idiomi. Ogni strumento ha le sue convenzioni, pattern e best practice. Ignorarle perché "così facevo in [inserite il vostro ultimo progetto o linguaggio preferito]" porterà a codice che sembra scomodo, incoerente e fuori posto.

Scrivere codice scadente mentre si impara va benissimo — ottimo, anzi! È così che si sperimenta, si fallisce e si migliora. Ma una volta superato il livello base, prendetevi il tempo per capire davvero lo strumento che state usando. In Go, per esempio, questo significa abbracciare la semplicità, la gestione idiomatica degli errori e le convenzioni come restituire implementazioni concrete invece di interfacce. Non cercate di forzare pattern da Java, Python o qualsiasi cosa abbiate usato in precedenza — rende solo il codice più difficile da seguire per chiunque conosca il linguaggio.

Dedicate tempo a leggere documentazione, progetti open-source o codebase idiomatiche. Chiedetevi: "Come risolverebbe questo problema qualcuno che conosce bene questo strumento?" Il vostro obiettivo non è solo far funzionare il codice — è farlo funzionare nel modo in cui lo strumento è stato pensato per essere usato. Imparare bene lo strumento non migliora solo il vostro codice; vi guadagna anche credibilità tra gli altri sviluppatori. Nessuno vuole mantenere un progetto Go che sembra uno strano miscuglio di pattern in stile Java.

In breve: prendetevi il tempo di imparare e rispettare gli strumenti che usate. È un investimento che ripaga sia per voi che per chiunque debba lavorare con il vostro codice in seguito.

Il codice leggibile non riguarda solo la competenza tecnica — riguarda l'empatia. Significa riconoscere che il vostro lavoro verrà letto, mantenuto e costruito da altre persone (incluso il vostro futuro io). Rendiamo loro la vita più facile.

Pulite regolarmente, scrivete test utili, evitate la complessità inutile e prendete decisioni ponderate. Se lo fate, non solo scriverete codice migliore — costruirete sistemi migliori, team migliori e software migliore. E questo è una vittoria per tutti.

Ma ricordate: non esiste una soluzione valida per tutti. Ogni progetto, team e situazione è diverso. Usate il vostro giudizio, adattatevi al contesto e non abbiate paura di sperimentare. Le best practice di oggi potrebbero essere gli anti-pattern di domani. Restate curiosi, restate umili e continuate ad imparare. È l'unico modo per scrivere codice che resista alla prova del tempo.