Descomposició
Quan un problema és complex, la primera pregunta no és «quina ordre escric primer?». La primera pregunta és «quines parts té aquest problema?» En aquest capítol aprendrem a dividir qualsevol missió en fases amb noms clars, de manera que el programa es llegeixi quasi com un pla escrit en català.
Dividir per vèncer
Fins ara hem resolt problemes petits: pujar un graó, avançar cinc caselles, recollir una perla. Però, i si la missió d'en Karel té tres parts ben diferenciades?
Imagina que en Karel ha de: 1) anar a buscar la primera perla, 2) anar a buscar la segona perla, i 3) tornar al punt de partida. Si escrius totes les ordres seguides, obtens una llista opaca que costa d'entendre i de corregir.
La solució és la descomposició: partir el problema gran en subproblemes que pots resoldre per separat i als quals dones un nom clar. Cada subproblema es converteix en una funció def. Quan tens totes les funcions, el programa principal és simplement la llista de fases.
Si pots descriure la solució en tres frases («primer en Karel fa X, llavors fa Y, finalment fa Z»), ja tens l'estructura del programa.
Un programa que s'explica sol
Llegeix el codi següent. Sense saber els detalls de cada funció, entens el que fa el programa?
def recull_perla_est():
move()
move()
grab()
def recull_perla_nord():
turn_left()
move()
move()
grab()
def torna_al_inici():
turn_around()
move()
move()
turn_right()
move()
move()
recull_perla_est()
recull_perla_nord()
torna_al_inici()
Sí: primer recull la perla de l'Est, llavors la del Nord, i finalment torna a l'inici. Les tres últimes línies expliquen el pla sencer. Les diferents funcions amaguen els detalls. Si vols saber com funciona un dels passos, vas a la definició. Si vols entendre el pla general, llegeixes les tres crides.
La instrucció turn_around() fa que Karel giri 180°, és a dir, dona mitja volta però sense desplaçar-se.
Nota: en el nostre curs, el programa s'executa directament de dalt a baix — no cal escriure def main():. Però l'estratègia de pensar en fases i donar-los noms és exactament la mateixa.
Missió en tres fases
En Karel és al cantó esquerre d'un corredor. Hi ha una perla dues caselles endavant (Est) i una altra dues caselles al Nord. La seva missió: recollir-les totes dues i tornar al punt de partida.
Observa com el codi reflecteix exactament el pla de tres fases:
Les tres últimes línies del programa es llegeixen quasi com un pla escrit en llengua natural. Aquesta és la força de la descomposició: el codi explica el que fa sense que hagis d'anar línia per línia.
El que s'espera en entrar i en sortir
Quan defineixes una funció nova, és útil pensar en dos moments clau: on és en Karel quan la funció comença? (precondició) i on és quan acaba? (postcondició).
Per exemple, recull_perla_est() assumeix que en Karel mira cap a l'Est i la perla és dues caselles endavant. Si la crides en una situació diferent, el resultat no serà el que esperes.
Escriure un comentari breu que indiqui la precondició i postcondició d'una funció és una bona pràctica quan escrivim codi informàtic:
# Precondició: Karel mira Est, perla a 2 caselles del davant
# Postcondició: Karel és sobre la perla (motxilla +1), mirant Est
def recull_perla_est():
move()
move()
grab()
No és obligatori, però t'ajudarà molt quan el programa comenci a créixer. Quan una funció acaba sempre a la mateixa posició i orientació, és molt més fàcil encadenar-lo amb la següent.
Consell pràctic: dissenya cadascuna de les funcions perquè en Karel quedi en una posició ben definida quan acabi. Això s'anomena deixar el robot net: la funicó recull i deixa en Karel com el va trobar (o en l'estat que la següent funció necessita).
Descomposició i repetició juntes
La descomposició no exclou els bucles for: pots combinar les dues tècniques. Una funció pot contenir un for al seu interior, i el programa principal pot cridar aquella funció tantes vegades com calgui.
En l'exemple de sota, en Karel ha de col·locar dues perles en dos llocs separats. Cada fase és una funió independent, que internament usa move() i drop():
Fixa't que cada funció sap on comença (precondició) i on acaba (postcondició). deixa_perla_a_col2() acabarà a la columna 2 mirant Est, que és exactament on comença deixa_perla_a_col5(). Les fases s'encadenen sense problemes.
Exercici
recull_primera(), recull_segona() i recull_tercera(). A continuació, has de cridar les tres funcions, en ordre.
Pista: pensa en com en Karel queda posicionat després de cada fase. recull_primera() el deixa a la columna 1 mirant Est; recull_segona() ha de partir des d'allà i arribar a la columna 3, etc.