Logo Ing. Domenico Alvaro
Ing. Domenico Alvaro

Ingegneria del software

Principi dell’ingegneria del software: modularità, astrazione e separazione delle responsabilità

Il codice può anche “funzionare”. Il problema è il tempo. Molti software diventano ingestibili perché all'inizio si è privilegiato lo speed-to-demo: logica e UI mescolate, dipendenze incrociate, confini poco chiari. Dopo mesi (o dopo l'ennesima richiesta) ogni modifica sembra una scommessa.

Perché servono i principi di ingegneria del software

Questi principi non sono teoria: sono la differenza tra un sistema che evolve e un sistema che si rompe ogni volta che cambi un dettaglio.

  • Codice difficile da modificare: cambi un'area e “insegue” bug altrove
  • Bug continui: le regole di dominio sono distribuite e nessuno sa dove
  • Tempi che aumentano: ogni sprint diventa più lento perché aumenta il rischio
  • Impossibilità di scalare: non scala il team (e non scala il prodotto)

Separazione degli interessi (Separation of Concerns)

Separare significa smettere di fare tutto nello stesso posto. UI e logica devono avere confini chiari: la presentazione non decide le regole di business, e la logica non dovrebbe conoscere l'HTML o i dettagli del framework.

Esempio concreto: una pagina “Gestione rapportini” deve solo raccogliere input e mostrare stati. Il calcolo delle validazioni, lo stato del workflow e le regole di export devono stare in servizi/moduli separati (backend o livello applicativo).

Impatto pratico: quando cambi UI (o quando aggiungi una nuova interfaccia) non riscrivi le regole. E i bug diventano più facili da localizzare.

Modularità

Un modulo deve avere una responsabilità precisa e non deve conoscere troppo del resto. Se ogni parte “sa tutto”, le modifiche diventano costose.

Esempio: in un ERP/gestionale ragioni per moduli indipendenti come “gestione utenti”, “pagamenti”, “report”, “magazzino”, “ticket”. Ogni modulo espone interfacce (contratti) e usa solo ciò che serve.

Impatto pratico: puoi sostituire un modulo (o espanderlo) senza riscrivere il sistema. Inoltre il testing diventa più mirato.

Astrazione

L'astrazione serve a non legarti ai dettagli che cambiano. Se oggi usi un provider o un formato export, domani potrebbe servire altro. Quindi interponi livelli stabili.

Esempio: usa interfacce/servizi per integrare ERP e sistemi esterni. Il resto del software chiama un contratto stabile (es. “SyncOrdini”), senza sapere se sotto c'è un'API HTTP, un'export o una chiamata diversa.

Impatto pratico: cambiare tecnologia non diventa un refactor globale. Cambi solo l'implementazione dell'adapter.

Anticipazione del cambiamento

Il software cambia sempre. La domanda vera è: “quanto costa cambiare?” Progetti per modifiche future quando definisci confini e contratti, invece di costruire tutto attorno a una versione “perfetta” del giorno 1.

Esempio: se sai che gli export (PDF/Excel) cambieranno con i requisiti, non scrivere la logica “nel generatore file”. Metti le regole in un livello applicativo e lascia che il rendering cambi senza toccare il core.

Impatto pratico: riduci le regressioni e mantieni prevedibilità sui tempi. In produzione non “scopri” i limiti: li gestisci prima.

Generalità

Generalità non significa farne un prodotto generico per tutti. Significa evitare soluzioni troppo specifiche e rigide: quelle che funzionano solo per un caso singolo, ma poi bloccano qualsiasi evoluzione.

Esempio: invece di avere un flusso “validazione rapportini solo per questo caso”, definisci regole parametrizzate (input/criteri) e moduli riutilizzabili su domini simili.

Impatto pratico: quando arriva la seconda esigenza, non riparti da zero. Riusi le parti e riduci rischio.

Incrementalità

Incrementalità significa costruire a step: rilasciare valore presto e crescere senza strappare. Ogni incremento deve essere coerente con l'architettura, altrimenti il sistema “si rovina piano”.

Esempio: parti da un nucleo (auth, ruoli, moduli base), poi aggiungi workflow e integrazioni. Ogni passo va testato e non deve rompere i contratti esistenti.

Impatto pratico: riduci rischio e rendi il progresso visibile. Il testing resta controllabile.

Errori comuni quando non rispetti questi principi

  • Codice monolitico: tutto nello stesso posto, nessun confine
  • Dipendenze incrociate: ogni modulo “tocca” l'altro
  • Modifiche che rompono tutto: regressioni continue e fix a catena
  • Difficoltà nel testing: non puoi testare senza eseguire l'intero sistema

Come applico questi principi nei miei progetti

Nei progetti reali applico i principi in modo pragmatico: confini chiari, moduli con responsabilità e contratti stabili tra UI, servizi e dati.

  • Flutter con BLoC: separo logica e UI. Lo strato BLoC gestisce stato, eventi e regole; la UI si occupa di renderizzare.
  • API Node.js: strutturo servizi e contratti. I test di sistema verificano i flussi, non solo endpoint singoli.
  • ERP modulari: moduli per dominio e workflow, con integrazioni incapsulate in adapter per ERP/CRM e export.
  • Repository pattern: separo persistenza dalla logica. Così cambio storage o formato export senza riscrivere il core.

Conclusione

La qualità del software dipende più dalla progettazione che dalla tecnologia usata. Puoi cambiare framework, ma se i confini sono chiari e i principi sono rispettati, il sistema resta modificabile, testabile e scalabile.