cerca
Ingegneria del Software - Appunti del 4 Maggio 2009
modifica cronologia stampa login logout

Wiki

UniCrema


Materie per semestre

Materie per anno

Materie per laurea


Help

Ingegneria del Software - Appunti del 4 Maggio 2009

Torna alla pagina di Ingegneria del Software

 :: Ingegneria del Software - Appunti del 4 Maggio 2009 ::

Architetture di componenti

Le architetture per realizzare fisicamente componenti saranno oggetto di più approfondite lezioni verso la fine del corso, quando studieremo nel dettaglio EJB. Tuttavia, il professore ha deciso di anticiparci qualcosa ora così da comprendere meglio i prossimi argomenti.

Facciamo un riassunto delle puntate precedenti. Lo scopo di suddividere il nostro sw in componenti è quello di avere una successiva facilità di manutenzione, visto che la manutenzione è la parte più costosa del processo di sviluppo del software.

UML ci fornisce alcuni strumenti per pensare ad una suddivisione in componenti; tuttavia si tratta di un'operazione eseguita durante la fase di implementazione e dipendente dal linguaggio e dall'architettura scelta. Esistono degli standard language independent, ma solo in linea di principio.

Quando decidiamo di dividere in componenti, utilizziamo fondamentalmente i criteri di stereotipia e di coesione.

Dynamic Linking

Ma il componente non è solo un modo per catalogare le classi, come potrebbe esserlo uno stereotipo. Oltre a ciò, il componente definisce anche:

  • le unità di distribuzione del software;
  • la tecnica di dynamic linking che andrò poi ad utilizzare.

Sul primo punto, ovvero le unità di distribuzione del software, è già stato detto qualcosa. Al momento dell'installazione, se il nostro software è modulare, è possibile scegliere quale moduli installare e su quali macchine della rete.

Il secondo punto riguarda invece il come i nostri componenti effettivamente comunicheranno. Il linking è quella fase del processo di compilazione in cui i riferimenti che il mio programma fa verso altri programmi o librerie vengono riempiti. Il link dinamico è un link che non viene eseguito del tutto in fase di compilazione, bensì in fase di esecuzione.

Se non ci fosse la possibilità di eseguire un link dinamico non potremmo avere librerie condivise, e tutti i programmi sarebbero dei monoliti giganteschi con, compilate all'interno, tutte le librerie di cui necessitano.

Chi definisce gli standard?

Nel mondo del software, gli standard vengono proposti dalle singole industrie, e se piacciono vengono ratificati. Per esempio, UML è stato inventato dalla Rational, e poi è stato codificato ed ora lo usano tutti.

I grandi componenti del mondo degli standard dei componenti sono:

  • Microsoft
  • Resto del mondo, tramite OMG (Object Management Group, lo stesso che ratifica UML)

Ci sono varie caratteristiche di un'architettura a componenti che portano alla sua eleggibilità a standard. Le vedremo meglio in futuro, ma ricordiamo già da ora la transazionalità, ovvero la proprietà per cui l'esecuzione di diverse operazioni su diversi componenti o va tutta a buon fine, o fallisce completamente e non rimane niente in sospeso o a metà, e la possibilità di installare parti di software su macchine diverse per configurazione hardware e software.

OMG ha prodotto OMA (Object Management Architecture), che è uno standard al quale si rifanno le varie architetture di componenti.

Il Software Bus

Parlavamo prima della necessità di avere un linker dinamico. Concretamente, ciò si realizza mediante un software bus, ovvero un programma il cui scopo è quello di ricevere chiamate dai componenti e di smistarle agli altri componenti, attivandoli e disattivandoli a seconda delle necessità.

Così come un bus fisico, su di una scheda madre, riceve segnali e li smista ai controller giusti, così il bus software riceve messaggi dai componenti, e li smista ai destinatari giusti.

Il primo software bus è stato ORB, ovvero Object Request Broker. A partire da ORB è stato creato lo standard CORBA, cioè Common Object Request Broker Architecture. Noi non lo studieremo, lo ricordiamo solo per motivi storici.

Concettualmente, un software bus ha a che fare con:

  • eventi: sono generati dai componenti, e vengono inoltrati al software bus. Gli altri componenti devono venir notificati dell'accadere di questi eventi
  • naming: l'assegnamento ad ogni componente di una URN (Universal Resource Name) che permette l'identificazione univoca del componente stesso tra tutti gli altri registrati presso lo stesso software bus, così che si possa fare appello al componente giusto.

Le aziende di software inoltre coltivano un sogno segreto, relativo alle funzionalità del software bus. La loro speranza è che il software bus si possa accollare tutte le responsabilità non funzionali di un software, come ad esempio i requisiti di security, di persistenza e così via. Le aziende dovrebbero scrivere codice rispettando i soli requisiti funzionali, e con la serena certezza che tutti i requisiti non funzionali sarebbero stati gestiti ed implementati dal software bus. Pia speranza? Non lo sappiamo ancora.

Quando si sente parlare di middleware, altro non è che un appellativo del software bus: è "middle" perché sta in mezzo a tutti.

EJB

Enterprise Java Beans è l'architettura a componenti di Java. È strettamente language dependent, essendo stata pensata appositamente per Java.

L'idea su cui si sono basate le prime versioni di EJB era quella di avere client leggeri e server pesanti. La "pesantezza" riguardava due fronti: quello delle responsabilità, e quello del carico di CPU. Sulle macchine server dovrebbero girare degli application server, ai quali, nel corso degli anni, sono state demandate sempre più responsabilità, secondo la visione delle aziende illustrata prima.

Tuttavia, ci si è resi conto che proprio a livello di CPU il carico di lavoro per una singola macchina era un po' troppo, e ultimamente si sta facendo marcia indietro. Ricordo che la Sun qualche anno fa aveva lanciato dei thin client, ma non hanno avuto molto successo. L'impiegato medio non vuole un pc poco potente connesso ad un server centrale, perché non può giocare ad Open Arena nelle pause.

EJB ci offre un mare infinito di sigle, in puro stile Java. Le cito qui, senza svilupparle troppo:

  • JNDI = il servizio di naming
  • JDBL = connettività a database
  • JTA e JTS = transactional API e Service
  • JPS = Java Server Pages, ovvero la possibilità di eseguire programmi Java come script per pagine dinamiche

COM e DCOM

L'architettura COM è stata l'architettura principale di Windows per diversi anni. Da essa è nata DCOM, che altro non è che Distributed COM.

COM nasce da OLE, ovvero Object Linking ad Execution. OLE forniva, ai tempi, quella tanto decantata possibilità di linkare ad esempio un foglio di Excel in una pagina Word, e di farlo funzionare come se fossimo stati direttamente in Excel. Molti Office fa ciò era una sorta di miracolo, un passo in più rispetto al copia & incolla.

COM fornisce anche uno standard binario: le DLL per le librerie sulla stessa macchina, e le OCX per le librerie chiamate in una rete.

Un oggetto COM ha dei metodi e uno stato. Internamente è composto di classi. Ha almeno un'interfaccia esterna, chiamata IUnknown', che deve essere sempre presente. Quando chiamo un COM, posso chiamare la sua interfaccia IUnknown e farmi dire quali sono le altre interfacce, e che metodi esse hanno. Questo è comodo perché non sono obbligato a conoscere l'interfaccia di un metodo prima di chiamarlo. Ovvio che se invece la conosco salto l'intervista ad IUnknown e vado avanti direttamente.

COM offre inoltre la possibilità di estendere un'interfaccia, aggiungendo nuove funzionalità. Nel mondo della teoria, quando aggiorno un COM, dovrei tenere quello vecchio ed estenderlo con un COM nuovo. In questo modo, le applicazioni vecchie che facevano appello al COM vecchio potranno continuare a funzionare, mentre le applicazioni nuove potranno usare il COM nuovo.

Ma non è così.

Spesso, troppo spesso, le versione nuove sono andate a sostituire in toto le versioni vecchie. Espongono più o meno la stessa interfaccia, ma non le stesse funzionalità. Pertanto, accade che un'applicazione possa esplodere improvvisamente quando chiama un COM che all'apparenza è uguale a quello di cui lei ha nozione, ma che ritorna valori un po' diversi. Inoltre, diversi COM presentavano interfacce non documentate, e diverse applicazioni le utilizzavano.

Questo è il motivo per cui, dopo un po' di tempo, il sistema Windows diventa instabile. Windows non ha alcun modo per verificare quali versioni dei COM vengono installate, e quali versioni di un determinato COM sono desiderate da un'applicazione.

.NET

.NET sostituisce COM. Non è più basato sul codice nativo, ma sul bytecode, un po' come Java. Il problema di chiamate a COM misteriosi che fanno crashare tutto viene notevolmente ridimensionato, perché all'interndo di una virtual machine è più facile recuperare dagli errori senza far morire l'applicazione.

.NET in realtà presenta una Common Language Runtime, che è il target di destinazione per diversi linguaggi di programmazione, attraverso uno stack predefinito di librerie. Quando si compila un programma, questo viene tradotto in un linguaggio intermedio detto MSIL (Microsoft Intermediate Language), e poi compilato tramite tecniche JIT (Just In Time, cioè la prima volta che viene chiamato).

Il diagramma dei componenti

Dopo questa carrellata sulle architetture di componenti, dobbiamo ricordare che nei diagrammi di componenti non si mettono le proprietà del middleware che abbiamo appena visto. Al massimo, nelle note che lo corredano scriverò che useremo EJB, ad esempio, e poi se la vedranno i programmatori.

Va poi notato che questo trasferimento di responsabilità dal software al middleware si traduce in un parallelo trasferimento di reponsabilità dal programmatore al sistemista, e per questo motivo occorrono sistemisti in gamba.

Il Testing

In questa lezione anticipiamo alcuni dei temi importanti del testing, che saranno affrontati a partire dalla lezione di domani.

La prima cosa da dire è che nelle grandi imprese esiste la figura del tester, che è distinta da quella del programmatore. Certo, possono anche coincidere, e spesso è così, ma questa informazione serve per ricordarci che il testing è un'attività con una sua peculiarità ed importanza, che merita tutta l'attenzione necessaria.

I piani di test sono documenti che vengono prodotti già dalla fase di analisi. Alla fine di ogni iterazione si andrà a verificare sul codice il test preparato durante questi momenti, oltre a tutti quei test che le fasi successive hanno prodotto.

Queste righe ci servono per introdurre un concetto fondamentale: il testing non è una fase del processo di sviluppo. Non è una fase a se stante, bensì un'attività complementare o ortogonale a tutte le altre fasi di sviluppo. Ogni fase ha i suoi test specifici.

Modelli e testing

Abbiamo blaterato a lungo sulla bellezza dei modelli, dovuta al fatto che tramite la semantica ci permettono di dimostrare in modo automatico certe proprietà del software.

Tuttavia, questo non basta per il testing. Tramite un modello posso garantire, se ad esempio sto usando le Reti di Petri, proprietà come temporizzazione e sincronizzazione, ma non posso dire nulla dei requisiti funzionali. Temporizzazione e sincronizzazione sono infatti requisiti non funzionali. Per quelli funzionali, è necessario il testing.

Errori e statistica

Una proprietà simpatica degli errori è che non sono distribuiti secondo le distribuzioni statistiche che siamo abituati a vedere. Le distribuzioni statistiche "solite" possono essere riassunte da un valore, che è la media.

Nelle creazioni sociali, come ad esempio il software, che è scritto da uomini, parlare di media non serve a niente. Una migliore approssimazione statistica è quella di Pareto, che dice riassuntivamente che l'80% degli eventi è generato dal 20% degli attori.

Traducendo questo dato nella distribuzione degli errori, possiamo dire che l'80% degli errori in un programma (vedremo domani come categorizzare gli errori) sarà presente nel 20% dei moduli del nostro software.

In virtù di tale proprietà, discorsi come "difettosità media per riga di codice" non hanno molto senso, proprio perché la media non è un valore tipico della distribuzione che modella le attività umane.

Un'altra implicazione di questa proprietà è che è praticamente inutile fare test esaustivi (oltre che impossibile). Testare tutti i possibili valori di input nell'80% dei casi non porterà a nulla. Occorre invece essere più furbi ed individuare il 20% di codice dove si annideranno gli errori. Come? Vedremo.

Annunciamo sin da ora che un test esaustivo, oltre che inutile perché in contrasto con la distribuzione di Pareto, è anche nella maggioranza dei casi semplicemente inattuabile. Per fare un esempio, se devo testare una funzione che prende in input tre valori, ciascuno di tipo integer, dovrei provare tutte le combinazioni di tre interi per avere un caso di test esaustivo... Quindi, no.


Torna alla pagina di Ingegneria del Software