Codi net
Que un programa funcioni no vol dir que estigui ben escrit. En aquest capítol veuràs per què val la pena escriure codi net, amb exemples concrets i situacions que reconeixeràs.
El problema del copia-enganxa
En Karel ha de recórrer un corredor recollint totes les perles que troba. Aquí tens dues solucions que fan exactament el mateix. Llegeix-les i fixa't en les diferències:
move()
if pearl_here():
grab()
move()
if pearl_here():
grab()
move()
if pearl_here():
grab()
move()
if pearl_here():
grab()
move()
if pearl_here():
grab()
move()
if pearl_here():
grab()
def avançar_i_recollir():
move()
if pearl_here():
grab()
while front_is_clear():
avançar_i_recollir()
El codi brut té un problema molt concret: si el corredor canvia de mida, has d'afegir o treure línies a mà. Si vols canviar la lògica —per exemple, deixar una perla en lloc de recollir-la— has de fer el canvi en sis llocs alhora. I és molt fàcil oblidar-ne un.
La versió neta diu literalment el que fa: «mentre puguis avançar, avança i recull». Funciona per a qualsevol mida de corredor sense tocar res. Comprova-ho: el codi és idèntic en els tres mons però s'adapta als corredors de 5, 7 i 10 caselles:
Aquesta és la primera regla del codi net: no dupliquis codi innecessàriament. Quan veus el mateix bloc repetit dues o tres vegades, és el moment de crear una funció i usar un bucle.
Aquesta idea té un nom en el món de la programació: DRY, que significa Don't Repeat Yourself («no et repeteixis»). El seu contrari —el codi que acabes de veure repetit sis vegades— es diu WET (Write Everything Twice). Quan escrius codi net, escrius codi DRY.
Els noms importen
El nom que li poses a una funció és la primera cosa que llegirà qualsevol persona que vegi el teu codi, inclòs tu mateix l'endemà. Aquí tens dues versions d'un programa que fan el mateix: en Karel recorre un corredor i recull cada perla que troba:
def p():
if pearl_here():
grab()
while front_is_clear():
move()
p()
def recollir_si_hi_ha_perla():
if pearl_here():
grab()
while front_is_clear():
move()
recollir_si_hi_ha_perla()
Amb p() no pots saber què fa la funció sense llegir el seu interior. Amb recollir_si_hi_ha_perla(), el codi principal es llegeix com una frase: «mentre puguis avançar, avança i recull si hi ha perla». No cal ni anar a mirar la definició.
Observa-ho en acció. El codi de la dreta —la versió neta— és el que s'executa:
La segona regla: posa noms que expliquen el que fa la funció. recollir_si_hi_ha_perla() és molt millor que p(). avançar_fins_a_paret() és molt millor que f(). El nom és la documentació més bàsica que pots donar al teu codi.
Una funció, una idea
Cada funció hauria de fer una sola cosa i tenir un nom que la descrigui exactament. Si quan expliques el que fa una funció necessites dir «fa X i també fa Y», és senyal que potser hauries de partir-la en dues.
Aquí en Karel ha d'avançar fins al final d'un corredor i deixar-hi una perla:
def anar_i_deixar_perla():
while front_is_clear():
move()
drop()
anar_i_deixar_perla()
def avançar_fins_a_paret():
while front_is_clear():
move()
# Cada acció és independent i reutilitzable.
avançar_fins_a_paret()
drop()
La versió neta té un avantatge clar: avançar_fins_a_paret() és tan genèrica que la pots reutilitzar en qualsevol repte futur, en qualsevol direcció, sense importar el que vulguis fer en arribar. La versió bruta lliga el moviment amb l'acció de deixar una perla, de manera que la funció només serveix per a aquest cas concret i no pot aprofitar-se en cap altre context.
La tercera regla: cada funció, una idea. Funcions petites i enfocades en una sola tasca són fàcils d'entendre, fàcils de modificar i molt fàcils de reutilitzar en altres programes.
Codi net avui, temps guanyat demà
El codi net no és qüestió d'estètica. Té efectes molt concrets i pràctics que notes des del primer dia:
⏱ A curt termini
- Trobes errors més de pressa. Amb
avançar_i_recollir()saps exactament on mirar. Ambf()ibb()has de llegir funció per funció fins a entendre qui fa què. - Modifiques en un sol lloc. Vols que en Karel deixi dues perles en lloc d'una? Canvies la definició de la funció i el canvi s'aplica arreu. Amb codi repetit, has de buscar i modificar cada còpia.
- Rellegeixes el teu codi l'endemà.
while front_is_clear(): avançar_i_recollir()s'entén d'un cop d'ull. Sis blocs iguals copiats t'obliguen a reubicar-te de zero.
📅 A mig termini
- Reutilitzes el que ja has escrit. Tens
recollir_si_hi_ha_perla()d'un repte anterior? La copies directament al repte nou. Funciona sense errors, sense reescriure res. - Construeixes comportaments complexos.
avançar_fins_a_paret()+recollir_si_hi_ha_perla()+turn_around()combinades donen un programa sofisticat llegible en tres línies. - El codi s'adapta sol. Si el món canvia —corredor més llarg, més perles— el teu codi net sovint no cal tocar-lo. El brut, sí.
🌍 A llarg termini
- Pots compartir-lo. Un company llegeix
while front_is_clear(): avançar_i_recollir()i l'entén sense que li expliquis res. El codi brut necessita una explicació verbal. - El codi es llegeix molt més que no s'escriu. En el món professional, el mateix codi el llegeixen desenes de persones durant anys. Escriure'l net és un acte de respecte cap als qui vindran després (incloent el tu del futur).
- Col·laborar es torna possible. Sense noms clars i funcions petites, treballar en equip en un mateix projecte és un caos.
En resum, tres regles simples:
- No dupliquis codi innecessàriament. Si veus el mateix bloc repetit dues o tres vegades, crea una funció i usa un bucle.
- Posa noms que expliquen.
avançar_i_recollir()sempre és millor quefer_cosa(). - Cada funció, una idea. Si quan expliques una funció uses la paraula «i també», considera partir-la en dues.
Elimina la repetició
while. El resultat ha de ser el mateix: en Karel ha de recórrer tot el corredor i recollir totes les perles.
Codi brut que has de netejar:
# ✗ NO copiïs això — reescriu-lo net!
move()
if pearl_here():
grab()
move()
if pearl_here():
grab()
move()
if pearl_here():
grab()
move()
if pearl_here():
grab()
move()
if pearl_here():
grab()
move()
if pearl_here():
grab()
move()
if pearl_here():
grab()
Pista: crea una funció avançar_i_recollir() que faci el move() i el if pearl_here(): grab(). Després usa un while front_is_clear(): per cridar-la tantes vegades com calgui, sigui quin sigui el corredor.
Noms i estructura
while. Reescriu-lo net.
Codi brut que has de netejar:
# ✗ NO copiïs això — reescriu-lo net!
# Problema 1: f i g no diuen res sobre el que fan
# Problema 2: els tres move() s'haurien d'expressar amb while
def f():
turn_left()
turn_left()
turn_left()
def g():
if pearl_here():
grab()
move()
move()
move()
f()
move()
g()
Pista: canvia els noms de f i g per noms que descriguin el que fan. Afegeix una funció avançar_fins_a_paret() que usi while front_is_clear(): move() en lloc dels tres move() explícits. Recorda que turn_right() existeix com a instrucció directa si vols simplificar el gir.
Neteja total
turn_left() que es podria fer en una sola instrucció. Troba tots els problemes i reescriu el codi completament net.
Codi brut que has de netejar:
# ✗ NO copiïs això — reescriu-lo net!
def aa():
move()
def bb():
if pearl_here():
grab()
def cc():
aa()
bb()
def dd():
turn_left()
turn_left()
def ee():
if not bag_is_empty():
drop()
def ff():
aa()
ee()
cc()
cc()
cc()
cc()
cc()
cc()
cc()
dd()
ff()
ff()
ff()
ff()
ff()
ff()
ff()
Set problemes a corregir:
- Les funcions
aa,bb,cc,dd,ee,ffno expliquen res amb els seus noms. cc()fa dues coses alhora: avançar i recollir. Viola la regla «una funció, una idea».ff()fa dues coses alhora: avançar i deixar una perla. Mateixa violació.dd()usa dosturn_left()per fer mitja volta, quan existeix la instruccióturn_around().- Els set
cc()consecutius s'han de substituir per unwhile front_is_clear():. - Els set
ff()consecutius s'han de substituir per unwhile front_is_clear():. - Les funcions
aaibbgairebé no calen si defineixes bé les funcions compostes.