Programmazione Object-Oriented per utenti di...

34
Programmazione Object-Oriented per utenti di CMSSW Mini-corso, Padova, 22/04/2010 Massimo Nespolo 1

Transcript of Programmazione Object-Oriented per utenti di...

Programmazione Object-Oriented

per utenti di CMSSW

Mini-corso, Padova, 22/04/2010

Massimo Nespolo

1

Riassunto:

• Refactoring:

– Come far evolvere il codice.

– In che direzione andare.

– A che livello agire.

• Design pattern (GoF):

– Che cosa sono.

– Perché parlarne.

– Qualche esempio.

Refactoring e design pattern

2

I principi di design

3

Linee guida principali (SOLID) che permettono di

evitare un cattivo design (rigido, fragile, immobile)

Bisogna però bilanciare sempre vantaggi e svantaggi:

aggiungono complessità, e vanno usati dove serve flessibiltà

1. S - SRP: Single responsibility principle.

2. O - OCP: Open/closed principle.

3. L - LSP: Liskov substitution principle.

4. I - ISP: Interface segregation principle.

5. D - DIP: Dependency inversion principle.

Refactoring

4

Il codice “brutto” non va

spiegato mediante commenti:

va riscritto. I commenti sono

per le intenzioni!

Modifica della struttura interna

di un programma eseguita

senza cambiare il

comportamento funzionale

Se non riusciamo ad ottenere quanto descritto in precedenza

al primo colpo, conviene fermarsi e rimettere mano al codice

Le nostre idee devono sempre essere

espresse mediante il linguaggio

A volte il codice “puzza”

5

Si chiamano “puzze” (smells) i segnali

che un codice è di bassa qualità

1. Duplicate code:

Effetto del copia-incolla, ma bisogna fare una analisi “globale”.

2. Long method e long parameter list:

Codice troppo specializzato, difficile da leggere e riusare.

3. Primitive obsession:

Non lavoriamo al giusto livello di astrazione.

4. Large class, lazy class, data class, divergent change:

Classi troppo ricche, troppo povere, o troppo “mescolate”.

5. Switch statement, conditional complexity e shotgun surgery:

Diventano rapidamente ingestibili (indice di Mc Cabe).

Schema di massima

6

Refactoring può coinvolgere diversi livelli (dal piccolo al grande):

1. Impacchettare adeguatamente il codice in funzioni.

2. Rendere più semplici le chiamate ai metodi.

3. Spostare variabili e metodi da una classe all’altra.

4. Organizzare i dati (cont. diretto/indiretto, type code, …).

5. Semplificare le espressioni condizionali (decomposizione).

6. Gestire le generalizzazioni (gerarchie di derivazione, interfacce).

7. Cambiare l’organizzazione del programma.

Se succede (per imperizia,

cambiamento delle condizioni

esterne, …) si rifattorizza

Evoluzione/ripulitura del codice

fatta in modo disciplinato per

minimizzare il rischio di bug

Nomi comunicativi

7

I nomi di variabili e funzioni

vanno legati al problema

Effettuiamo un rename

(semplice ma efficace)

Usiamo sostantivi per le variabili (cose),

e verbi per le funzioni (azioni)

Spesso da qui partono

refactoring più complessi

Emergono relazioni tra variabili

e funzioni prima nascoste

Il nome di una funzione è

formato da 2 verbi. È giusto?

A livello di funzioni

8

Con pochi parametri (7±2),

allo stesso livello di astrazione

Non è banale scrivere funzioni “belle”,

ossia facili da leggere e da riutilizzare

Funzioni corte

(1, massimo 2 schermate)

Questa è la strategia fondamentale per

rimuovere il codice duplicato:

mettiamolo in una funzione separata.

Catene di operazioni booleane

tra variabili dentro un if

Estraggo tutto dentro una

funzione con nome adatto

Extract class/subclass

9

Spesso ci si accorge che le funzioni di un gruppo si

passano sempre gli stessi parametri: eliminiamoli!

Nuova classe con quelle

funzioni e quelle variabili

Se siamo già in una classe,

estraiamo una sotto-classe

Partiamo quindi con classi

semplici, ed aggiungiamo

funzionalità nuove

Se la classe diventa troppo

complessa, rifattorizziamo

estraendone una parte

Usiamo bene le gerarchie

10

Una sottoclasse “è-una” classe base,

ma in C++ eredita anche dati e funzioni

Se c’è del codice duplicato

nelle classi derivate

Sistemiamo i nomi, e spostiamo

tutto nella classe base

Se qualcosa nella classe base è

usato solo da poche derivate

Spostiamo quello che non è

comune verso il basso

EreditarietàContenimento

Qualche volta, si può fare lo scambio

Metodi al livello giusto

11

Pull-up

method

Pull-down

method

Un caso dubbio…

12

Uno studente “è-una” persona Ereditarietà

E per chi studia e lavora? Eredito da

ambedue (ereditarietà multipla, ed

in C++ deve essere virtuale).

Il lavoratore “è-una” persona Ereditarietà

Ma queste relazioni non

possono più cambiare

(l’ereditarietà è statica)

virtual virtual

Soluzione

13

Stavamo confondendo essere ed avere!

Una persona ha un’attività, che

può cambiare nel tempo

Contenimento mediante

“classi ruolo” (astratte)

Il contenimento è una relazione

dinamica, che può cambiare

Persona non dipende da

Lavoro o Studio (DIP)

Contenimento

Ereditarietà

Extract interface

14

Ma attività è una classe astratta, che deve/può

avere varie realizzazioni concrete

Se abbiamo già le classi concrete, estraiamo

un’interfaccia unica come generalizzazione

Facilità di lettura

(compiti separati)

DIP

(dettagli isolati)

Hot-spot

(possiamo estendere)

Switch e condizioni sparpagliate

15

Gli switch (if-else) sono la

prima fonte di complessità

Nascono innocenti,

ma crescono male

Ancora peggio se la catena

si ripresenta in più punti

Devo fare modifiche in

parallelo (shotgun surgery)

Nuovi comportamenti

aggiungono nuove clausole

Da qui originano difficoltà

di lettura, bachi, rigidità

Polimorfismo (ancora)

16

Lavoratore lavoratore;

Studente studente;

if ( adessoSiamoInEstate ) {

lavoratore.lavora();

} else {

studente.studia();

}

Persona* p = new Persona;

p->setStatus(Persona::Studente);

p->FaiQuelCheDevi() ;

In un programma ad oggetti,

non dovrebbero esserci if-else

Utilizziamo interfacce e

polimorfismo (late binding)

Il polimorfismo è il sostituto ad oggetti dello switch

Dagli switch ai pattern

17

La logica condizionale (switch)

può avere diversi effetti sugli oggetti

Algoritmi diversi

per un problemaStrategy

Cambio di comportamento

a seconda dello statoState

Modifica di alcuni passi

in una proceduraTemplate method

Creazione di oggetti diversi

ma correlati Factory method

Design pattern (GoF, 1994)

18

L’idea viene dall’architettura

(Christopher Alexander)

Soluzione tipica, provata sul

campo, a problemi ricorrenti

L’idea viene portata dentro l’ingegneria del software dalla

banda dei quattro (Erich Gamma, Richard Helm, Ralph

Johnson and John Vlissides) con il libro Design Patterns:

Elements of Reusable Object-Oriented Software

Riflessione critica su

ereditarietà e contenimento

Catalogo dei pattern

divisi in tre categorie

Perché parlarne ora?

19

Noi discutiamo i design pattern

(non i pattern architetturali)

Risolvono problemi su scala

“medio-piccola” (di classi)

Strutture consolidate

dall’esperienza

Idee per il codice nuovo,

ma anche per il refactoring

Forniscono un linguaggio di

alto livello molto diffuso

Aiutano a capire la struttura e la

documentazione del software

Enorme valenza culturale per chiunque scriva software,

oppure debba interagire con grandi sistemi (CMSSW)

Struttura di un pattern

20

Ci sono vari modi per

implementare un certo pattern

È l’idea che lo definisce,

non il diagramma delle classi!

1. Nome (breve e comunicativo):

Alza il livello della discussione e della documentazione.

2. Il problema da risolvere:

Sintomi di un design rigido, condizioni da rispettare.

3. La soluzione:

Costituenti, relazioni, responsabilità e collaborazioni.

4. Le conseguenze:

Bilancio costi/benefici, impatto sul riuso, implementazioni.

Un pattern cattura la “saggezza” accumulata

L’introduzione vale il libro

21

2. Program to an interface, not an implementation

Non appoggiarsi al fatto che la classe

derivata eredita tutto il codice ed i dati

1. Separate things that change from things that stay the same

Posizionare interfacce tra la parte

stabile e quella che cambia (OCP)

3. Favor object composition over class inheritance

L’ereditarietà è statica, ed espone più

dettagli del semplice contenimento

Il catalogo (23 pattern)

22

Creazionali

Come posso creare gli oggetti

concreti se conosco solo le

interfacce astratte?

Strutturali

Come conviene combinare

classi (ereditando) ed

oggetti (componendo)?

Comportamentali

Come far interagire gli oggetti,

distribuendo bene le

responsabilità tra di essi?

Strategy (policy)

23

Lo stesso compito può essere

eseguito in modi diversi

Dobbiamo poter sostituire

gli algoritmi, anche a runtime

Context contiene un puntatore di

tipo Strategy (interfaccia)

Classi concrete con

i vari algoritmi

Ricostruzione delle tracce

24

const OrderedSeedingHits& triplets = theGenerator->run(region,ev,es);

unsigned int nTriplets = triplets.size();

for (unsigned int iTriplet = 0; iTriplet < nTriplets; ++iTriplet) {

const SeedingHitSet& triplet = triplets[iTriplet];

std::vector<const TrackingRecHit *> hits;

for (unsigned int iHit = 0, nHits = triplet.size(); iHit < nHits; ++iHit) {

hits.push_back( triplet[iHit]->hit() );

}

reco::Track* track = theFitter->run(es, hits, region);

if( theFilter && !(*theFilter)(track, hits) ) {

delete track;

continue;

}

tracks.push_back(TrackWithTTRHs(track, triplet));

}

theGenerator->clear();

theGenerator,

theFitter e theFilter

sono puntatori di

tipo classe astratta

Adapter (wrapper)

25

A volte un oggetto non ha

il “guscio giusto”

Ereditiamo dall’interfaccia

corretta, ed incapsuliamo

Interfaccia corretta

Ereditarietà privata

o contenimento

Adattatore

(classe o oggetto)

Classe vecchia che

vogliamo riciclare

Request gira

il messaggio

Decorator

26

Voglio fare le stesse cose,

ma con un passo in più

Eredito, e sposto le decorazioni

in una classe separata

Il decorator (astratto) eredita da

component, e contiene un

puntatore a component (astratto)

In Operation(), chiamo la

Operation() del decorator, e poi

aggiungo gli abbellimenti

Template method

27

Sequenza fissa di operazioni,

ma alcuni passi variano

Isoliamo lo scheletro, e

ridefiniamo i singoli passi

Classe base:

TemplateMethod

(concreto) definisce

la sequenza virtual or

pure virtual

Classe derivata:

sovrascrive i

singoli passi

Template method: un esempio

28

void VirtualJetProducer::produce(edm::Event& iEvent,

const edm::EventSetup& iSetup)

{

edm::Handle<reco::CandidateView> inputsHandle;

iEvent.getByLabel(src_, inputsHandle);

for(size_t i=0; i<inputsHandle->size(); ++i) {

inputs_.push_back( inputsHandle->ptrAt(i) );

}

fjInputs_.reserve( inputs_.size() );

inputTowers();

runAlgorithm( iEvent, iSetup );

output( iEvent, iSetup );

}

Virtuale pura: passo della

procedura che può (deve)

cambiare nelle classi derivate

Template

Method

Iterator

29

Devo accedere agli elementi di

un contenitore in sequenza

Voglio essere indipendente

dalla struttura del contenitore

Inizio, fine,

elemento corrente,

prossimo elemento

Nelle STL, disaccoppio

contenitori ed algoritmi

Dipendenze di creazione

30

Il nostro codice dipende solo

dalle interfacce (astratte)

Prima o poi, dobbiamo creare le

istanze di classi concrete

?

void BaseClass::run()

{

IOStrategy* s = new IOFromFile();

std::vector<double> ptVec;

s->read( ptVec );

s->write( ptVec );

}

Qui siamo isolati dal modo

in cui leggiamo/scriviamo

il vettore (estensibilità)

Anche se implemento nuove

strategie, e/o derivo da BaseClass,

viene creata sempre IOFromFile

Factory method

31

Elimino le chiamate dirette a

new (non ridefinibile)

Sposto il new dentro una

funzione che crei gli oggetti

Creo i sotto-oggetti

mediante una

funzione virtual

Posso ridefinire la

funzione, e creare

oggetti di tipo nuovo

Sintesi della quarta puntata

32

• Refactoring:

– Come far evolvere il codice.

– In che direzione andare.

– A che livello agire.

• Design pattern (GoF):

– Che cosa sono.

– Perché parlarne.

– Qualche esempio.

Pensieri finali

33

Non si diventa esperti di OOP

in 7-8 ore di lezione

Abbiamo toccato e discusso

gli aspetti fondamentali

Tuttavia…

Di strada insieme ne abbiamo fatta parecchia:

dal C alle classi, all’ereditarietà, al polimorfismo, ai pattern

Quanto detto dovrebbe servire

sia ad inquadrare la filosofia,

sia come linea guida pratica

Volutamente abbiamo mischiato

teoria e codice, concetti di base

e problemi sofisticati

Conclusione

34

Bjarne Stroustrup. The C++ Programming Language, pp. 692 :

Design and programming are human activities; forget that and all is lost.

Il codice deve comunicare

subito che cosa fa

Un codice ben strutturato abbrevia decisamente

il cammino verso la pubblicazione!

Il sistema deve poter essere

esteso facilmente