Refactoring¶
Dove si ristruttura un programma funzionante.
… Ma c’è una certa differenza tra un programma che funziona e un buon programma. Se vogliamo imparare a programmare non dobbiamo accontentarci di un programma che funziona, dobbiamo affinare una certa sensibilità anche all’aspetto estetico. Non dobbiamo affezionarci troppo al nostro prodotto, ma cercare di migliorarlo.
Parametri di default¶
Il risultato del programma è soddisfacente e anche la distanza tra i mattoni sembra adeguata, ma se cambiamo il lato dei quadrati, andrà ancora bene? Parametrizziamo anche quella, ma lo facciamo dandole come valore predefinito 5:
def fila(lato, numero, spazio_colonne=5):
"""Disegna una fila di mattoni."""
for cont_col in range(numero):
quadrato(lato)
tina.up()
tina.forward(lato+spazio_colonne)
tina.down()
tina.up()
tina.back(numero*(lato+spazio_colonne))
tina.down()
In questo modo la funzione fila può essere chiamata con due o con tre argomenti:
Se viene chiamata con due argomenti, il terzo viene automaticamente posto uguale a 5,
>>> fila(30, 3)
Disegna una fila di 3 quadrati di lato 30 distanziati di 5 passi,
Se viene chiamata con tre argomenti, il terzo parametro assumerà il terzo valore.
>>> fila(30, 3, 12)
Disegna una fila di 3 quadrati di lato 30 distanziati di 12 passi.
In questo modo la funzione fila(lato, numero, spazio_colonne=5)
è diventata più flessibile.
Anche la funzione muro()
va cambiata, ci sono un paio di cose che
stonano in questa funzione:
- cinque righe si ripetono quasi identiche, e non va bene che in un programma si ripetano blocchi di istruzioni (quasi) identiche;
- ci sono troppi numeri;
Partiamo dal primo problema: la soluzione è costruire una funzione che le esegua con un solo comando.
Le tre righe che vanno da tina.up()
a tina.down()
producono uno
spostamento di Tartaruga in avanti e a sinistra, senza lasciare traccia.
Possiamo generalizzare questo comportamento aggiungendo oltre allo
spostamento verticale uno orizzontale.
Possiamo costruire una funzione sposta
che riceve come argomenti lo
spostamento nella direzione della Tartaruga e lo spostamento nella sua
direzione perpendicolare. Questa funzione dovrà avere quindi due parametri:
def sposta(avanti=0, sinistra=0):
"""Effettua uno spostamento di Tartaruga
in avanti e verso sinistra senza disegnare la traccia."""
tina.up()
tina.forward(avanti)
tina.left(90)
tina.forward(sinistra)
tina.right(90)
tina.down()
Scrivendo i parametri in questo modo, possiamo chiamare la funzione sposta in vari modi:
sposta()
, non fa niente;sposta(47)
, sposta avanti Tartaruga di 47 unità senza tracciare segni;sposta(47, 61)
, sposta Tartaruga avanti di 47 e a sinistra di 61 unità senza tracciare segni;sposta(sinistra=61)
, sposta Tartaruga a sinistra di 61 unità senza tracciare segni;
Anche la funzione fila
può essere migliorata usando questa funzione:
def fila(lato, numero, spazio_colonne=5):
"""Disegna una fila di mattoni quadrati."""
for cont_col in range(numero):
quadrato(lato)
sposta(avanti=lato+spazio_colonne)
sposta(avanti=-numero*(lato+spazio_colonne))
E muro diventa:
def muro():
"""Disegna un muro di quadrati."""
for cont_rig in range(15):
fila(20, 18,)
sposta(sinistra=20+5)
sposta(sinistra=-15*(20+5))
Nota
in queste funzioni ho utilizzato un terzo metodo per inserire un argomento in un parametro: il passaggio dell’argomento «per nome».
Confrontando la versione precedente di muro
con questa si può notare
una bella semplificazione!
Ora occupiamoci di eliminare un po” di numeri parametrizzando anche la
procedura muro
. I numeri presenti nella funzione riguardano:
la lunghezza del lato, il numero di righe, il numero di colonne, lo spazio
tra le righe, lo spazio tra le colonne.
Questi due ultimi valori possono essere lasciati di default uguali a 5.
Trasformandoli tutti in parametri la funzione muro
diventa:
def muro(lato, righe, colonne, spazio_colonne=5, spazio_righe=5):
"""Disegna un muro di mattoni."""
for cont_rig in range(righe):
fila(lato, colonne, spazio_colonne)
sposta(sinistra=lato+spazio_righe)
sposta(sinistra=-righe*(lato+spazio_righe))
E tutto il programma diventa:
# Lettura delle librerie
import pygraph.pyturtle as tg
# Definizione di funzioni
def sposta(avanti=0, sinistra=0):
"""Effettua uno spostamento orizzontale e verticale
di Tartaruga senza disegnare la traccia."""
tina.up()
tina.forward(avanti)
tina.left(90)
tina.forward(sinistra)
tina.right(90)
tina.down()
def quadrato(lato):
"""Disegna un quadrato di dato lato vuoto o pieno."""
for cont in range(4):
tina.forward(lato)
tina.left(90)
def fila(lato, numero, spazio_colonne=5):
"""Disegna una fila di mattoni quadrati."""
for cont_col in range(numero):
quadrato(lato)
sposta(avanti=lato+spazio_colonne)
sposta(avanti=-numero*(lato+spazio_colonne))
def muro(lato, righe, colonne, spazio_colonne=5, spazio_righe=5):
"""Disegna un muro di mattoni."""
for cont_rig in range(righe):
fila(lato, colonne, spazio_colonne)
sposta(sinistra=lato+spazio_righe)
sposta(sinistra=-righe*(lato+spazio_righe))
# Programma principale
piano = tg.TurtlePlane()
tina = tg.Turtle()
sposta(-250, -190)
muro(20, 15, 20)
# Rende attiva la finestra grafica
piano.mainloop()
Compattiamo il codice¶
Funziona tutto a meraviglia, o almeno dovrebbe funzionare se non ho introdotto qualche errore! Potremmo considerarci soddisfatti se l’istinto del programmatore non rodesse dentro… Perdendo un po” in chiarezza possiamo compattare di più il codice. Vale la pena? Dipende da ciò che si vuole ottenere, comunque, prima di dare un giudizio proviamo un’altra versione.
Prima avevamo staccato delle righe di codice per fare una funzione separata
che realizzasse gli spostamenti data la componente orizzontale e verticale.
Ora fondiamo due funzioni che hanno degli elementi in comune: la procedura
muro
e la procedura fila
. L’intero muro si può ottenere annidando,
uno dentro l’altro due cicli: il ciclo più esterno impila le file e quello
più interno allinea quadrati. In pratica al posto della chiamata alla
procedura fila(...)
, trascriviamo tutte le sue istruzioni.
Dobbiamo anche aggiustare i nomi e l’indentazione:
def muro(lato, righe, colonne, spazio_colonne=5, spazio_righe=5):
"""Disegna un muro di mattoni."""
for cont_rig in range(righe): # disegna tutte le righe
for cont_col in range(colonne): # disegna una riga
quadrato(lato)
sposta(avanti=lato+spazio_colonne)
sposta(avanti=-colonne*(lato+spazio_colonne))
sposta(sinistra=lato+spazio_righe)
sposta(sinistra=-righe*(lato+spazio_righe))
I due cicli annidati devono avere due variabili diverse
noi abbiamo usato cont_rig
e cont_col
,
ma spesso si usano per queste variabili di ciclo i nomi i
e j
.
Possiamo ancora eliminare una riga di codice! Come?
Alla fine del ciclo più interno ci sono due chiamate alla funzione
sposta
che possono essere sostituite da una sola.
Nota
Avere cicli annidati di solito non è una buona idea: di solito rende il codice meno comprensibile. Quindi è una scelta da fare consapevolmente.
Nota
Il fatto di ridurre le dimensioni di un programma non è solo una questione di spazio, ma è fondamentale per renderne più semplice la manutenzione.
E con questo il programma è terminato…
# Lettura delle librerie
import pygraph.pyturtle as tg
# Definizione di funzioni
def sposta(avanti=0, sinistra=0):
"""Effettua uno spostamento orizzontale e verticale
di Tartaruga senza disegnare la traccia."""
tina.up()
tina.forward(avanti)
tina.left(90)
tina.forward(sinistra)
tina.right(90)
tina.down()
def quadrato(lato):
"""Disegna un quadrato di dato lato vuoto o pieno."""
for cont in range(4):
tina.forward(lato)
tina.left(90)
def muro(lato, righe, colonne, spazio_righe=5, spazio_colonne=5):
"""Disegna un muro di mattoni."""
for cont_rig in range(righe): # disegna tutte le righe
for cont_col in range(colonne): # disegna una riga
quadrato(lato)
sposta(avanti=lato+spazio_colonne)
sposta(-colonne*(lato+spazio_colonne), lato+spazio_righe)
sposta(sinistra=-righe*(lato+spazio_righe))
# Programma principale
piano = tg.TurtlePlane()
tina = tg.Turtle()
sposta(-250, -190)
muro(20, 15, 20)
# Rende attiva la finestra grafica
piano.mainloop()
…o quasi…
Riassumendo¶
- Quando è possibile è meglio sostituire i numeri e le costanti presenti in una funzione con parametri.
- Le procedure possono avere anche dei parametri con dei valori predefiniti (di default).
- Gli argomenti di una funzione possono essere passati anche per nome.
- Più linee di istruzioni che si ripetono all’interno di un programma possono essere raggruppate in un’unica funzione.
- All’interno di un ciclo possono essere annidati altri cicli, bisogna fare attenzione al nome delle variabili.
- Un programma più breve, più semplice e con nomi significativi è meglio di un programma più lungo, più complicato e con nomi insensati.