Dr. Carlo Pescio
Introduzione della Tecnologia ad Oggetti in Azienda: Gestione e Prevenzione dei Rischi

Presentato alla conferenza "Tecnologia ad Oggetti per l'Industria", Firenze, 1996.


Alcune malattie al loro inizio sono facili da curare, ma difficili da riconoscere... ma nel corso del tempo... diventano facili da riconoscere ma difficili da curare
Niccolò Machiavelli

Introduzione
La tecnologia object oriented può portare benefici concreti, ma la sua introduzione nelle aziende non è priva di rischi, spesso ignorati o sottovalutati. Nella mia attività di consulente ho potuto notare come molte delle difficoltà siano tutto sommato comuni ed abbiano sintomi simili anche in realtà diverse; in più occasioni ho avuto modo di sperimentare dei rimedi, delle strategie di prevenzione e delle tattiche per gestire i problemi. In molti casi i fattori sono di tipo umano, e si possono riscontrare in altre situazioni di paradigm shift; in altri casi si tratta di problemi più strettamente tecnici e legati in modo specifico alla tecnologia ad oggetti. In quanto segue discuterò alcuni tra i rischi più comuni e le strategie che ho adottato con successo in diversi progetti; ho ritenuto opportuno porre la giusta enfasi sulle cause, e non solo sui possibili rimedi, in modo da stimolare anche la ricerca e la sperimentazione di metodi alternativi di prevenzione, diagnosi, e gestione dei rischi.

Superare il mito degli oggetti, ovvero: scegliere la tecnologia ad oggetti per le giuste ragioni.
Vi sono ottime ragioni per passare al paradigma object oriented: una migliore integrazione tra fase di analisi, design e codifica, sia a livello di terminologia che di metodi di astrazione, la possibilità di incrementare il riuso del codice e del design, una più ampia gamma di tecniche per ridurre la complessità dello sviluppo, minori problemi di manutenzione ed upgrade del software. Su queste basi molti produttori di tool, ma anche professionisti e ricercatori, hanno però talvolta costruito false promesse di immediati salti di produttività, prospettive di riuso inverosimili, ed il mito di un "object nirvana" che è responsabile di fallimenti e delusioni. Non è inusuale vedere team di sviluppo cui viene ordinato di passare all'OOP dall'upper management, a seguito della lettura di un articolo o della dimostrazione di un CASE tool e delle conseguenti, mirabolanti promesse. È questo il primo rischio cui si va incontro nell'introduzione della tecnologia object oriented nelle aziende: assumere che gli oggetti portino un immediato beneficio sui tempi di sviluppo; la sua diagnosi è relativamente semplice, essendo il sintomo principale un generale scontento dell'upper management: gli oggetti avrebbero dovuto far risparmiare tempo e risorse, mentre gli sviluppatori non solo non stanno procedendo alla velocità sperata, ma si trovano spesso bloccati in situazioni difficili da districare. In realtà va chiarito sin dall'inizio che gran parte dei benefici degli oggetti sono a medio/lungo termine: i primi progetti richiederanno maggiore sforzo, e presumibilmente anche maggiori investimenti economici in tempo di sviluppo, tool e formazione, mentre più avanti gli sviluppatori saranno in grado di riutilizzare codice e design precedenti, ed aumentare così la produttività e la qualità del prodotto; anche la manutenzione e la ristrutturazione verranno semplificate, ma ciò non sarà visibile nelle prime fasi dello sviluppo. Un effetto collaterale dell'eccessivo ottimismo iniziale è anche una generale riluttanza ad investire in formazione, o ad assumere personale già esperto nell'analisi, design e programmazione object oriented; non di rado, questo approccio è inizialmente supportato dagli stessi programmatori, che per molte ragioni sono piuttosto portati ad affermazioni del tipo "ovviamente sono in grado di imparare il C++ da solo, in poco tempo". Se da un lato ciò è ragionevole, perché il C++ è sostanzialmente un sopra-insieme del C, dall'altro un simile approccio, diretto peraltro dal basso (codifica) verso l'alto (design, analisi), si rivela quasi sempre fallimentare: come consulente, ho spesso occasione di rivedere il risultato dei team che hanno seguito tale percorso, e l'inevitabile conclusione è che la struttura del programma non è assolutamente orientata agli oggetti, e che ben pochi (o nessuno) dei benefici promessi dalla tecnologia object oriented si realizzeranno nel progetto senza le necessarie azioni correttive. I sintomi di questo rischio, ovvero assumere che la transizione agli oggetti sia ovvia ed immediata, sono spesso più riconoscibili tra i programmatori e progettisti: un'enfasi eccessiva sugli aspetti implementativi più che di progettazione, sui tool più che sui metodi, e l'incapacità di spiegare in modo comprensibile e completo anche i concetti più basilari dell'OOP, come il binding dinamico o il polimorfismo. Il rimedio, naturalmente, è l'adeguata formazione degli sviluppatori; in generale, una buona formazione non può prescindere dall'esperienza diretta, ed è quindi necessario affiancare ad uno studio/training anche la possibilità di affrontare lo sviluppo di progetti pilota, o lo sviluppo sotto la supervisione o con l'affiancamento di persone esperte, in grado di guidare e formare il giudizio degli sviluppatori; affidarsi ad alcuni giorni di training intensivo, proposto magari da un produttore di tool, è raramente la soluzione adeguata ai problemi della formazione. Da notare che l'enfasi eccessiva sugli strumenti è un sintomo abbastanza comune di un altro rischio, ovvero assumere che gli oggetti eliminino automaticamente i tradizionali rischi di sviluppo, e che ci si possa quindi concentrare solo sugli oggetti, dimenticando tutti i problemi al contorno. In realtà non solo esistono problemi extra-tecnici che vanno gestiti, ma la tecnologia object oriented non è neppure completamente matura: ad esempio, la distinzione tra attributi e classi, ma anche l'uso o meno degli attributi in fase di analisi, sono ancora oggetto di dibattito [EJW95], e se da un lato quasi tutte le metodologie di design object oriented sono parzialmente basate su state transition diagram, nessuna di esse spiega esattamente quali interrelazioni possono esistere tra le transizioni lecite per una classe e quelle delle sottoclassi [EE94]. La fiducia eccessiva in una metodologia ("Qui usiamo solo il metodo di Booch!") è un fenomeno tipico nel mondo del software, ricco di profeti e di "guerre di religione"; in realtà, nessuna metodologia è in grado di garantire il successo di un progetto, che è più spesso basato sui fattori umani [Bro79], [DML87], [McC95], [Wei71] che su quelli strettamente tecnologici.

Gestire gli oggetti, ovvero: l'influenza del management nel successo della tecnologia ad oggetti.
Non sempre la spinta alla transizione verso la tecnologia ad oggetti viene dall'alto: anzi, non di rado è un gruppo di programmatori e progettisti entusiasti (o stanchi, o desiderosi di rinnovare le proprie conoscenze) che cercano di introdurre nei progetti dapprima un linguaggio, poi un metodo di design, più raramente un metodo di analisi orientato agli oggetti. Nel fare questo, spesso si rendono involontariamente colpevoli di propagandare in modo eccessivo i benefici della tecnologia, aumentando le probabilità dei rischi precedenti; purtroppo, senza l'adeguato sostegno del management, è realmente difficile ottenere dei risultati di rilievo nell'introduzione degli oggetti nelle aziende. La mancanza di formazione e supporto del management è di per sé abbastanza ragionevole, dal momento che nel migliore dei casi la dirigenza avrà assistito ad un seminario o letto un articolo in cui si vantavano i prodigiosi risultati degli oggetti, la possibilità di modellare esattamente il dominio del problema, e la naturalezza dei metodi di astrazione utilizzati. Tuttavia tali fattori sono anche la causa prima dei miti degli oggetti visti sopra, della mancanza di formazione degli sviluppatori (se i metodi di astrazione sono naturali, perché dovremmo spendere denaro per formare il personale?) e di un generale clima di scontento non appena i miracoli tanto attesi non vengono realizzati. E non verranno sicuramente realizzati, perché con tali premesse si tenderà in modo naturale a riporre aspettative eccessive nei primi progetti. I sintomi sono anche in questo caso piuttosto riconoscibili: dietro la promessa di venditori e degli sviluppatori più entusiasti, l'azienda tenta di fare troppo, troppo in fretta, e troppo presto: ad esempio, cominciare a sviluppare in C++ sotto Windows usando una sofisticata libreria commerciale di classi, prospettando inoltre un ciclo di sviluppo abbreviato rispetto alle precedenti esperienze in MS-DOS e Pascal. I fattori correttivi più importanti in questi casi sono due: cercare di educare il management sui reali benefici a lungo termine degli oggetti, ma anche ascoltare le ragioni del management per vedere se, ed in che modo, gli oggetti possono essere realmente introdotti in azienda. Molte piccole software house, o piccoli gruppi di sviluppo interni alle aziende, non hanno le problematiche di riuso e manutenzione o le necessità di accurata progettazione che più traggono beneficio dagli oggetti. In alcuni casi un programma viene sviluppato per fare fronte ad un'esigenza che dura al più qualche mese, e poi diventa inutile perché cambiano le regole del business; allora un approccio RAD, con strumenti come il Visual Basic, può anche essere più adatto di una metodologia object oriented che richieda grandi investimenti nella progettazione architetturale. La mancanza di comunicazione tra dirigenza e sviluppo è infatti la fonte principale di richieste contradditorie (es. prototipazione, qualità e design); da un lato il marketing richiede un ciclo di sviluppo rapido, che si adegui velocemente ai nuovi requisiti utenti, dall'altro la dirigenza richiede di stabilire un controllo di qualità, magari nello sforzo di adeguarsi alle normative ISO 9000, ed infine si insiste sul design sperando in un futuro riuso. Il sintomo più comune è in questi casi l'insoddisfazione e la confusione dei programmatori, che essendo in fondo alla catena vengono raggiunti da richieste assolutamente contraddittorie, come l'esigenza di sviluppare in fretta e di modificare rapidamente il codice, mantenendo però una adeguata documentazione (senza che venga allocato il tempo necessario) e minimizzando l'impatto delle modifiche sul design. Anche in questo caso, trattandosi di un problema largamente extra-tecnico, l'unica soluzione reale passa attraverso la comunicazione e la formazione a tutti i livelli, il riconoscimento effettivo delle necessità aziendali, la valutazione degli obiettivi da raggiungere ed una onesta pianificazione degli interventi. All'interno di tale pianificazione, è importante prendere in considerazione l'eventuale resistenza da parte degli sviluppatori: lo sviluppo del software è una attività umana e sociale, e vi sono fattori tipicamente umani che possono entrare in gioco, come la resistenza ai cambiamenti, la paura di perdere lo status di esperto, il timore di non riuscire a completare la transizione ad un nuovo paradigma. Gestire le innovazioni significa anche individuare le possibili cause di resistenza e porvi rimedio, ad esempio evitando di assegnare al "team C++" chi ha investito anni di studio e di lavoro per diventare l'esperto aziendale di Fortran, senza aver avuto da questi una onesta ed ampia assicurazione del suo desiderio di passare i prossimi anni ad imparare un nuovo paradigma. Viceversa, alle prime difficoltà, chi ha avuto successo in un diverso paradigma tenderà in modo naturale a tornare ad esso, vanificando i benefici degli oggetti ed innescando una spirale denigratoria, in quanto la forzatura del paradigma non porterà che ad ulteriori problemi. Molti ritengono che un background in un linguaggio di terza generazione, o nel paradigma strutturato, sia controproduttivo nell'utilizzo della tecnologia object oriented; è interessante notare che simili affermazioni vengono spesso da persone con un ampio background di programmazione strutturata. In realtà, ciò che conta sono le attitudini personali ed il desiderio di imparare, la capacità di mettersi in discussione e di autocritica; le doti essenziali di un buon architetto nel paradigma strutturato sono le stesse nel paradigma object oriented. Una ulteriore forma di resistenza si manifesta spesso come tendenza a sottostimare l'importanza e la difficoltà di analisi e design. Utenti, manager e sviluppatori che "vogliono il risultato" tenderanno a non allocare risorse per queste fasi (il che è facilmente diagnosticabile osservando il planning, che dovrebbe allocare almeno il 25% del tempo per analisi e design), anzi in molti casi verranno vissute come una perdita di tempo; questa è una tipica mentalità da hacker, molto comune tra i programmatori che non hanno avuto una formazione da software engineer, ma è spesso avallata e supportata dai project manager, che si illudono che "codice scritto" equivalga a "progresso del progetto". Peter Coad [CN93] chiama questo fenomeno WHISKEY ("Why in the Hell Isn't Someone 'Koding' Everything Yet?"), ed è molto comune che, in mancanza di adeguata preparazione, il management diventi nervoso all'idea che non vi sia ancora del codice "che gira". Ricordiamo invece che i benefici degli oggetti sono a medio/lungo termine, e che in mancanza di una attenta analisi e di un buon design, molti di questi benefici non si verificheranno. Nuovamente, oltre alla giusta informazione, è necessario anche adottare il giusto ciclo di vita in funzione del tipo di software sviluppato e della mentalità aziendale, preferendo ad esempio un modello a spirale o a prototipi se ciò può eliminare alla fonte alcuni contrasti sociali. È anche importante avere alcuni sviluppatori in grado di fungere da esempio e da riferimento per gli altri: se in precedenza il ciclo di vita del software si esauriva in "scrivi il codice e correggilo finché funziona", difficilmente avremo un passaggio indolore ad un metodo più formale. In molte occasioni, una convincente introduzione di concetti fondamentali di software engineering, come accoppiamento ed coesione, centrata su esempi concreti e con riferimenti precisi al paradigma object oriented, può fornire il necessario background, ma anche il necessario entusiasmo, per passare ad un ciclo di vita più pianificato ed ordinato.

Delimitare il problema, ovvero: l'importanza dell'analisi.
Uno dei tratti psicologici più comuni nel settore scientifico e tecnologico è indubbiamente la spinta a trovare soluzioni. Non appena si vede un problema, scatta un meccanismo inconscio che ci spinge ad ideare delle possibili soluzioni; in molti casi, in assenza di problemi ne vengono cercati dei nuovi (gran parte della ricerca è proprio fondata su questa tensione interna). La predisposizione e la capacità di individuare soluzioni ai problemi che si presentano sono ovviamente di grande beneficio durante lo sviluppo: vi è però una fase in cui la naturale tendenza a ragionare "per soluzioni" diventa pericolosa: il momento dell'analisi. Idealmente, l'analisi dovrebbe individuare il modello del problema, definire cosa il sistema debba fare, senza inquinamenti dovuti alle soluzioni, ovvero a come tale sistema debba portare a termine i suoi compiti, senza quindi confondere problema e soluzione. In realtà, ottenere un simile risultato è possibile solo a fronte di uno sforzo cosciente e continuo: non si può semplicemente usare una metodologia di analisi, incluse quelle basate su use case [JCJO92], e pensare di essere al riparo da ogni rischio. Ricordiamo infatti che anche l'utente, quando ci espone i suoi problemi, tende spesso a proporci inconsciamente la sua personale soluzione ai problemi aziendali; questo fenomeno è costantemente scoperto durante il business reengineering, quando le procedure aziendali vengono messe in discussione per trovare nuove soluzioni ai reali problemi dell'azienda. A cosa può portare la confusione tra analisi e design? Di solito a soluzioni troppo generali o inefficienti, oppure a soluzioni troppo ristrette o rigide: dipende in gran parte dalle attitudini di chi sceglie, consciamente o meno, il percorso adottato. Un atteggiamento tipicamente "matematico", portato all'astrazione, tenderà ad introdurre elementi di eccessiva generalità nel modello, costringendo poi ad implementare soluzioni eccessivamente flessibili, con dispendio di tempo e talvolta perdita di efficienza: chi ha lavorato in grandi progetti riconoscerà quasi sicuramente la "sindrome dell'applicazione universale", ovvero la sensazione di sviluppatori e progettisti che l'applicazione stia degenerando verso un sistema che, nelle intenzioni degli analisti, dovrebbe essere in grado di fare qualunque cosa con modifiche minimali. Viceversa, un atteggiamento tipicamente "ingegneristico", pragmatico e portato al risultato, tenderà ad introdurre semplificazioni ed assunzioni, spesso guidate dalla tecnologia, all'interno del dominio del problema. La conseguenza diretta è l'incapacità del sistema di assorbire l'impatto di modifiche ai requisiti senza ristrutturazioni significative, ma anche la difficoltà di scalare le soluzioni adottate verso altri tipi di tecnologia: un fenomeno molto sentito nel passaggio, ad esempio, da applicazione modellate su un terminale ad applicazioni document-centric in ambienti grafici. Molto spesso si finisce per riportare nel nuovo ambiente una serie di assunzioni anacronistiche, come la presenza di pagine a dimensioni fisse, quando una rivalutazione del problema porterebbe a soluzioni più adatte alla nuova tecnologia. I sintomi dei rischi qui esposti sono difficili da notare dall'interno; nessuna delle metodologie di analisi dà una base formale per la fase di analisi, per quanto si possa trovare in [CEW93] un tentativo di formalizzare la nozione di "bontà" di una classificazione di oggetti, simile in un certo senso alla teoria della normalizzazione relazionale. In generale, senza un aiuto esterno non ci si rende conto dei problemi su esposti sino a quando non si arriva in fase di codifica (soluzione troppo generale) o di manutenzione o revisione dei requisiti (soluzione troppo restrittiva); in alcuni casi, non si identifica mai il problema come tale, ma si ha una cattiva ricezione del prodotto da parte del mercato. Una buona tecnica preventiva, che ho sperimentato con successo in diversi progetti, è stato l'affiancamento di personale "estraneo" alle precendenti soluzioni aziendali, anche se non necessariamente esterno all'azienda; il rischio è infatti più forte quando si inizi lo sviluppo di un progetto "simile" ad altri già trattati, o la reingegnerizzazione di un prodotto già sviluppato. In questo caso, affiancare all'analista una figura che chieda le motivazione delle scelte che non appaiono naturali, che esponga la sua visione del dominio del problema, e che più in generale rallenti la corsa verso la "soluzione ideale" dell'analista, può evitare gran parte dei problemi. Naturalmente, esistono altre considerazioni di carattere politico (l'analista può subire la situazione come un insulto alle proprie capacità, la figura aggiuntiva dovrebbe avere una autorità sufficiente a non essere considerata un peso morto, eccetera) che in generale fanno propendere per l'affiancamento di una persona piuttosto esperta ed in grado di muovere delle critiche sensate, viceversa il suo ruolo verrà -di fatto- ignorato dal resto del team. In alcuni casi, un ottimo architetto (vedere punto seguente) può essere in grado di riconoscere un inquinamento da soluzione nel prodotto dell'analisi, svolgendo di fatto il ruolo di verifica su esposto; l'architetto ha in genere autorità sufficiente a muovere critiche e proporre schemi alternativi, quindi anche i fattori politici sono più semplici da gestire. Purtroppo, gli ottimi architetti sono pochi, nel software come in altri settori; in mancanza di supporto umano, una buona tecnica per evitare le soluzioni troppo generali è di richiedere all'analista di motivare la presenza di ogni classe nel dominio del problema, e valutare in un meeting di revisione [ES93], [Fag76] l'effettiva rilevanza di ognuna di esse. Per evitare le soluzioni troppo rigide, è spesso possibile stabilire sin dall'inizio una serie di linee evolutive del prodotto, e richiedere che esse siano percorribili senza eccessive variazioni: nuovamente, riunioni di revisione di analisi e design sono necessarie per verificare l'aderenza del risultato ai requisiti.

L'integrità concettuale, ovvero: l'importanza dell'architettura nel design orientato agli oggetti.
Possiamo definire l'architettura di una applicazione come la struttura del sistema, che comprende i moduli attivi, i meccanismi di interazione tra i moduli, ed un insieme di regole che governano tale interazione. La scelta della struttura non è immediata, anzi, terminata la fase di analisi, esistono di norma un gran numero di scelte possibili; la scelta operata riflette spesso una "visione globale" di come suddividere le responsabilità, un piano di evoluzione, una visione a lungo termine. Per questo non bisogna assumere che vi sia un'unica "struttura naturale" per il progetto: occorre anzi avere ben chiara l'importanza della scelta architetturale, sia dal punto di vista tecnico che politico. Tecnicamente, la struttura ideale tende all'integrità concettuale, ovvero a garantire che l'insieme di regole che governano il sistema sia piccolo, che a problemi simili corrispondano soluzioni simili, che non vengano applicate soluzioni locali ma soluzioni omogenee con il resto del sistema. Osserviamo il contrasto con un sistema sviluppato in modo anarchico: in molti casi, le regole di interazione sono diverse per ogni interfaccia. È importante capire che l'interfaccia è ben più di quanto si possa leggere nella dichiarazione di una classe: esistono precondizioni e postcondizioni che non vengono documentate; esistono pattern di accesso, dipendenze temporali, problemi di ownership degli oggetti, restrizioni sul subclassing, e molti altri fattori di cui, in pratica, non si può non tenere conto, ma che non sono immediatamente riconoscibili. È difficile comprendere veramente un sistema quando in ogni punto valgono regole diverse: chi ha avuto esperienza di integrazione di prodotti e librerie di terze parti, o ha utilizzato librerie complesse che mancavano di uniformità concettuale, riconosce sicuramente il fenomeno, analogo alla vista di un palazzo con parti barocche, parti post-moderne, parti in stile liberty. Il ruolo dell'architetto è proprio questo: garantire l'uniformità e l'integrità concettuale; è un ruolo difficile, che richiede anche una buona capacità di gestire gli inevitabili contrasti con chi si vede rifiutare una brillante (o meno) soluzione locale che mal si adatta alla struttura del progetto, o con chi vuole usare una tecnica solo per dimostrare le proprie capacità e "lasciare il segno" nel progetto. Tuttavia, nella mia esperienza, la presenza di un buon architetto è una delle condizioni fondamentali per il successo di un progetto. Naturalmente, la sola presenza di un architetto non garantisce il successo, né impedisce che vengano adottate soluzione che, a lungo termine, si rivelano errate o comunque non ottimali; è allora necessario accettare il costo di una ristrutturazione del sistema. Anche in questo caso, è importante avere l'esperienza per evitare di ristrutturare troppo o troppo poco: il primo caso, tutto sommato poco frequente in grandi progetti, dove mancano le effettive risorse per modificare spesso l'intera struttura, si nota facilmente perché gli sviluppatori sono costantemente costretti a modificare le loro classi, che non sono mai stabili, ma in un continuo stato "di flusso". Molto spesso, la soluzione correttiva è proprio quella di introdurre (o di cambiare!) un architetto. Il secondo caso è invece molto comune, proprio per la mancanza di risorse che spinge a dire "lo faremo alla prossima versione", ben sapendo che mai, nella storia dell'azienda, c'é stato il tempo di rivedere pesantemente la struttura prima di iniziare ad implementare tutte le novità della versione successiva. Ricordiamo però che l'entropia del codice tenderà comunque ad aumentare in assenza di azioni correttive: è necessario rivedere periodicamente le soluzioni adottate, identificare potenziali aree problematiche, e studiare alternative migliori. Nuovamente, si tratta di una scelta tra un costo presente ed un costo futuro, e non sempre si ha la possibilità di accettare il costo immediato; è tuttavia importante essere almeno a conoscenza dei punti deboli della struttura, se non altro per evitare di sovraccaricarli in versioni future; in realtà, ciò richiede anche la giusta cultura aziendale, dove sia possibile esporre un problema senza il timore di ripercussioni. In molti casi, gli sviluppatori hanno imparato a nascondere le aree problematiche, adducendo magari a difficoltà implementative per evitare di introdurre modifiche in tali aree, perché ammettere pubblicamente un errore strutturale sarebbe controproducente nel meccanismo aziendale. Questo è in realtà un doppio sintomo, della necessità di ristrutturare il codice e di rivedere le regole sociali all'interno dell'azienda. Vediamo infine un ultimo rischio collegato all'architettura: lasciare che l'integrità architetturale prenda il sopravvento sull'usabilità del prodotto, o in altri termini, spostare l'enfasi dall'utente agli oggetti. Non è raro che si adottino certe soluzioni, che hanno un impatto non trascurabile sull'interfaccia utente, semplicemente perché sembra più consono al paradigma object oriented o all'architettura del sistema. Prendiamo ad esempio l'uso sempre più diffuso dei menu pop-up attivabili su ogni oggetto visibile, ad esempio con il tasto destro del mouse. Certamente, si tratta di una tecnica per esporre la natura di "oggetto" dei vari elementi grafici, rappresentando in un menu le azioni che l'oggetto può compiere; ma si tratta realmente di un modello di interazione naturale per la massa degli utenti? In generale, la progettazione dell'interazione uomo-macchina deve seguire altri criteri [Nor88], [Tog96] e non esporre il modello ingegneristico dell'applicazione.

Distinguere i principi dalle tecniche, ovvero: non dobbiamo abbandonare gli insegnamenti dell'ingegneria del software.
Un nuovo paradigma nasce spesso da un confronto piuttosto aspro con le metodologie preesistenti: è quindi ragionevole aspettarsi un rifiuto, o almeno una forte critica, dei concetti precedenti. Ciò porta però a trascurare anche gli aspetti positivi e gli insegnamenti più importanti, al punto che molti sviluppatori che si sono convertiti al paradigma object oriented, o che hanno iniziato a lavorare professionalmente con un linguaggio orientato agli oggetti, ignorano completamente i capisaldi dell'ingegneria del software. Consideriamo ad esempio due degli errori di progettazione più comuni: non minimizzare le dipendenze tra le classi e non isolare le parti soggette a variazioni; il primo è addirittura così comune che, di fatto, il problema più grande nella manutenzione dei programmi object oriented [WCMH92] sembra proprio essere la comprensione delle interazioni e dipendenze tra le classi. Eppure lo studio di entrambi i problemi risale agli albori dell'ingegneria del software [Par72], [Par79]. Come accorgersi del problema? Qui si vede il valore dell'analisi e del design, e della rappresentazione grafica dei modelli; se da un lato è infatti piuttosto complesso valutare questi fattori esclusivamente in base al codice, dall'altro le dipendenze tra le classi appaiono lampanti in un diagramma delle classi. Senza un modello, ci si accorgerà del problema più avanti, tipicamente in fase di debugging e manutenzione. Come si può porre rimedio al problema? Riportando l'attenzione dello sviluppatore ai principi fondamentali dell'information hiding (o insegnandoli, se necessario): distinguendo le tecniche, come l'incapsulazione e la creazione di classi protocollo, dai principi fondamentali, come l'information hiding e la necessità di presentare interfacce resilienti rispetto alle variazioni nei requisiti e nell'implementazione. Analogamente, dovrebbe essere compito dell'analisi indicare la stabilità dei requisiti, semplificando il compito del progettista, o la revisione del design. La mancanza di precisione nei requisiti è un caso particolare di un problema ben noto, ovvero non documentare le assunzioni e le decisioni; soprattutto nella fase di design, che fa da ponte tra analisi e codifica, il progettista assume numerosi fattori al contorno: ad esempio, che le ricerche siano più frequenti degli inserimenti, che gli attributi di una classe siano stabili, che alcune classi vengano sempre riutilizzate in connessione, e così via. Molto spesso, queste assunzioni restano solo nella mente del progettista: anche per questo il ruolo dell'architetto è così critico, come depositario delle assunzioni nascoste; si tratta però di un evidente punto debole dell'intero processo di sviluppo. La diagnosi è piuttosto difficile, e prevede la revisione dei documenti di design; una buona tecnica è anche chiedere periodicamente di spiegare le ragioni di una certa decisione (ad esempio, l'utilizzo o meno di una libreria) presa alcuni mesi prima: se non si può rispondere semplicemente cercando tra la documentazione di design, è evidente che non viene tenuta traccia delle decisioni prese. La cura è possibile ma costosa, e passa per la necessaria allocazione del tempo utile per documentare le assunzioni e le decisioni: talvolta, basta però dare loro la giusta enfasi, magari a scapito di altro tipo di documentazione che invece è già catturato dai diagrammi prodotti. Tornando alla differenza tra principi e tecniche, professionalmente mi capita spesso di assistere a dibattiti su quale design sia "più object oriented" o "più incapsulato", eccetera; per quanto esistano diversi tentativi di introdurre misure quantitative specifiche nel paradigma object oriented, ad es. [CK94], [LK94] (e le rispettive critiche [BBM95], [BS96]), molto spesso il dibattito si può esaurire cercando di evitare i due errori più gravi: non massimizzare la coesione e non minimizzare l'accoppiamento. Consideriamo ad esempio uno dei casi più controversi: l'utilizzo di classi "manager", che gestiscono insiemi di altre classi; si tratta di un caso molto frequente, tanto da essere nominato tra i pattern strutturali [GHJV95] come "mediator". Vi è un forte dibattito sull'uso delle classi manager: Coad e Nicola [CN93] le sconsigliano caldamente, articoli recenti le ritengono candidate al "cancro del software" [Hay96], ed in effetti nella mia esperienza ho visto numerosi esempi di classi manager cresciute a dismisura, ed utilizzate in modo tale da trasformare gli altri oggetti in elementi passivi. D'altra parte, alcuni autori [Sou94] le ritengono molto utili per "aumentare l'incapsulazione". Come risolvere il dilemma? Proprio distinguendo tra i principi e le tecniche: le classi manager sono utili quanto diminuiscono l'accoppiamento, dannose quando diminuiscono la coesione; la scelta migliore è spesso frutto di considerazioni di trade/off. Tutti i casi di "cancro del software" succitati sono chiari esempi di sistemi a bassa coesione, ed i casi in cui le classi manager "aumentano l'incapsulazione" sono quelli in cui, di fatto, diminuiscono l'accoppiamento [Pes95a]. È però fondamentale che a progettisti e programmatori venga insegnato (o re-insegnato) ad applicare le tecniche (incapsulazione, ereditarietà, polimorfismo) per soddisfare ai principi (separation of concern, information hiding, alta coesione, basso accoppiamento), non in base ad una personale opinione di cosa sia "più object oriented"; nuovamente, posso dire per esperienza che questo è possibile in tempi brevi e con risultati di tutto rilievo: tra l'altro, la coesione e l'accoppiamento sono facilmente misurabili, e non sono necessarie metriche molto sofisticate per raggiungere buoni risultati.

Progettare gli oggetti, ovvero: spostare l'enfasi dalla programmazione al design.
Vi sono due modi di utilizzare i costrutti di un linguaggio: per quello che fanno, o per quello che esprimono. Nel primo caso stiamo spostando l'attenzione alla codifica, ed intendiamo la programmazione come un colloquio uomo-macchina; nel secondo caso stiamo spostando l'attenzione sulla progettazione, ed intendiamo la programmazione come un colloquio uomo-macchina e uomo-uomo. Per chi proviene dal paradigma strutturato, o per chi si è sempre concentrato sui soli aspetti pragmatici dell'implementazione, viene però naturale utilizzare meccanismi come l'ereditarietà o l'incapsulazione per il loro effetto, non per la semantica che trasmettono. La conseguenza più comune è che si tende a mantenere una struttura imperativa, con classi assolutamente non autonome (molto facile da capire, attraverso la presenza di metodi di get/set) e con un controllo centralizzato dell'esecuzione (le "classi manager" di cui sopra). Non vi è un serio tentativo di identificare ruoli e responsabilità delle diverse classi, che vengono usate esattamente come strutture dati del C o del Pascal.
Un problema più sottile, ma non meno diffuso, è di confondere ereditarietà e contenimento; in più di una occasione, ho visto sviluppatori derivare da una classe, quando logicamente avrebbero dovuto aggregarla, "perché così ho accesso diretto ai metodi". Questo approccio è assolutamente perdente in fase di manutenzione ed anche di estensione, dove le condizioni che consentivano di implementare l'aggregazione tramite ereditarietà si sfaldano e ci si trova a dover modificare l'interfaccia della classe, e quindi tutto il codice che chiama i metodi della classe. Un approccio più mirato al design, e che utilizzi l'ereditarietà pubblica solo per esprimere il concetto di IS-A, previene sin dall'inizio l'insorgere del problema. La distinzione tra is-a, has-a, ed is-implemented-using è molto importante, ed in alcuni metodi di design (es. Booch) ed alcuni linguaggi (es. C++) è esprimibile direttamente, a tutto vantaggio della chiarezza, dei controlli statici, e della manutenibilità del codice. Purtroppo è anche molto comune confondere ereditarietà di interfaccia e di implementazione: nella mia esperienza, ho avuto modo di vedere che in molti progetti, anche di dimensione ragguardevole, nonché in librerie commerciali di un certo peso (in tutti i casi ben sopra le 100.000 righe di codice) l'ereditarietà di implementazione (ereditarietà privata in C++) non è mai utilizzata. L'ereditarietà viene sempre espressa come ereditarietà di interfaccia, tranne che le classi derivate vengono, di fatto, accoppiate sull'implementazione. questa è una delle ragioni che portano al cosiddetto problema della fragile base class [Pes95b], dove modifiche alla sola implementazione di una classe base si ripercuotono su tutte le classi derivate. L'errore qui è pensare che l'incapsulazione protegga da ogni difetto nel design: l'incapsulazione è una tecnica per separare le implementazioni, e quindi ha anche il compito di proteggere dai difetti interni di una classe, ma non protegge dai difetti strutturali, di interfaccia e di progetto. Un ulteriore errore di design che è molto semplice da verificare su un diagramma delle classi è di consentire accoppiamenti transitivi tra le classi: ad esempio, avendo una classe che accede ai sotto-oggetti di un'altra classe. Il rispetto di un criterio di design come la Legge di Demeter [LH93] [Pes95c] può in questi casi prevenire il problema alla radice; notiamo che come sempre, vi sono in casi in cui la legge può e deve essere violata: di norma, si tratta anche di punti in cui un commento o una documentazione aggiuntiva sulle assunzioni al contorno (vedi sopra) si rivela fondamentale. È naturale chiedersi se la necessità di progettare l'applicazione, e non semplicemente le singole classi, non sia un "tradimento" dei principi dell'OOP: in effetti, su questa riflessione alcuni autori [Ude94] hanno sommariamente concluso che l'OOP è fallita. Anche questo è un errore piuttosto grave: confondere classi e componenti software; le tecnologie ad oggetti non portano necessariamente alla creazione di componenti, anzi, la struttura più comune per le librerie è quella di white-box framework [JF91] e non di black-box framework. Stroustrup stesso, in una conversazione [Pes96] ha ammesso che la creazione di componenti non è uno degli obiettivi del C++ e richiede uno sforzo addizionale; altri autori [Höl93] [TNG92] hanno investigato a fondo sulle difficoltà insite nella produzione e nella integrazione di componenti software. Le conseguenze sono piuttosto ovvie: ogni volta che qualcuno dice "possiamo sviluppare questa parte in modo del tutto indipendente perché è un componente separato", sia questi un manager, un progettista, o un produttore di tool, chiedetegli su quali basi può garantire che il protocollo tra quel "componente" ed il resto del sistema sia sufficientemente stabile ed universalmente accettato all'interno del progetto. In mancanza di una risposta convincente, è necessario dedicare un pò di tempo a definire e raffinare l'interfaccia del componente, prima che insorgano gli inevitabili problemi di integrazione.

Il riuso non è gratuito, ovvero: gestire i costi ed i benefici del riuso.
Fa parte del folklore degli oggetti che la tecnologia object oriented "aumenti il riuso"; come per tutte le grandi promesse, la conseguenza immediata è di sottostimare la difficoltà del riuso. Il riuso è possibile all'interno di ogni paradigma e linguaggio, ed anche se gli oggetti aiutano indiscutibilmente ad ottenere codice riusabile, il riuso va progettato all'interno degli oggetti: come visto sopra, l'uso degli oggetti non protegge da errori di progettazione che possono seriamente compromettere le possibilità di riuso. L'errore più comune è lasciare che classi "generali" si trovino a dipendere da classi specializzate, impedendo di fatto il riuso delle classi generali al di fuori del contesto specializzato. L'introduzione di criteri di design come la stratificazione, unitamente alle tecniche per trasformare un modello non stratificato in uno stratificato, è forse il correttivo tecnico più efficace. Va fatto comunque notare che i maggiori impedimenti al riuso sono gli stessi, indipendentemente dal paradigma, e sono sostanzialmente fattori umani: i manager tendono spesso a sottostimare le resistenze degli sviluppatori, ma in molte organizzazioni esistono due fenomeni endemici che riducono il riuso al minimo indispensabile: la mentalità da macho-programmer e la mancanza di tempo per progettare un componente riusabile. La prima è assai più diffusa di quanto si voglia ammettere - molti programmatori pensano che riusare il codice scritto da altri sia da principianti, e che tutto sommato nessun altro programmatore sia capace di scrivere codice migliore del loro. Nelle parole di un programmatore Microsoft [You96] "Ogni programmatore qui è fermamente convinto di essere l'unico programmatore competente - gli altri sono idioti. Perché dovrebbe riusare il codice scritto da idioti?". Questo atteggiamento è molto diffuso fra i team composti di programmatori molto selezionati e qualificati, dove uno dei rischi maggiori è proprio la difficoltà nell'ottenere una reale cooperazione tra gli sviluppatori; in questi casi, è spesso indispensabile un vivo incoraggiamento da parte dell'upper management, anche basato su incentivi economici [Faf94], per superare l'ostacolo dell'eccessivo protagonismo. Viceversa, in aziende dove gli sviluppatori sono meno inclini all'autoesaltazione, è la mancanza di tempo il fattore più determinante; il problema del programmatore diventa allora: Chi paga il costo del riuso? Se il riuso non è pianificato, accettato ed incoraggiato dalla dirigenza, il programmatore deve scegliere tra l'investimento di tempo per la progettazione, lo sviluppo ed il testing di un componente realmente riusabile, oppure l'immediata realizzazione di una classe custom, ritagliata sulle proprie esigenze. Pochi programmatori accettano l'idea di rimanere indietro nel planning, o di investire parte del loro tempo libero, pur di creare una soluzione generale; non viene quindi mai a crearsi quella famosa "libreria di classi" che dovrebbe portare all'accelerazione dei tempi di sviluppo in futuri progetti. Anche in questi casi, solo la buona comunicazione tra programmatori, progettisti e manager può portare al giusto compromesso, scegliendo le classi più importanti e significative e investendo per la creazione di elementi riusabili. Va infatti scongiurato anche il rischio opposto, ovvero di investire troppo nel riuso: ovvero, investire grandi quantità di tempo e denaro per creare componenti teoricamente riusabili ma che, in pratica, non verranno mai riutilizzati. Questo fenomeno è abbastanza comune in grandi progetti, dove inizialmente si scorgono grandi opportunità di riuso, ma dove fattori fuori controllo tendono poi a minare le effettive opportunità: investire due anni per creare una libreria di componenti riusabili per l'interfaccia utente, solo per scoprire che le nuove versioni dei sistemi operativi hanno introdotto una quantità di modelli di interazione cui non avevamo pensato due anni prima, non è un problema accademico ma un rischio che va gestito e controllato.
Vediamo infine un ultimo fenomeno piuttosto comune: talvolta una classe nasce riusabile e si trasforma in codice non riusabile. In un mondo ideale ciò non avviene, perché ogni decisione di codifica viene verificata usando i requisiti di design come riscontro; nel mondo reale, tuttavia, i problemi vengono spesso riscontrati "a valle" nel processo di sviluppo (codifica, testing) e vi è una forte pressione, reale o psicologica, che porta ad attuare soluzioni locali, che mal si conciliano con il design originale. Per questo è importante distinguere tra riuso del design e riuso del codice: un buon design è spesso più facilmente riusabile del codice che lo implementa, perché il codice è stato "sporcato" durante lo sviluppo o la manutenzione; i casi più emblematici di riuso del design sono rappresentati dai design pattern [Coa95, GHJV94], ma anche a livello di architettura delle applicazioni si possono avere ottimi risultati. Ad esempio, una applicazione di gestione bonifici ed una di gestione di ricevute bancarie possono condividere gran parte del design architetturale: in un mondo ideale, con tempi di sviluppo adeguati, personale esperto ed un buon architetto, probabilmente potrebbero condividere anche buona parte del codice. Nel mondo reale, con tempi di sviluppo sempre più ristretti, e programmatori che non sempre hanno ricevuto training e formazione adeguati, si può optare per un obiettivo meno ambizioso ma più realistico: condividere gran parte del design, con i conseguenti vantaggi sui tempi di progettazione ma anche di riassegnazione degli sviluppatori, scegliere alcune classi fondamentali sulla cui riusabilità vale la pena di investire, e lasciare le altre alla discrezione dei programmatori.

Reader's Map
Molti visitatori che hanno letto
questo articolo hanno letto anche:

Bibliografia:
[AB92] Mehmet Aksit, Lodewijk Bergmans, "Obstacles in Object-Oriented Software Development", Technical Report, University of Twente, 1992.
[BBM95] Victor Basili, Lionel Briand, Walcélio Melo, "A Validation of Object Oriented Design Metrics", Technical Report CS-TR-3443, University of Maryland, 1995.
[Bro79] Frederick Brooks, "The Mythical Man-Month", Addison-Wesley, 1979.
[BS96] Aaron Binkley, Stephen Schach, "Impediments to the Effective Use of Metrics within the Object-Oriented Paradigm", OOPSLA'96 Workshop on Software Metrics, 1996.
[CEW93] Stephen Clyde, David Embley, Scott Woodfield, "Object-Class Congruency: Improving the Quality of Classification Abstractions in Object Oriented Software Systems", Technical Report, Brigham Young University, 1993.
[CK94] S. Chidamber, C. Kemerer, "A Metrics Suite for Object Oriented Design", IEEE Transactions on Software Engineering, Vol 20, No 6, 1994.
[CN93] Peter Coad, Jill Nicola, "Object Oriented Programming", Prentice-Hall, 1993.
[Coa95] Peter Coad, "Object Models: Strategies, Patterns, & Applications", Prentice Hall, 1995.
[DML87] Tom De Marco, Timoty Lister, "Peopleware", Dorset House, 1987.
[EE94] Jürgen Ebert, Gregor Engels, "Observable or Invocable Behaviour - You Have to Choose!", Technical Report, Leiben University, 1994.
[EJW95] David Embley, Robert Jackson, Scott Woodfield, "OO Systems Analysis: It Is or Isn't It?", IEEE Software, Vol. 12 No. 4, July 1995.
[ES93] Robert Ebenau, Susan Strauss, "Software Inspection Process", McGraw-Hill, 1993.
[Faf94] Danielle Fafchamps, "Organizational Factors and Reuse", IEEE Software, Vol. 11 No. 5, September 1994.
[Fag76] M. E. Fagan, "Design and code inspections to reduce errors in program development", IBM Systems Journal, Volume 15, No 3, 1976.
[GHJV94] Gamma, Helm, Johnson, Vlissides, "Design Patterns", Addison-Wesley, 1994.
[Hay96] Philip Haynes, "Detection and Prevention of Software Cancer in OO Systems", OOPSLA'96 Workshop on Software Metrics, 1996.
[Höl93] Urs Hölzle, "Integrating Independently-Developed Components in Object-Oriented Languages", in ECOOP '93 Proceedings, Springer Verlag, 1993.
[JCJO92] Jacobson, Christerson, Jonsson, Overgaard, "Object-Oriented Software Engineering, a Use-Case Based Approach", Addison-Wesley, 1992.
[JF91] Ralph Johnson, Brian Foote, "Designing Reusable Classes", Technical Report, University of Illinois, Urbana-Champaign (una precedente versione è apparsa su Journal of Object Oriented Programming, Vol. 12 No. 2, 1988).
[LK94] M. Lorenz, J. Kidd, Object Oriented Software Metrics, Prentice-Hall, 1994.
[LH93] Karl Lieberherr, Ian Holland, "Assuring Good Style for Object-Oriented Programs", Technical Report, Northeastern University, 1993.
[McC95] Jim McCarthy, "Dynamics of Software Development", Microsoft Press, 1995.
[Nor88] Donald Norman, "The Psichology of Everyday Things", Basic Books, 1988.
[Par72] David Parnas, "On the Criteria to be Used in Decomposing Systems into Modules", Communications of ACM, December 1972.
[Par79] David Parnas, "Designing Software for Easy of Extension and Contraction", IEEE Transactions on Software Engineering, March 1979.
[Pes95a] Carlo Pescio, "Architettura: Un Esempio", Computer Programming No. 40, Ottobre 1995.
[Pes95b] Carlo Pescio, "Il Problema della Fragile Base Class in C++", Computer Programming No. 41, Novembre 1995.
[Pes95c] Carlo Pescio, "C++ Manuale di Stile", Edizioni Infomedia, 1995.
[Pes96] Carlo Pescio, "Intervista a Bjarne Stroustrup", Computer Programming No. 50, Settembre 1996.
[Sou94] Jiri Soukup, "Taming C++, Pattern Classes and Persistence for Large Projects", Addison-Wesley, 1994.
[TNG92] Dennis Tsichritzis, Oscar Nierstrasz, Simon Gibbs, "Beyond Objects: Objects", International Journal of Intelligent and Cooperative Information Systems, Vol. 1 No. 1, 1992.
[Tog96] Bruce Tognazzini, "Tog on Software Design", Addison-Wesley, 1996.
[Ude94] Jon Udell, "Componentware", BYTE, Vol. 19 No. 5, May 1994.
[WCMH92] Norman Wilde, Allen Chapman, Paul Matthews, Ross Huitt, "Describing Object Oriented Software: What Maintainers Need to Know", Technical Report, University of West Florida, 1992.
[Web95] Bruce Webster, "Pitfalls of Object-Oriented Development", M&T Books, 1995.
[Wei71] Gerald Weinberg, "The Psychology of Computer Programming", Dorset House, 1971.
[You96] Edward Yourdon, "Rise & Resurrection of the American Programmer", Prentice-Hall / Yourdon Press, 1996.

Biografia
Carlo Pescio (pescio@acm.org) svolge attività di consulenza in ambito internazionale nel campo delle tecnologie Object Oriented. Ha svolto la funzione di Software Architect in grandi progetti per importanti aziende europee e statunitensi. Autore di numerosi articoli su tematiche object oriented e di software engineering, nonché del libro "C++ Manuale di Stile", è incaricato della valutazione dei progetti dal Direttorato Generale della Comunità Europea come Esperto nei settori di Telematica e Biomedicina. È laureato in Scienze dell'Informazione ed è membro dell'ACM, dell'IEEE e della New York Academy of Sciences.