Salta al contenuto
Note per Studenti Ingegneria del software - prodotto software e processo di sviluppo

Ingegneria del software - prodotto software e processo di sviluppo

In questa pagina 7

Questa nota apre la parte di ingegneria del software (corso di Elementi di Ingegneria del Software, Ing. Informatica UniPD): che cosa si studia, perché serve una disciplina apposta e quali sono i problemi che la giustificano. Le note successive entrano nei singoli temi, nell'ordine del corso: Java avanzato (Java - generics, reflection e passaggio dei parametriIn Java ogni dato ha una propria identità a tempo di esecuzione (il cast è controllato e può lanciare ClassCastException, a differenza del C) e i parametri si passano sempre per valore (di un riferimento, per gli oggetti: si può modificare l'oggetto ma non riassegnare il riferimento del chiamante); i generics (CodaG) spostano i controlli di tipo a tempo di compilazione ma sono cancellati dalla type erasure, quindi non sono i template del C++; la riflessione (Class, Constructor, Method, Field) permette di caricare classi per nome, creare oggetti, chiamare metodi e leggere o scrivere campi a tempo di esecuzione, con eccezioni specifiche (ClassNotFoundException, InvocationTargetException, IllegalAccessException) e il costo di aggirare il controllo statico.Java - generics, reflection e passaggio dei parametri →, Concorrenza in Java - thread, sincronizzazione e poolIn Java un thread si crea con Thread e Runnable e si avvia con start() (run() chiamato direttamente non crea nessun thread); i thread che modificano dati condivisi producono race condition (aggiornamenti persi) se la sequenza lettura-modifica-scrittura non è atomica, e si protegge con synchronized (monitor dell'oggetto, rientrante), con classi atomiche o con lock; wait/notifyAll dentro un synchronized, sempre in un ciclo while, realizzano l'attesa di una condizione (pool di risorse, produttore-consumatore); l'ordine opposto di acquisizione di due lock provoca stallo (deadlock) e si evita con un ordine globale; i thread pool (ExecutorService, Callable, Future) riusano pochi thread per molti compiti; i pattern concorrenti ricorrenti sono monitor, produttore-consumatore, thread pool, Singleton thread-safe, oggetti immutabili, future.Concorrenza in Java - thread, sincronizzazione e pool →), Ingegneria dei requisiti - requisiti funzionali e non funzionaliUn requisito è la descrizione di un servizio o di un vincolo del sistema, scritta ad astrazione diversa per chi decide (requisiti utente, in linguaggio naturale) e per chi realizza (requisiti di sistema, strutturati e dettagliati); si dividono in funzionali (servizi e comportamento), non funzionali (proprietà e vincoli: prodotto, organizzativi, esterni, da scrivere con una misura verificabile) e di dominio; il processo di ingegneria dei requisiti è iterativo (elicitazione con interviste, etnografia, storie e scenari; analisi e negoziazione; specifica; validazione; gestione del cambiamento con identificatori e tracciabilità) e produce il documento dei requisiti, che dice cosa fa il sistema e non come; i modelli UML (contesto, casi d'uso, sequenza, classi, stati) aiutano a chiarire i requisiti.Ingegneria dei requisiti - requisiti funzionali e non funzionali →, Architettura del software - decisioni, organizzazione e stiliL'architettura del software descrive come un sistema è organizzato in componenti, le loro relazioni e la loro distribuzione; è il ponte tra requisiti e progetto dettagliato, e le sue decisioni derivano soprattutto dai requisiti non funzionali (prestazioni, sicurezza, disponibilità, manutenibilità), spesso in conflitto tra loro; si descrive con più viste (logica, processi, sviluppo, fisica); gli stili di organizzazione ricorrenti sono a livelli (layered), MVC, repository, client-server, pipe and filter, publish-subscribe e microservizi; la modularizzazione mira ad alta coesione e basso accoppiamento, nascondendo le decisioni dietro interfacce (information hiding, inversione delle dipendenze).Architettura del software - decisioni, organizzazione e stili →, le responsabilità e i pattern (Principi GRASP per l'assegnazione delle responsabilitàGRASP (General Responsibility Assignment Software Patterns) sono principi per assegnare le responsabilità alle classi, dove una responsabilità è un obbligo di conoscere (knowing) o di fare (doing): Creator (chi crea A? la classe che contiene o registra A, la usa da vicino o ha i dati per inizializzarla), Information Expert (la classe che ha le informazioni necessarie), Low Coupling (poche dipendenze tra classi per ridurre l'impatto dei cambiamenti), Controller (chi riceve gli eventi di sistema? un oggetto che rappresenta il sistema o il caso d'uso, che coordina e non calcola), High Cohesion (classi focalizzate su un unico scopo); si completano con Polymorphism, Pure Fabrication, Indirection e Protected Variations, che sono alla base di molti pattern GoF.Principi GRASP per l'assegnazione delle responsabilità →, Design pattern - introduzione e catalogo GoFUn design pattern è una soluzione generale, riusabile e collaudata a un problema ricorrente di progettazione in un certo contesto, con un nome (che arricchisce il vocabolario dei progettisti), un problema, una soluzione (struttura e partecipanti) e le conseguenze; il catalogo GoF (Gamma, Helm, Johnson, Vlissides) ha 23 pattern in tre famiglie: 5 creazionali (come si creano gli oggetti), 7 strutturali (come si compongono classi e oggetti), 11 comportamentali (come collaborano e si ripartiscono le responsabilità); dietro i pattern ci sono principi come programmare verso un'interfaccia, incapsulare ciò che varia, preferire la composizione all'ereditarietà, aperto/chiuso e inversione delle dipendenze; un pattern si sceglie dal problema, non dal nome, e usarlo senza bisogno complica il progetto.Design pattern - introduzione e catalogo GoF →), Software testing - progettazione dei casi di test e JUnitIl testing esegue il programma con dati artificiali per mostrare che soddisfa i requisiti (validation testing) e per scoprirne i difetti (defect testing), ma può rivelare la presenza di errori e non la loro assenza; il test di sviluppo comprende unit test (singole classi e metodi), test dei componenti (le interfacce) e test di sistema (le interazioni); i casi di test si ricavano con le classi di equivalenza (una per partizione) e i valori limite (ai bordi delle partizioni), più linee guida per sequenze e input anomali; JUnit automatizza i test con @Test, fixture @Before/@After (@BeforeClass/@AfterClass), le asserzioni (assertEquals con atteso per primo), eccezioni attese, timeout e @Ignore; nel TDD il test si scrive prima del codice (red, green, refactor) e i test automatici diventano la suite di regressione; seguono i test di rilascio (black box sui requisiti, prestazioni) e dell'utente (alpha, beta, accettazione).Software testing - progettazione dei casi di test e JUnit → e infine Processi di sviluppo - modelli plan-based, metodi agili e ScrumUn processo software è un insieme strutturato di attività (specifica, progetto e realizzazione, validazione, evoluzione) e si descrive con attività, prodotti, ruoli e condizioni; i processi plan-driven pianificano tutto in anticipo e misurano il progresso sul piano (cascata, modello a V con i livelli di test corrispondenti, sviluppo incrementale pianificato, integrazione e configurazione di componenti, con prototipi e consegna incrementale per gestire il cambiamento, e miglioramento del processo con i livelli di maturità SEI), mentre i metodi agili pianificano in modo incrementale e accolgono il cambiamento (manifesto agile, XP con i suoi valori e le sue dodici pratiche, storie dell'utente e story point); Scrum organizza il lavoro in sprint con tre ruoli (Product Owner, Scrum Master, team), eventi (planning, daily scrum, review, retrospective) e artefatti (product backlog, sprint backlog, incremento), stimando con il planning poker; non c'è un processo giusto, ma priorità diverse (stabilità dei requisiti, criticità, dimensione e distribuzione del team).Processi di sviluppo - modelli plan-based, metodi agili e Scrum →. L'esame consiste nello sviluppo di un progetto, quindi le note sono orientate alle scelte che un progetto richiede (requisiti, struttura, pattern, test), non alla memorizzazione.

Che cosa è l'ingegneria del software

Definizione (ingegneria del software). È l'insieme di teorie, metodi e strumenti per lo sviluppo di software professionale; copre tutti gli aspetti della produzione (specifica, progetto, realizzazione, test, manutenzione) e anche la parte organizzativa, non solo quella tecnica. L'obiettivo è una produzione industriale e ripetibile, non un'arte.

Il motivo di una disciplina a sé:

  • il software sostiene le economie e controlla un numero crescente di sistemi (identità digitale, domotica, trasporti): un errore o un accesso non autorizzato ha conseguenze reali;
  • il costo del software supera quello dell'hardware, perché la manutenzione costa spesso più dello sviluppo; per questo conviene usare hardware standard e scrivere software su misura, e non fare over-engineering all'inizio;
  • ripartizione indicativa dei costi di sviluppo: circa il 60% sviluppo e il 40% test; per il software su misura il costo di manutenzione cresce con le versioni.

Differenze con altre discipline:

confronto idea
ingegneria del software e informatica teorica l'informatica studia teoria e fondamenti; l'IdS la pratica della costruzione, compresi gli aspetti umani e di processo
ingegneria del software e ingegneria dei sistemi i sistemi comprendono hardware, software e processi; il software è una parte del sistema

Perché il software fallisce

  1. Complessità crescente. I metodi di IdS permettono di costruire sistemi più grandi, e il mercato ne chiede di sempre più grandi e veloci da produrre; un difetto in un sistema complesso si propaga molto lontano.
  2. Metodi non usati. Scrivere programmi è facile, e senza conoscere i metodi si ricade nel cowboy coding (codice senza analisi, documentazione né test): ne sono conseguenza sanzioni (per esempio le multe per mancato rispetto del GDPR) e prodotti "resi smart" senza buone pratiche.

Il prodotto software

Definizione (software). Programmi e documentazione. La documentazione non è accessoria: ciò che è ovvio per uno sviluppatore non lo è per un altro né per l'utente.

tipo per chi chi decide sulle funzioni
generico (generic) un mercato di clienti non precisi, venduto così com'è lo sviluppatore
specifico (custom) un cliente, o un tipo di azienda, in particolare (per esempio un sistema embedded) il cliente

Ogni prodotto è in qualche misura su misura anche se riusa moduli.

Attributi di un buon software

attributo significato esempio di fallimento
manutenibilità (maintainability) il software deve potersi evolvere con le richieste del cliente, e deve essere progettato anche in vista della sua evoluzione codice compresso in una riga, illeggibile: chi lo modifica dopo introduce errori
affidabilità e sicurezza (dependability and security) ci si deve poter fidare del sistema: non causa danni fisici o economici se fallisce e non può essere manipolato da malintenzionati Therac-25 (anni '80, apparecchio per radioterapia con errori di software che portarono a dosi eccessive); Knight Capital (2012, perdite enormi in pochi minuti per un rilascio sbagliato)
efficienza (efficiency) non sprecare risorse (tempo di risposta, memoria, elaborazione), tenendo conto dei costi
accettabilità (acceptability) comprensibile, usabile e compatibile con ciò che l'utente già usa

Il compromesso tra qualità e tempi è una scelta di mercato: rilasciare qualcosa che funziona "abbastanza" e correggere sulla base delle segnalazioni può essere più conveniente che arrivare tardi con un prodotto perfetto, ma dipende dalla criticità (per un software che comanda un apparecchio medico non è ammesso).

Tipi di applicazione

tipo caratteristica
standalone tutto in locale, senza rete
transazionali interattive (interactive transaction-based) si appoggiano a un database e a un terminale remoto (e-commerce)
sistemi di controllo embedded software dentro un dispositivo hardware, probabilmente la maggior parte dei sistemi esistenti
elaborazione a lotti (batch) grandi quantità di dati in ingresso e le uscite corrispondenti (previsioni del tempo, addestramento di modelli); la precisione viene prima di tutto
intrattenimento lettori, console
modellazione e simulazione (digital twin) modellano processi fisici per prevederne il comportamento
raccolta dati sensori IoT che inviano dati ad altri sistemi
sistemi di sistemi insiemi di sistemi indipendenti coordinati

Non esiste una procedura unica: contano applicazione, richieste del cliente e storia del gruppo di sviluppo. Il principio comune dell'ingegneria è dividere il problema in parti abbordabili.

Le attività fondamentali

Ogni processo software contiene le stesse quattro attività, organizzate in modo diverso nei vari modelli (vedi Processi di sviluppo - modelli plan-based, metodi agili e ScrumUn processo software è un insieme strutturato di attività (specifica, progetto e realizzazione, validazione, evoluzione) e si descrive con attività, prodotti, ruoli e condizioni; i processi plan-driven pianificano tutto in anticipo e misurano il progresso sul piano (cascata, modello a V con i livelli di test corrispondenti, sviluppo incrementale pianificato, integrazione e configurazione di componenti, con prototipi e consegna incrementale per gestire il cambiamento, e miglioramento del processo con i livelli di maturità SEI), mentre i metodi agili pianificano in modo incrementale e accolgono il cambiamento (manifesto agile, XP con i suoi valori e le sue dodici pratiche, storie dell'utente e story point); Scrum organizza il lavoro in sprint con tre ruoli (Product Owner, Scrum Master, team), eventi (planning, daily scrum, review, retrospective) e artefatti (product backlog, sprint backlog, incremento), stimando con il planning poker; non c'è un processo giusto, ma priorità diverse (stabilità dei requisiti, criticità, dimensione e distribuzione del team).Processi di sviluppo - modelli plan-based, metodi agili e Scrum →):

attività che cosa si decide o si fa
specifica (specification) cliente e ingegneri definiscono che cosa il software deve fare e con quali vincoli (Ingegneria dei requisiti - requisiti funzionali e non funzionaliUn requisito è la descrizione di un servizio o di un vincolo del sistema, scritta ad astrazione diversa per chi decide (requisiti utente, in linguaggio naturale) e per chi realizza (requisiti di sistema, strutturati e dettagliati); si dividono in funzionali (servizi e comportamento), non funzionali (proprietà e vincoli: prodotto, organizzativi, esterni, da scrivere con una misura verificabile) e di dominio; il processo di ingegneria dei requisiti è iterativo (elicitazione con interviste, etnografia, storie e scenari; analisi e negoziazione; specifica; validazione; gestione del cambiamento con identificatori e tracciabilità) e produce il documento dei requisiti, che dice cosa fa il sistema e non come; i modelli UML (contesto, casi d'uso, sequenza, classi, stati) aiutano a chiarire i requisiti.Ingegneria dei requisiti - requisiti funzionali e non funzionali →)
sviluppo (development) progetto e realizzazione (Architettura del software - decisioni, organizzazione e stiliL'architettura del software descrive come un sistema è organizzato in componenti, le loro relazioni e la loro distribuzione; è il ponte tra requisiti e progetto dettagliato, e le sue decisioni derivano soprattutto dai requisiti non funzionali (prestazioni, sicurezza, disponibilità, manutenibilità), spesso in conflitto tra loro; si descrive con più viste (logica, processi, sviluppo, fisica); gli stili di organizzazione ricorrenti sono a livelli (layered), MVC, repository, client-server, pipe and filter, publish-subscribe e microservizi; la modularizzazione mira ad alta coesione e basso accoppiamento, nascondendo le decisioni dietro interfacce (information hiding, inversione delle dipendenze).Architettura del software - decisioni, organizzazione e stili →, Design pattern - introduzione e catalogo GoFUn design pattern è una soluzione generale, riusabile e collaudata a un problema ricorrente di progettazione in un certo contesto, con un nome (che arricchisce il vocabolario dei progettisti), un problema, una soluzione (struttura e partecipanti) e le conseguenze; il catalogo GoF (Gamma, Helm, Johnson, Vlissides) ha 23 pattern in tre famiglie: 5 creazionali (come si creano gli oggetti), 7 strutturali (come si compongono classi e oggetti), 11 comportamentali (come collaborano e si ripartiscono le responsabilità); dietro i pattern ci sono principi come programmare verso un'interfaccia, incapsulare ciò che varia, preferire la composizione all'ereditarietà, aperto/chiuso e inversione delle dipendenze; un pattern si sceglie dal problema, non dal nome, e usarlo senza bisogno complica il progetto.Design pattern - introduzione e catalogo GoF →)
validazione (validation) controllo che il software faccia ciò che serve al cliente (Software testing - progettazione dei casi di test e JUnitIl testing esegue il programma con dati artificiali per mostrare che soddisfa i requisiti (validation testing) e per scoprirne i difetti (defect testing), ma può rivelare la presenza di errori e non la loro assenza; il test di sviluppo comprende unit test (singole classi e metodi), test dei componenti (le interfacce) e test di sistema (le interazioni); i casi di test si ricavano con le classi di equivalenza (una per partizione) e i valori limite (ai bordi delle partizioni), più linee guida per sequenze e input anomali; JUnit automatizza i test con @Test, fixture @Before/@After (@BeforeClass/@AfterClass), le asserzioni (assertEquals con atteso per primo), eccezioni attese, timeout e @Ignore; nel TDD il test si scrive prima del codice (red, green, refactor) e i test automatici diventano la suite di regressione; seguono i test di rilascio (black box sui requisiti, prestazioni) e dell'utente (alpha, beta, accettazione).Software testing - progettazione dei casi di test e JUnit →)
evoluzione (evolution) modifiche successive richieste dal cliente

Principi che valgono qualunque sia il tipo di applicazione:

  • gestione dello sviluppo (un processo adatto a ogni parte, capito da tutti);
  • affidabilità;
  • comprensibilità: specifiche e codice documentati per l'uso previsto;
  • riuso: dove ha senso si riusa software già scritto, che però può non essere documentato per un uso diverso da quello originale, e fa nascere vulnerabilità se fa anche cose non volute. Un modulo si può trattare come black box solo dopo aver controllato che faccia davvero ciò che dichiara.

Cambiamenti che il web ha portato: si vende l'uso del software (servizio, pay-per-use, abbonamenti) e non una copia; dominano riuso, sviluppo incrementale e sistemi orientati ai servizi; HTML5 e AJAX permettono interfacce evolute (vedi Architettura delle applicazioni web - browser, server web e livelliUn'applicazione web è distribuita su più livelli (tier): presentazione (browser: HTML, CSS, JavaScript), logica applicativa (server web e application server) e dati (DBMS). Il client invia richieste HTTP, il server risponde; HTTP è senza stato, quindi lo stato dell'utente viaggia in cookie e sessioni. Il web server serve file statici, l'application server (container, per esempio Tomcat per le servlet Java) esegue codice che genera risposte dinamiche; spesso il web server fa da reverse proxy davanti all'application server. Il browser trasforma i byte della risposta in una pagina: decodifica, tokenizzazione e costruzione del DOM, CSS che diventa CSSOM, render tree, layout (reflow), paint e composizione; un script senza defer o async blocca il parsing, il CSS blocca il rendering. Origine = schema + host + porta; la same-origin policy limita ciò che uno script può leggere da altre origini. Web 1.0: pagine generate dal server, ogni azione ricarica la pagina; Web 2.0: AJAX, contenuti degli utenti, servizi REST; Web 3.0: dati con significato esplicito (RDF, linked data).Architettura delle applicazioni web - browser, server web e livelli →).

Il costo dei difetti, verifica e validazione

Più tardi si scopre un difetto, più costa correggerlo. Il grafico del corso (dati di Boehm e Basili, 2001) mostra un moltiplicatore del costo di correzione che cresce da circa 1 (requisiti) a circa 10 (progetto), 30-60 (realizzazione) fino a 100 (dopo il rilascio):

fase in cui il difetto è scoperto costo relativo di correzione (ordine di grandezza)
requisiti 1
progetto 10
realizzazione 30-60
rilascio 100

È per questo che specifiche chiare e test sin dall'inizio non sono un lusso. Due concetti da non confondere:

Definizione (verifica). Verification: il software è corretto? Soddisfa la specifica? E la specifica è corretta rispetto ai requisiti, assumendo vere le proprietà del dominio?

Definizione (validazione). Validation: i requisiti sono corretti? Sono completi, coerenti, e riflettono davvero il problema del cliente?

Quindi la verifica risponde a "costruiamo il prodotto nel modo giusto?" e la validazione a "costruiamo il prodotto giusto?". Un programma può essere verificato (aderisce alla specifica) e non validato (la specifica non era ciò che l'utente voleva).

Esempio. Una specifica dice: "il sistema calcola la multa in euro". Il codice la implementa senza errori (verifica riuscita). Ma i soci della biblioteca volevano la multa in giorni di sospensione dal prestito: manca la validazione, e il difetto è nei requisiti.

Le tre bestie: incertezza, irreversibilità, complessità

Lo sviluppo software è un insieme di attività mutuamente dipendenti, che si sovrappongono nel tempo e nell'ambito, e una buona organizzazione è la chiave del successo. Tre problemi di fondo, presenti prima, durante e dopo lo sviluppo:

problema prima durante dopo
incertezza (difficoltà di comunicazione tra le parti) i clienti non hanno deciso del tutto cosa vogliono problemi inattesi con i materiali di supporto (per esempio le librerie) difetti e arresti scoperti dagli utenti; cambiare sistema operativo può non essere possibile
irreversibilità (tempo e risorse non recuperabili) la qualità attesa dal cliente non si può ritirare struttura del sistema, test unitari, scelte di pattern e strumenti decisioni su aggiornamenti e livello di supporto
complessità (troppe informazioni insieme) capire il dominio e l'attività del cliente struttura algoritmica, architettura, dimensione del codice gestire l'evoluzione del sistema

Incertezza e irreversibilità rendono difficile il coordinamento: è la ragione per cui i processi iterativi e agili cercano di fare scelte reversibili e di ricevere feedback presto.

Etica professionale

L'ingegneria del software ha un codice etico (ACM/IEEE) con otto principi, ai quali si ispirano decisioni di professionisti, docenti, dirigenti e studenti:

principio contenuto
pubblico agire in modo coerente con l'interesse pubblico
cliente e datore di lavoro agire nell'interesse di entrambi, compatibilmente con l'interesse pubblico
prodotto garantire che prodotti e modifiche rispettino gli standard professionali più alti
giudizio mantenere integrità e indipendenza di giudizio
gestione i responsabili sostengono un approccio etico alla gestione dello sviluppo
professione far avanzare integrità e reputazione della professione
colleghi essere onesti e di supporto verso i colleghi
sé mantenersi aggiornati sulla propria pratica e promuovere un comportamento etico

Responsabilità tipiche: riservatezza verso il cliente, competenza (non millantare capacità), proprietà intellettuale (rispettare leggi e licenze) e uso improprio (non usare le proprie capacità per produrre danni). Rispettare la legge non basta.

Domande d'esame

1. Perché in un progetto software la manutenzione pesa più dello sviluppo, e quali conseguenze ha sulle scelte di progetto? Traccia. Il software vive e cambia con le richieste, l'ambiente e la tecnologia; dopo il rilascio ogni modifica richiede di capire codice e documentazione altrui. Conseguenze: leggibilità prima della compattezza, documentazione, progetto modulare e testato (test di regressione), niente over-engineering iniziale ma struttura che consente di cambiare (Design pattern - introduzione e catalogo GoFUn design pattern è una soluzione generale, riusabile e collaudata a un problema ricorrente di progettazione in un certo contesto, con un nome (che arricchisce il vocabolario dei progettisti), un problema, una soluzione (struttura e partecipanti) e le conseguenze; il catalogo GoF (Gamma, Helm, Johnson, Vlissides) ha 23 pattern in tre famiglie: 5 creazionali (come si creano gli oggetti), 7 strutturali (come si compongono classi e oggetti), 11 comportamentali (come collaborano e si ripartiscono le responsabilità); dietro i pattern ci sono principi come programmare verso un'interfaccia, incapsulare ciò che varia, preferire la composizione all'ereditarietà, aperto/chiuso e inversione delle dipendenze; un pattern si sceglie dal problema, non dal nome, e usarlo senza bisogno complica il progetto.Design pattern - introduzione e catalogo GoF →).

2. Spiegare la differenza tra verifica e validazione con un esempio. Traccia. Verifica: il programma rispetta la specifica ("lo costruiamo bene"); validazione: la specifica corrisponde a ciò che il cliente vuole ("costruiamo il prodotto giusto"). Esempio: la specifica dice multa in euro, il sistema la calcola bene, ma l'utente voleva una sospensione dal prestito: verifica sì, validazione no.

3. Elencare gli attributi di un buon software e dire quale prevale in un sistema embedded di un dispositivo medico. Traccia. Manutenibilità, affidabilità e sicurezza, efficienza, accettabilità. In un dispositivo medico prevalgono affidabilità e sicurezza (un guasto può causare danni fisici, come nel caso Therac-25), che ammette un rilascio più lento e controlli formali; per un'applicazione di intrattenimento prevalgono tempi di rilascio e accettabilità.

Versione ripasso

problema prima durante dopo
incertezza clienti indecisi librerie che sorprendono difetti scoperti dagli utenti
irreversibilità qualità attesa non ritirabile struttura, test unitari, pattern e strumenti supporto e aggiornamenti promessi
complessità dominio del cliente architettura, algoritmi, dimensione evoluzione del sistema

Teoria collegata