1. Ingegneria del software: visione d'insieme
L'ingegneria del software è il settore dell'informatica che si occupa della creazione di sistemi software talmente grandi o complessi da dover essere realizzati da una o più squadre di ingegneri. Di solito questi sistemi esistono in varie versioni e rimangono in servizio per parecchi anni. Durante la loro vita subiscono numerose modifiche: per eliminare difetti, per potenziare caratteristiche già esistenti, per implementarne nuove, per eliminare quelle obsolete, o per essere adattati a funzionare in un nuovo ambiente. Possiamo definire l'ingegneria del software come "l'applicazione dell'ingegneria al software". Più precisamente, l'IEEE Standard 610.12-1990 Glossario standard della terminologia dell'ingegneria del software (ANSI) definisce l'ingegneria del software come l'applicazione di un approccio sistematico, disciplinato e quantificabile nello sviluppo, funzionamento e manutenzione del software. Parnas [1978] ha definito l'ingegneria del software come la "creazione di software multiversione da parte di più operatori". Questa definizione mette in risalto l'essenza dell'ingegneria del software e sottolinea le differenze tra programmazione e ingegneria del software. Un programmatore scrive un programma completo, mentre un ingegnere del software scrive un componente software che sarà poi combinato con componenti scritti da altri ingegneri del software per creare un sistema. Il componente scritto da un ingegnere del software può essere modificato da altri ingegneri del software e potrà essere usato da altri per creare versioni diverse del sistema, anche se il suo creatore ha abbandonato il progetto da tempo.
La programmazione è un'attività individuale mentre l'ingegneria del software è essenzialmente un'attività di gruppo.
Di fatto, la locuzione "ingegneria del software" fu coniata verso la fine degli anni Sessanta quando ci si rese conto che tutto ciò che si era appreso riguardo alle corrette tecniche di programmazione non aiutava a costruire sistemi software migliori.
Mentre il campo della programmazione aveva fatto progressi straordinari - attraverso lo studio sistematico di algoritmi e strutture dati e l'invenzione della "programmazione strutturata"- esistevano ancora notevoli difficoltà nella creazione di sistemi software complessi. Le tecniche utilizzate da un fisico per scrivere un programma di calcolo per la soluzione di un'equazione differenziale, necessaria per un esperimento, non erano adeguate per un programmatore che, all'interno di un gruppo, cercava di creare un sistema operativo o un sistema di gestione degli inventari.
In questi casi complessi era necessario un classico approccio ingegneristico: definire chiaramente il problema e quindi sviluppare e usare strumenti e tecniche per risolverlo. Indubbiamente, l'ingegneria del software ha fatto progressi dagli anni Sessanta a oggi. Sono state create tecniche standard e, più che essere praticata come un qualcosa di artigianale, l'ingegneria del software ha acquisito una maggiore disciplina, cosa che è tradizionalmente associata con l'ingegneria.
Nonostante ciò, le differenze con l'ingegneria tradizionale permangono.
Nel disegnare un sistema elettrico, come ad esempio un amplificatore, l'ingegnere elettronico può descrivere il sistema in modo preciso. Tutti i parametri e i livelli di tolleranza sono stabiliti chiaramente e sono compresi facilmente e in modo chiaro sia dall'ingegnere sia dal cliente. Nei sistemi software tali parametri rimangono ancora sconosciuti. Non sappiamo ancora quali parametri specificare e come specificarli. Nelle discipline ingegneristiche classiche, l'ingegnere ha strumenti e competenze matematiche per descrivere le caratteristiche del prodotto, separatamente da quelle del progetto. Per esempio, un ingegnere elettronico può fare affidamento sulle equazioni matematiche per verificare che un progetto non violerà requisiti di alimentazione. Nell'ingegneria del software, tali strumenti matematici non sono ancora ben sviluppati e si sta tuttora discutendo sulla loro reale applicabilità .
L'ingegnere del software si appoggia più sull'esperienza e sul suo giudizio personale che su tecniche matematiche.
Viceversa, mentre esperienza e giudizio sono necessari, anche strumenti di analisi formale sono essenziali nella pratica dell'ingegneria. Questo libro presenta l'ingegneria del software come una disciplina ingegneristica, evidenziando determinati principi che riteniamo essere essenziali alla "creazione di software multiversione da parte di più persone". Questi principi sono molto più importanti di qualsiasi particolare notazione o metodologia per costruire software e permettono all'ingegnere del software di valutare differenti metodologie e usarle al momento opportuno. Il Capitolo 3 analizza i principi dell'ingegneria del software; quelli successivi mostrano invece la loro applicazione nei vari ambiti della disciplina. In questo capitolo passiamo in rassegna l'evoluzione dell'ingegneria del software e le sue relazioni con altre discipline.
Scopo di questo capitolo è offrire uno scorcio sul settore dell'ingegneria del software.
Il ruolo dell'ingegneria del software nel progetto di un sistema
Un sistema software è spesso un componente di un sistema più vasto. L'ingegneria del software è quindi parte di un'attività di progettazione di sistema molto più vasta in cui i requisiti del software interagiscono con i requisiti di altre parti del sistema durante la progettazione. Ad esempio, un sistema telefonico è composto da computer, telefoni, linee telefoniche e cavi, altri componenti hardware come i satelliti e infine da software che controlla i vari componenti. E' la combinazione di tutti questi componenti che deve soddisfare i requisiti del sistema. Requisiti come "il sistema non deve essere inattivo per più di un secondo in venti anni" o "quando una cornetta telefonica viene alzata, il segnale di linea libera viene attivato entro mezzo secondo" possono essere soddisfatti con una combinazione di hardware, software e dispositivi speciali. La decisione su come meglio soddisfare i requisiti implica numerosi compromessi. I sistemi delle centrali elettriche o di monitoraggio del traffico, sistemi bancari o di amministrazione ospedaliera sono altri esempi, che mostrano la necessità di vedere il software come un componente di un più ampio sistema. Il software diviene sempre di più la parte interna intelligente di vari sistemi, dalle televisioni agli aeroplani. Si parla, in tal caso, di software embedded. Avendo a che fare con tali sistemi, l'ingegnere del software deve rivolgere uno sguardo più ampio al problema più generale dell'ingegneria di sistema. Ciò richiede che l'ingegnere del software partecipi allo sviluppo dei requisiti di tutto il sistema e che questi comprenda l'area applicativa prima di iniziare a pensare quali interfacce astratte debbano essere soddisfatte dal software. Ad esempio, se il dispositivo hardware che svolge funzione di interfaccia con l'utente ha capacità limitate per l'input di dati, nel sistema non risulterà necessario un word processor sofisticato. Considerare l'ingegneria del software come parte dell'ingegneria dei sistemi ci fa riconoscere l'importanza del compromesso, che è la caratteristica di ogni disciplina ingegneristica. Una classico compromesso concerne la scelta di ciò che deve essere trasformato in software e ciò che deve essere trasformato in hardware. L'implementazione in software offre maggiore flessibilità , mentre l'implementazione in hardware offre migliori prestazioni. Ad esempio, nel Capitolo 2 vedremo un esempio di macchina operante a gettoni che può essere costruita sia con varie fessure, ciascuna per un tipo diverso di gettone, sia con una sola fessura, lasciando al software il compito di riconoscere i diversi gettoni. Un compromesso ancora più di base implica la decisione su cosa debba essere automatizzato e cosa debba essere eseguito manualmente.
Breve storia dell'ingegneria del software
La nascita e l'evoluzione dell'ingegneria del software come disciplina nel campo dell'informatica risale alla maturazione dell'attività di programmazione. Agli albori dell'informatica, il problema principale della programmazione era come mettere insieme una sequenza di istruzioni per fare in modo che il computer producesse qualcosa di utile. I problemi che venivano programmati erano ben compresi e conosciuti: ad esempio, come risolvere un'equazione differenziale. Il programma era scritto, ad esempio, da un fisico per risolvere un'equazione di proprio interesse e il problema era circoscritto tra il computer e l'utente-programmatore; nessun'altra persona era coinvolta. Con la diminuzione dei prezzi dei computer e la loro diffusione, un numero sempre maggiore di persone iniziarono a utilizzarlo. I linguaggi di alto livello furono inventati nei tardi anni Cinquanta per rendere più facile comunicare con le macchine, ma nonostante ciò, l'attività di far eseguire al computer qualcosa di utile rimaneva sempre essenzialmente il compito di una sola persona che doveva scrivere un opportuno programma per un ben determinato compito.
In questo periodo, "programmare" divenne una professione: una persona poteva chiedere al programmatore di scrivere un programma, invece di realizzarlo per conto proprio.
Questo accordo introdusse una separazione tra utente e computer: l'utente specificava cosa voleva ottenere da una data applicazione utilizzando un linguaggio diverso dalla notazione di programmazione. Il programmatore leggeva quindi tale specifica e la traduceva in un insieme ben preciso di istruzioni macchina. Questo, ovviamente, portò a volte a un'interpretazione erronea delle intenzioni dell'utente da parte del programmatore, anche per problemi relativamente semplici. Nei primi anni Sessanta i progetti di software complessi furono veramente pochi e quei pochi furono intrapresi da pionieri del campo dell'ingegneria, che erano molto esperti. Ad esempio, il sistema operativo CTSS sviluppato al MIT fu un progetto vasto, sviluppato da individui estremamente motivati e competenti. Nella seconda parte degli anni Sessanta, vi furono tentativi di creare grandi sistemi di software commerciali. Di questi progetti, quello meglio documentato fu il sistema operativo OS 360 per la famiglia di computer IBM 360. Le persone che lavoravano in questi ambiti presto si resero conto che costruire vasti sistemi di software era decisamente diverso rispetto a quelli più piccoli. Vi erano delle difficoltà profonde nel cercare di adattare le tecniche di sviluppo di piccoli programmi allo sviluppo di grossi software. L'espressione "ingegneria del software" fu coniata in questo specifico periodo e vennero tenute conferenze per discutere sui problemi che tali progetti incontravano nello sviluppare il prodotto promesso. I progetti di grossi software, generalmente, sforavano il budget prefissato ed erano in ritardo rispetto al termine previsto. Un'altra locuzione coniata in quel periodo fu "crisi del software". Si comprese che i problemi riscontrati nella creazione di grandi sistemi software non riguardavano la pura e semplice capacità di combinare tra loro le istruzioni del computer. Piuttosto, i problemi che dovevano essere risolti non erano ben compresi, almeno non da tutte le persone coinvolte nel progetto. Coloro che lavoravano sul progetto spendevano molto più tempo per comunicare tra di loro che per scrivere codice. Addirittura a volte alcune persone abbandonavano il progetto, influenzando così non solo il lavoro che avevano fatto, ma anche il lavoro di quanti facevano affidamento su di loro. Sostituire un individuo richiedeva il trasferimento orale dei requisiti del progetto e del modello del sistema. Ci si rese conto che ogni cambiamento dei requisiti del sistema originale influenzava numerose parti del progetto, ritardando inoltre la consegna del sistema. Questo tipo di problemi non esisteva ai tempi della "programmazione" e richiedeva quindi un nuovo tipo di approccio. Vennero proposte e provate numerose soluzioni. Alcuni suggerirono un miglioramento delle tecniche di organizzazione e gestione dei progetti; altri proposero una diversa organizzazione in squadre. Altri ancora sostennero che fossero necessari linguaggi di programmazione e strumenti migliori; molti auspicarono l'adozione di standardizzazioni, come ad esempio convenzioni di codifica uniformi. Alcuni invocarono l'uso di un approccio formale e matematico. Di certo non mancavano le idee. Alla fine si raggiunse un accordo sul fatto che il problema della costruzione di un software dovesse essere affrontato nello stesso modo adottato dagli ingegneri per costruire sistemi grandi e complessi come ponti, raffinerie, fabbriche, navi e aeroplani. Lo scopo era vedere il sistema software finale come un prodotto complesso e la sua creazione come un lavoro ingegneristico. L'approccio ingegneristico richiedeva management, organizzazione, strumenti, teorie, metodologie e tecniche. Nacque così l'ingegneria del software. In un articolo ormai classico del 1987, Brooks, parafrasando Aristotele, sostenne che vi erano solo due tipi di sfide nello sviluppo del software: l'essenziale e l'accidentale (vedi Letture). Le difficoltà accidentali sono quelle che riguardano gli strumenti e le tecnologie correnti: ad esempio, i problemi sintattici che sorgono dal linguaggio di programmazione utilizzato. Si possono superare tali difficoltà con strumenti e tecnologie migliori. Le difficoltà essenziali, invece, non sono generalmente superate con l'uso di nuovi strumenti. Problemi di progettazione complessa — ad esempio, la creazione di un modello utile per le previsioni atmosferiche o per l'economia - richiedono sforzo intellettuale, creatività e tempo. Brooks sostenne che non vi era alcuna soluzione magica, nessun "proiettile d'argento" (Il proiettile d'argento è quello che uccide il lupo mannaro) per risolvere i problemi essenziali incontrati dagli ingegneri del software. Il ragionamento di Brooks mostra le false supposizioni dietro alla locuzione "crisi del software", che venne ideata in quanto i progetti di software erano continuamente in ritardo e oltre il budget prestabilito. La conclusione fu che il problema era temporaneo e avrebbe potuto essere risolto con strumenti e tecniche di management migliori. In realtà i progetti erano in ritardo perché l'applicazione era complessa e scarsamente compresa sia dai clienti che dagli sviluppatori e nessuno aveva alcuna idea di come stimare la difficoltà del lavoro e di quanto tempo ci sarebbe voluto per risolverlo. Nonostante l'espressione "crisi del software" sia talvolta ancora usata, è opinione generale che le difficoltà inerenti lo sviluppo di software non siano di breve periodo. Applicazioni nuove e complesse sono ardue da affrontare e non sono di rapida soluzione. La storia mostra la crescita dell'ingegneria del software partendo dalla programmazione. Alcune evoluzioni tecnologiche hanno giocato un ruolo importante nello sviluppo della disciplina. L'influenza maggiore è stata il cambio di equilibrio tra i costi dell'hardware e del software. Mentre un tempo il costo di un sistema computerizzato era determinato soprattutto dal costo dell'hardware, e il software era un fattore ininfluente, ora la componente software è di gran lunga quella dominante nel costo di un sistema. La diminuzione del costo dell'hardware e la crescita di quello del software ha sfavorito quest'ultimo, accentuando l'importanza economica dell'ingegneria del software. Un altro orientamento evolutivo si è creato anche all'interno del campo stesso. Vi è stata una crescente enfatizzazione a vedere l'ingegneria del software come una disciplina che va ben oltre la pura attività di codifica. Al contrario, il software viene considerato un prodotto che ha un intero ciclo di vita, partendo della sua concezione, continuando attraverso la progettazione, lo sviluppo, la messa in funzione, la manutenzione e l'evoluzione.
Lo spostamento dell'enfasi dalla codifica all'intero ciclo di vita del software ha favorito lo sviluppo di metodologie e di sofisticati strumenti a sostegno delle squadre coinvolte.
Per parecchie ragioni, possiamo quindi pensare che l'importanza dell'ingegneria del software continuerà ad aumentare. Una prima ragione è di tipo economico: le spese mondiali per il software sono passate da 140 miliardi di dollari nel 1985 a 800 miliardi di dollari nel 2000. Solo questo basterebbe a dimostrare quanto l'ingegneria del software crescerà come disciplina. In secondo luogo, il software ormai permea la nostra società : sempre di più viene usato software per controllare funzioni critiche di varie macchine, come aeroplani e dispositivi medici, e per supportare funzioni di importanza critica per il mondo intero, come ad esempio il commercio elettronico. Questo fatto garantisce una crescente attenzione della società verso software affidabile, al punto da promulgare legislazioni su standard specifici, requisiti e procedure di certificazione. Senza dubbio, imparare a creare software migliore in modo migliore continuerà a essere fondamentale.
Il ruolo dell'ingegnere del software
L'evoluzione dell'ingegneria del software ha portato a definire il ruolo professionale dell'ingegnere del software, l'esperienza e la formazione richiesta. L'ingegnere del software deve, ovviamente, essere un buon programmatore, molto versato in strutture dati e algoritmi ed esperto in uno o più linguaggi di programmazione. Questi sono dei requisiti per la cosiddetta "programmazione in piccolo", definita approssimativamente come la creazione di programmi che possono essere scritti interamente da un unico individuo. Ma un ingegnere del software è coinvolto anche nella "programmazione in grande", che richiede molte più abilità . L'ingegnere del software deve aver familiarità con più approcci di progetto, deve essere capace di tradurre richieste e desideri vaghi in precise specifiche, e anche di interagire con l'utente di un sistema nei termini dell'applicazione più che nel gergo tecnico degli informatici. Queste capacità richiedono a loro volta flessibilità e apertura mentale per comprendere e familiarizzare con i fondamenti delle differenti aree applicative. L'ingegnere del software deve essere capace di muoversi attraverso diversi livelli di astrazione nei diversi stadi del progetto, dalle procedure applicative e dai requisiti di una specifica applicazione, alle astrazioni per il sistema software, a uno specifico progetto del sistema fino al livello dettagliato della codifica in un linguaggio di programmazione. Come in molti altri campi dell'ingegneria, l'ingegnere del software deve sviluppare capacità che gli permettano di costruire una vasta varietà di modelli e di ragionare su questi per poter operare scelte riguardo ai vari compromessi (trade-off) che si incontrano nel processo di sviluppo del software.
I modelli usati nella fase di definizione dei requisiti sono diversi da quelli usati nella progettazione dell'architettura software, i quali a loro volta sono diversi da quelli usati nella fase di implementazione.
In alcuni momenti, il modello può essere usato per rispondere a domande sia sul comportamento del sistema che sulle sue prestazioni. L'ingegnere del software è anche un membro di un gruppo di lavoro e necessita quindi di capacità comunicative e interpersonali. Deve inoltre essere in grado di coordinare il lavoro, sia il proprio sia quello di altri. Come già detto in precedenza, un ingegnere del software ha molteplici responsabilità . Sovente, molte organizzazioni dividono le responsabilità tra vari specialisti con qualifiche differenti. Ad esempio, un analista di sistema ha la responsabilità di ricavare i requisiti, di interagire con il cliente e di comprendere l'area applicativa, mentre un analista di prestazioni ha la responsabilità di analizzare le prestazioni del sistema. A volte lo stesso ingegnere svolge ruoli differenti in momenti differenti del progetto o in progetti differenti.
Il ciclo di vita del software
Il software subisce uno sviluppo e un'evoluzione, dall'idea iniziale di un possibile prodotto o sistema software fino a quando viene implementato e consegnato al cliente (e anche in seguito) . Si dice che il software ha un ciclo di vita composto da varie fasi. A ciascuna di queste è associato lo sviluppo di una parte del sistema o di qualche elemento a questo legato come, ad esempio, un manuale per l'utente o un piano di test. Nel modello tradizionale del ciclo di vita, chiamato "modello a cascata", ogni fase ha un inizio e una fine ben definiti, con dei risultati parziali ("artefatti") che vengono trasferiti a una ben identificata fase successiva. Raramente però nella realtà le cose sono così semplici. Un modello a cascata è composto dalle seguenti fasi.
Analisi e specifica dei requisiti. L'analisi dei requisiti è di solito la prima fase di un
progetto per lo sviluppo di un software di grandi dimensioni. Viene intrapresa dopo
aver compiuto uno studio di fattibilità per definire in modo preciso i costi e i benefici
di un sistema software con lo scopo di identificare e documentare i requisiti del sistema.
Questo studio può essere compiuto dal cliente, dallo sviluppatore, da specialisti
nell'analisi di mercato o da una qualsiasi combinazione di questi. Nei casi in cui i
requisiti non siano chiari (per esempio, un nuovo sistema che non è mai stato precedentemente
realizzato), è necessario che vi sia ampia interazione tra il cliente e lo sviluppatore.
In questa fase i requisiti dovrebbero essere scritti impiegando una terminologia
comprensibile dall'utente finale, ma molte volte non è cosi. Numerose metodologie
di ingegneria del software ritengono che questa fase debba anche portare alla creazione
di manuali per gli utenti e alla progettazione dei test di sistema che saranno effettuati
alla fine, prima della consegna del sistema.
Progettazione di sistema e sua specifica. Una volta che i requisiti del sistema sono
stati documentati, gli ingegneri del software progettano un sistema software che li soddisfi.
Questa fase è a volte divisa in due sottofasi: il progetto architetturale, o di alto livello,
e il progetto dettagliato. Il progetto architetturale affronta l'organizzazione globale
del sistema in termini di componenti di alto livello e delle loro interazioni. Man
mano che ci si addentra in livelli di progettazione sempre più dettagliati, i vari componenti
vengono scomposti in moduli di basso livello, con interfacce definite in modo
accurato. Tutti i livelli di progettazione vengono indicati in un documento di specifica
che fornisce informazioni sulle decisioni di progettazione prese.
La separazione della fase di analisi dei requisiti da quella di progettazione è un perfetto
esempio della fondamentale dicotomia tra "che cosa" e "come" che spesso incontriamo
nell'informatica. Il principio generale consiste nel fare una chiara distinzione
tra che cosa è il problema e come si risolve tale problema. In questo caso, la fase di analisi
dei requisiti cerca di specificare qual è esattamente il problema. E per questo che
diciamo che i requisiti dovrebbero essere specificati in base alle esigenze dell'utente finale.
Di solito vi sono numerosi modi di soddisfare i requisiti, a volte ricorrendo anche
a soluzioni manuali che non richiedono l'uso del computer. Lo scopo della fase di
progettazione è quella di giungere alla specifica di un particolare sistema software che
soddisfi i requisiti dichiarati. Anche in questo caso, vi sono numerosi modi per creare
il sistema specificato. Nella fase di codifica, che segue quella di progettazione, un preciso
sistema viene codificato per soddisfare le specifiche di sistema. Nel prosieguo del
libro vedremo altri esempi della dicotomia del che "cosa/come".
Codifica e test di modulo. In questa fase, l'ingegnere produce il codice che sarà consegnato
al cliente sotto forma di un sistema funzionante. Anche altre fasi del ciclo di
vita del software potranno portare allo sviluppo di codice, ad esempio per effettuare
test, per sviluppare prototipi e test driver, ma in tutti questi casi il codice sviluppato
sarà impiegato solamente dagli sviluppatori. Bisogna notare che i singoli moduli creati
nella fase di codifica vengono testati prima di passare a quella successiva.
Figura 1.1 Modello del ciclo di vita a cascata del software.
Integrazione e test di sistema. Tutti i moduli sviluppati e testati singolarmente nella fase precedente vengono in questa assemblati, integrati, e testati come un unico sistema.
Consegna e manutenzione. Una volta che il sistema supera tutti i test, viene consegnato al cliente ed entra nella fase di manutenzione in cui vengono incorporate tutte le modifiche apportate al sistema dopo la prima consegna.
La Figura 1.1 offre una visione grafica del ciclo di vita dello sviluppo di un software e offre un chiarimento grafico del termine "cascata". Ciascuna fase crea risultati che "fluiscono" in quella seguente e idealmente il processo procede in modo ordinato e lineare. Per come è stato qui presentato, questo modello offre una visione parziale e semplificata del convenzionale ciclo di vita a cascata del software. Il processo può essere scomposto in un diverso insieme di fasi, con nomi diversi, diversi fini e una diversa granularità . Potrebbero anche essere proposti schemi di cicli di vita totalmente differenti, non basati strettamente su fasi sviluppate in cascata. Ad esempio, è ovvio che se un test dovesse scoprire i difetti del sistema, occorrerebbe tornare indietro almeno fino alla fase di codifica e forse anche a quella di progettazione per correggere gli errori. In generale, ogni fase potrebbe portare alla luce problemi nelle fasi antecedenti, e quando ciò dovesse accadere sarebbe necessario tornare indietro e rifare parte del lavoro già svolto. Ad esempio, se la fase di progettazione del sistema scopre inconsistenze o ambiguità nei requisiti del sistema, si dovrà riesaminare la fase di analisi dei requisiti per determinare quali erano quelli che realmente si intendeva specificare e quali quelli errati. Un'altra semplificazione fatta con la precedente rappresentazione del ciclo di vita a cascata riguarda il fatto che si suppone che ciascuna fase sia completata prima che la successiva abbia inizio. Nella pratica invece, è spesso vantaggioso iniziare una fase prima che la precedente sia finita. Questo può succedere, ad esempio, se dei dati necessari al completamento della fase di specifica dei requisiti non sono disponibili per un po' di tempo. Oppure può essere necessario perché le persone pronte a iniziare la fase successiva sono disponibili e non hanno altri lavori da fare. Un'altra ragione per sovrapporre le varie fasi può anche essere la riduzione del tempo di lancio del prodotto. Il termine usato per definire tale moderno processo organizzativo, che cerca di abbreviare il tempo di consegna dei prodotti introducendo del parallelismo nelle fasi di sviluppo di processi precedentemente sequenziali è ingegneria concorrente.
Last updated