Primers programes amb funcions
Anteriorment, heu après a definir funcions, i a fer petits test-drivers per cridar-les. Ara començareu a fer els primers programes utilitzant funcions i sobretot, a identificar quines funcions reclama cada programa.
Com programar amb funcions
Els passos per a fer un programa utilitzant funcions són semblants als que utilitzàveu en el bloc anterior per programar sense definir funcions. L’única diferència és que entre el pas de fer el refinament i el de codificar, caldrà pensar les funcions que utilitzareu i determinar-ne el docstring.
En el moment de la codificació, el primer que haureu de fer és programar les funcions previstes. El pas final serà reunir totes les funcions en un programa principal que les vagi cridant seguint el refinament inicial.
Primer programa amb funcions
El primer programa que fareu utilitzant funcions serà:
Llegir un nombre n i mostrar els n primers nombres primers.
En observar el codi d’aquest programa, veureu que, a diferència d’altres unitats que s’utilitzaven nous conceptes i instruccions, podeu entendre’l de cap a peus i no us cal cap explicació addicional.
import sys def es_primer(n): ''' Funció que diu si un nombre és primer Entrada: un enter positiu major que 1 output: boolean ''' # per tots els nums mes petits for i in range(2, n): # si es un divisor? if n % i == 0: # no es primer return False return True #1. llegir la quantitat de primers quantitat = int(sys.argv[1]) #2. Per cada nombre des del 2 fins a aconseguir-ne la quantitat: comptador = 0 num = 2 while comptador < quantitat: #3 Si és primer if es_primer(num): #4 mostrar-lo print(num) #5 comptar-lo comptador += 1 num += 1
La novetat d’aquesta unitat recau en comprendre quan cal definir les funcions adequades i com fer-ho.
El factor més rellevant per a fer un bon programa, amb un bon ús i una bona definició de funcions és seguir el refinament que s’ha de fer abans de codificar. Fixeu-vos que les funcions utilitzades venen determinades per les accions descrites en el refinament. El programa principal s’encarrega d’anar cridant-les (en aquest cas només n’hi ha una) i enllaçar els resultats.
Passos per a resoldre un programa incorporant funcions
En aquest apartat se seguiran un a un els passos de la metodologia per a fer els programes. Aprendreu a aplicar-los quan l’objectiu és programar de manera modular.
Joc de proves i especificacions d'entrada
Especificacions d’entrada: un nombre enter major a zero.
A la taula hi ha el joc de proves del programa:
| Entrada | Sortida |
|---|---|
| 1 | 2 |
| 5 | 2, 3, 5, 7, 11 |
| 10 | 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 |
Refinament
Refinar és el moment clau de la programació modular. Sempre ho és en fer un programa, però com que les funcions no deixen de ser mòduls independents al que el programa principal fa crides, és el refinament qui determina quines funcions s’han d’utilitzar i definir.
El primer refinament (el del programa principal) seria:
#1. llegir la quantitat de primers (n) #2. Per cada nombre des del 2 fins a aconseguir-ne n: #3 Si és primer #4 mostrar-lo #5 comptar-lo
Cal estudiar cada apartat del refinament i decidir si convé definir una funció pròpia (o utilitzar-ne una que ja hàgim definit), utilitzar una funció predefinida del Python o “encarregar” la feina directament al programa principal. Cada línia del refinament representa una acció, i ara us heu de preguntar quines de les accions s’han d’expandir amb una funció.
Pot ser que es puguin utilitzar funcions del sistema (com llegir o mostrar) i en altres accions cal implementar una estructura de control del flux (el pas 2 es correspon a una estructura repetitiva). Però n’hi ha altres, com és el tercer apartat en el qual es pretén calcular si un nombre és o no primer, que s’ha de decidir crear (o utilitzar) una funció pròpia.
En aquest moment, i abans de codificar, caldrà definir el docstring de la funció (que en aquest exemple ja teníeu):
def es_primer(n): ''' Funció que diu si un nombre és primer Entrada: int major que 1 Sortida: bool '''
El pas de refinar ja l’heu de fer en el mateix fitxer on més endavant hi haurà el codi de la funció. Cada vegada que decidiu expandir un pas del refinament definint una funció, heu de declarar la funció amb el seu docstring a dalt del fitxer (després dels imports).
Codificar el programa
Una vegada teniu el refinament fet i heu decidit quines funcions us cal definir, cal començar per codificar i comprovar-les.
Codificar cada funció Fer una funció és com fer un petit programa, i caldrà que feu per a cada una d’elles tots els passos per a resoldre’l: el joc de proves, refinar, codificar i verificar. Sembla que hàgiu entrat en un bucle, però no és així, ja que el problema que teniu ara és més senzill que el problema inicial. Programar de manera modular és descompondre un gran problema complicat en petits problemes més senzills.
En aquest punt cal crear un nou fitxer per a codificar les noves funcions. Necessitareu un fitxer diferent d’on s’ha començat a fer el refinament del programa principal perquè abans d’utilitzar la funció en el programa, heu d’estar segurs que funciona. Perseguir tots els errors al final és molt més costós que fer-ho per parts.
En aquest nou fitxer hi haurà totes les noves funcions definides i un test-driver per a cada una d’elles. Un test driver és un programa principal que crida a la funció per testejar el seu correcte funcionament.
Si en un fitxer heu definit més d’una funció, els test drivers, és a dir, les crides a les funcions per testejar-les, han d’anar tots junts després de les definicions de totes les funcions (ja que el programa principal estarà compost de diversos test drivers).
Abans de codificar cada funció cal definir: les especificacions d’entrada (que ja les teniu al docstring), el joc de proves (que serà el test-driver) i fer-ne el refinament.
El fitxer complet amb les funcions d’aquest exemple serà:
def es_primer(n): ''' Funció que diu si un nombre és primer Entrada: un enter positiu major que 1 output: bool ''' # per tots els nums mes petits for i in range(2, n): # si es un divisor? if n % i == 0: # no es primer return False return True print(es_primer(2)) print(es_primer(4)) print(es_primer(100)) print(es_primer(97))
Codificar el programa principal
Quan ja teniu totes les funcions definides i verificades per separat cal muntar el programa principal com si fos un puzle. Després de la capçalera es posen els imports, després les definicions de les funcions (que heu de copiar del fitxer on les heu testat), i més endavant el programa principal. El resultat d’aquests passos és el fitxer del programa de l’apartat anterior.
Si hi ha control d’errors, aquest es posa just abans del programa principal (el control d’errors ja és programa pròpiament dit, perquè el flux d’execució ha de passar primer pel control d’errors abans d’entrar en el programa principal).
Resolució del mateix programa amb un refinament diferent
Tal com s’ha indicat, el refinament en un programa és el pas determinant que porta a definir unes o altres funcions que acabarà cridant el programam principal. Pot haver-hi diferents maneres de resoldre un mateix programa, inclús es poden definir diferents funcions segons el refinament inicial. Si se segueix un refinament correcte i es defineixen funcions “naturals”, el codi d’un programa pot ser correcte amb versions diferents.
En aquest apartat procedirem a codificar el mateix programa seguint un refinament alternatiu. L’enunciat seria:
Fer un programa que llegeixi un nombre n i mostri els n primers nombres primers. Aquest programa ha de ser robust.
Com ja sabeu, s’ha de codificar i verificar el programa principal suposant que les entrades són correctes (acceptant que acompleixen les especificacions d’entrada). Una vegada comprovat, haureu de procedir a afegir el control d’errors.
Joc de proves i especificacions d'entrada (del programa principal)
Especificacions d’entrada: un nombre enter major o igual a zero.
El joc de proves podeu veure’l en la taula.
Evidentment, l’algorisme per a resoldre un programa no ha d’afectar el joc de proves ni a les especificacions d’entrada del programa.
Refinament
El refinament seria el següent:
#1. llegir la quantitat (n´úmero de primers a mostrar) #2. repetir fins a arribar a la quantitat #3. mostrar el següent primer
Fixeu-vos que aquest refinament és substancialment diferent del que s’havia fet en el capítol anterior.
Tria de funcions:
- Per llegir l’entrada (línia 1) utilitzareu
sys.argv. - El segon pas del refinament s’implementarà amb una estructura repetitiva (en aquest cas, la més adequada serà un
for). - “mostrar el següent primer” indica dues accions: calcular el següent primer i mostrar-lo. Està clar que per mostrar-lo s’utilitzarà
print(). Però per calcular el següent primer caldrà definir una funció: té una finalitat clara, i es veu de manera senzilla i natural quins són els tipus d’entrada i sortida. Tot i que en principi sembla que no serà una funció que necessitem reutilitzar en futurs programes, amb aquest refinament aquesta és una funció que cal definir.
Hi ha una única funció. El docstring serà:
def seguent_primer(n): ''' Funció que mostra el següent nombre primer posterior a n Entrada: enter positiu major que zero Sortida: int '''
Fase de codificació
Cal començar programant les funcions que utilitzarà el programa. En aquest moment heu de crear el fitxer on definireu i provareu les funcions a utilitzar.
Cada funció és un petit programa, i per a cada funció haureu de buscar el joc de proves (que escriureu en el test-driver), les especificacions d’entrada (que en aquest cas estan definides al docstring), refinar i codificar.
Abans de començar a codificar la funció seguent_primer(n), el fitxer ha de ser semblant al següent:
def seguent_primer(n):
'''
Funció que mostra el següent nombre primer posterior a n
Entrada: enter positiu major que zero
Sortida: int
'''
# següent nombre
# mentre no sigui primer, buscar el següent nombre
# test-driver
print('seguent_primer()')
print(seguent_primer(1))
print(seguent_primer(1))
print(seguent_primer(20))
print(seguent_primer(23))
En aquest moment heu de tornar a decidir si alguna de les accions d’aquests comentaris necessita expandir-se o no en una nova funció.
La segona línia deixa clar que hi ha una estructura repetitiva i que en aquest bucle caldrà comprovar si un nombre és primer. De cap manera heu de repetir codi: ja teniu una funció que calcula si un nombre és o no primer. La funció següent_primer() cridarà la funció es_primer() que ja teniu definida. Com que aquesta última funció ja la teniu testada, només caldrà copiar-la en el fitxer (el test-driver no cal tornar-lo a fer).
En resum, el fitxer amb la definició i test-driver de la funció seguent_primer() ha d’assemblar-se al següent:
def seguent_primer(n):
'''
Funció que mostra el següent nombre primer posterior a n
Entrada: enter positiu major que zero
Sortida: int
'''
# següent nombre
n = n + 1
# mentre no sigui primer, buscar el següent nombre
while not es_primer(n):
n = n + 1
return n
def es_primer(n):
'''
Funció que diu si un nombre és primer
Entrada: un enter positiu major que 1
output: boolean
'''
# per tots els nums mes petits
for i in range(2, n):
# si es un divisor?
if n % i == 0:
# no es primer
return False
return True
# test-driver
print('seguent_primer()')
print(seguent_primer(1))
print(seguent_primer(1))
print(seguent_primer(20))
print(seguent_primer(23))
Quan les funcions estan definides i testades, ja podeu codificar el programa principal, que anirà gestionant les dades, enviant-les i rebent-les de les funcions quan sigui necessari.
El resultat final del programa principal (sense el control d’errors serà el següent):
import sys def seguent_primer(n): ''' Funció que mostra el següent nombre primer posterior a n Entrada: enter positiu major que zero Sortida: int ''' # següent nombre n = n + 1 # mentre no sigui primer, buscar el següent nombre while not es_primer(n): n = n + 1 return n def es_primer(n): ''' Funció que diu si un nombre és primer Entrada: un enter positiu major que 1 output: bool ''' # per tots els nums mes petits for i in range(2, n): # si es un divisor? if n % i == 0: # no es primer return False return True #1. llegir la quantitat de primers quantitat = int(sys.argv[1]) primer = 2 print(primer) # repetir fins a tenir la quantitat for num in range(primer, quantitat + 1): # mostrar el següent primer primer = seguent_primer(primer) print(primer)
Fixeu-vos que primer van els import, després les definicions de les funcions i finalment el programa principal.
Verificació
En aquest punt és necessari comprovar que s’acompleix el joc de proves. També caldrà depurar i modificar el programa si no fos així.
Control d'errors
Per pensar el control d’errors, cal començar amb el joc de proves dels errors: trobar i anar descartant totes les dades que no acompleixin les especificacions d’entrada, i fer-ho de manera ordenada assegurant d’haver descartat les dades dolentes abans de fer un pas “perillós” (com pot ser aplicar la funció int().
Sempre cal començar evitant que el programa s’interrompi bruscament en el cas que sigui executat sense argument. Quan es programa el control d’errors mai es parla d’especificacions d’entrada, ja que per la naturalesa del control d’errors, el programa per ser robust s’ha de preparar per rebre totes les dades que li puguin arribar. Com que en aquest cas la lectura es fa per línia d’ordres, les dades introduïdes seran sempre str.
El joc de proves del control d’errors podeu veure’l en la taula.
| Entrada | És acceptada? |
|---|---|
| (sense argument) | False |
| 2 3 patata | True |
| 0 | False |
| -12 | False |
| +23 | True |
| 1-2 | False |
| patata | False |
| 2. | False |
| 1 | True |
Fixeu-vos en el segon exemple d’execució (2 3 patata). S’accepta perquè l’argument que s’utilitzarà és el primer, que acompleix les especificacions d’entrada. Seguint el que fan moltes aplicacions de linux, es considera que l’entrada és vàlida perquè el programa s’executarà amb normalitat. Els arguments de més no molesten, ja que s’ignoren i no passa res.
També estaria bé determinar el contrari i decidir que aquesta entrada no és acceptada. Però és un cas que s’ha de posar en el joc de proves perquè si no pot haver-hi una ambigüitat.
Codificar i afegir el control d'errors
En codificar el control d’errors s’ha de fer com amb qualsevol altre fragment de programa: refinar i decidir si es pot expandir o no amb una funció. La diferència és que no se segueixen els preceptes de la programació estructurada i que cada vegada que detecteu una dada que no acompleix les especificacions d’entrada, mostreu un missatge i interrompeu l’execució.
En programar el control d’errors s’ha de fer el refinament i determinar si cal expandir-lo amb una funció o no. En aquest exemple s’ha utilitzat la funció es_enter(), que ja s’havia definit prèviament, i per això s’ha copiat sense haver de fer el test-driver (perquè ja s’havia fet).
Finalment, i unint totes les peces que s’han programat per separat, el fitxer final del programa serà:
import sys
def seguent_primer(n):
'''
Funció que mostra el següent nombre primer posterior a n
Entrada: enter positiu major que zero
Sortida: int
'''
# següent nombre
n = n + 1
# mentre no sigui primer, buscar el següent nombre
while not es_primer(n):
n = n + 1
return n
def es_primer(n):
'''
Funció que diu si un nombre és primer
Entrada: un enter positiu major que 1
output: boolean
'''
# per tots els nums mes petits
for i in range(2, n):
# si es un divisor?
if n % i == 0:
# no es primer
return False
return True
def es_enter(cadena):
'''
Entrada: str
Sortida: bool
'''
# eliminar el signe,
if cadena[0:1] in '+-':
cadena = cadena[1:]
return cadena.isdigit()
MISSATGE = f'Usage: {sys.argv[0]} enter_positiu'
# CONTROL D'ERRORS
# hi ha argument
if len(sys.argv) < 2:
print(MISSATGE)
exit(1)
cadena = sys.argv[1]
# si no és enter
if not (es_enter(cadena)):
print(MISSATGE)
exit(1)
quantitat = int(cadena)
# si no és estrictament positiu
if not quantitat > 0:
print(MISSATGE)
exit(1)
# PROGRAMA PRINCIPAL
primer = 2
# repetir fins a tenir la quantitat
for num in range(primer, quantitat + 1):
# mostrar el següent primer
print(primer)
primer = seguent_primer(primer)
Programació estructurada i modular
En aquest objecte d’aprenentatge es parla sobre els conceptes de programació imperativa, programació modular i programació estructurada. Ja heu deixat enrere el paradigma de la programació imperativa, i ara us he de centrar a escriure programes que respectin la programació modular i la programació estructurada, seguin els consells proporcionats.
Les constants se solen posar abans de definir les funcions. Però també hi ha el criteri de definir les variables prop d’on s’han d’utilitzar. És per això que la constant MISSATGE s’ha definit al principi del programa (en el bloc del control d’errors), i no abans de les funcions.
Paradigmes de programació
La programació imperativa és un paradigma de programació en el qual els programes descriuen pas a pas com s’han d’executar les tasques. Aquesta forma de programar se centra en escriure instruccions que indiquen a la computadora com realitzar les operacions desitjades.
La programació modular és un enfocament de desenvolupament de programari que consisteix a dividir un programa en mòduls més petits, independents i fàcilment gestionables. Cada mòdul compleix una funció específica o realitza una tasca concreta del programa. Aquesta tècnica permet organitzar el codi font d’una manera més estructurada i ordenada, facilitant el desenvolupament, la comprensió i el manteniment del software.
La programació estructurada és un paradigma de programació que es basa en l’ús de tres estructures principals per controlar el flux d’execució d’un programa: seqüència, selecció (condicionals) i iteració (bucles). Aquest paradigma va sorgir com a resposta a les preocupacions sobre la claredat, la comprensibilitat i la fiabilitat del codi dels programes.
En la programació imperativa els programes es dissenyen de manera que el programa principal sempre té el control del flux, utilitzant les estructures de control, les variables i els operadors.
En la programació modular el programa principal cedeix el flux a altres mòduls de codi (funcions o altres subrutines). Aquestes executen uns càlculs i retornen informació al programa principal, que agafa de nou el control del flux i continua amb l’execució.
Fins ara heu après a programar seguint el paradigma de la programació estructurada i imperativa. L’objectiu d’ara en endavant és que programeu segons la programació estructurada i modular.
Consells per a la programació estructurada modular
Ara ja teniu un coneixement suficient sobre com definir i utilitzar funcions per poder introduir alguns consells i recomanacions sobre com estructurar-les, per a aconseguir que segueixin el paradigma de la programació estructurada i així incrementar-ne la llegibilitat i aconseguir facilitar-ne el manteniment.
Aquest consell mai no s’ha d’agafar al peu de la lletra: definireu funcions l’objectiu de les quals serà mostrar la sortida d’una manera determinada, o agrupar les dades d’entrada en una estructura adient pel programa principal. Però en aquests casos el seu únic objectiu serà mostrar o llegir les dades.
L'encarregat de la gestió de les E/S del programa ha de ser el programa principal
El programa principal actua com a punt focal del flux d’execució del programa. Centralitzant la gestió de les entrades i sortides al programa principal, es clarifica la lògica global del programa i es manté la cohesió. Si les funcions secundàries gestionessin les entrades i sortides, es podria difuminar la línia entre les responsabilitats de cada component i es podria perdre l’objectiu de les funcions i la seva reusabilitat.
És molt important tenir molt clar que les funcions mai han de mostrar un resultat per pantalla. Han de fer els càlculs i retornar els valors al programa principal, que decidirà què fer-ne.
Per exemple, una funció no hauria d’utilitzar la funció exit() per finalitzar l’execució del programa. En general, qui s’ha d’encarregar de gestionar els casos en què cal finalitzar-lo (únicament en el control d’errors) ha de ser sempre el programa principal.
Funció que gestiona la sortida de dades
Fixeu-vos en aquesta funció i el seu test-driver:
def sensacio_termica_MOLT_MALAMENT(graus_c):
'''
Funció que rep una temperatura en graus Celsius i mostra la sensació
tèrmica
Entrada: int
Sortida: None
'''
if graus_c < 10:
print('fa fred')
elif graus_c < 25:
print('s\'està bé')
else:
print('fa calor')
sensacio_termica_MOLT_MALAMENT(0)
sensacio_termica_MOLT_MALAMENT(20)
sensacio_termica_MOLT_MALAMENT(30)
Si voleu fer un altre programa que segons la sensació tèrmica mostri un color (blau, blanc o vermell, per exemple), no podeu reutilitzar aquesta funció.
La primera llei que heu de seguir en definir funcions és que mai han de gestionar les entrades i sortides dels programes, sinó que han de fer el càlcul i retornar-lo al programa.
Funció amb diversos return
Aquesta segona versió de la funció és millor que l’anterior, ja que el programa rep la sensació, i podrà decidir què fer amb ella (si mostrar-la directament, mostrar un color o el que necessiti).
def sensacio_termica_MALAMENT(graus_c): ''' Funció que rep una temperatura en graus Celsius i retorna una sensació. Entrada: int Sortida: str ''' sensacio = 's\'està bé' if graus_c < 10: return 'fa fred' elif graus_c < 25: return sensacio else: return 'fa calor'
Acabar la funció amb un return
L’última línia d’una funció sempre hauria de ser un return indentat a la mateixa alçada que el docstring. D’aquesta manera uns assegureu que no us deixeu cap cas i és més fàcil de llegir.
Les funcions quan arriben al final del seu bloc i no han trobat un return, retornen el valor None. Un primer consell és escriure un return o return None (que fan el mateix) al final de totes les funcions, de manera que quedi clar que han acabat i es retorna el flux al programa que les ha cridat.
En la funció sensacio_termica_MALAMENT(), tot i que el flux sempre acaba sortint per un return de l’estructura condicional, l’última instrucció no és un return a la mateixa alçada que la definició de la funció. Tot i que el processador no té cap problema, al primer cop d’ull no es pot assegurar que haurà sortit per un return (ja que no n’hi ha cap identat al final de la funció
que ens indiqui que s’hagi de sortir per allí).
Funció amb un return al final
Aquí teniu una versió millorada de la funció:
def sensacio_termica_MENYS_MALAMENT(graus_c): ''' Funció que rep una temperatura en graus Celsius i retorna una sensació. Entrada: int Sortida: str ''' sensacio = 's\'està bé' if graus_c < 10: return 'fa fred' if graus_c < 25: return sensacio return 'fa calor'Fixeu-vos que si hi ha un
return en una branca d’una estructura repetitiva, no cal posar elif/else, ja que si hi entra el flux, no s’arribarà a l’ if següent. Això evita endinsar la indentació i permet que el codi s’entengui millor (és el mateix concepte que utilitzeu al codificar el control d’errors d’un programa).
Un únic punt de sortida
És una bona pràctica tenir un únic punt de sortida a les funcions sempre que sigui possible, ja que ajuda a mantenir el codi més estructurat i fàcil de seguir. Si és necessari, podeu utilitzar variables per emmagatzemar resultats temporals i retornar-les al final de la funció.
Un únic return en una estructura condicional
Si recolliu els valors en una variable i feu un únic return al final de la funció, aconseguiu tenir un únic punt de sortida, tal com aconsella la programació estructurada.
Aquesta és la versió correcta de la funció:
def sensacio_termica(graus_c): ''' Funció que rep una temperatura en graus Celsius i retorna una sensació. Entrada: int Sortida: str ''' sensacio = 's\'està bé' if graus_c < 10: sensacio = 'fa fred' elif graus_c >= 25: sensacio = 'fa calor' return sensacio
A més a més de no utilitzar els return en mig d’un condicional (que porta a diversos punts de sortida), també és recomanable no utilitzar-los per sortir d’un bucle.
Recordeu que la programació estructurada recomana un únic punt de sortida de les estructures de control. Si a més de sortir per la condició del while o després de l’últim element iterat, en el for es posa un return, hi ha com a mínim un parell de punts de sortida del bucle.
return en un bucle
Fixeu-vos en la següent funció:
def en_llista_MALAMENT(num): ''' Funció que retorna la posició en que un nombre es troba en la llista [3, 56, 12, -7], i -1 si no hi és. Entrada: int Sortida: int ''' LLISTA = [3, 56, 12, -7] for i in range(0, len(LLISTA)): if num == LLISTA[i]: return i return -1
Tenir les funcions amb un únic punt de sortida, és una recomanació, no una obligació. En el cas d’escriure diversos returns dins d’una estructura condicional, es diria que és obligatori recollir la dada i oferir un únic return. Però quan es tracta de sortir d’un bucle la recomanació no és tan “obligatòria”. De totes maneres, heu de saber que hi ha una manera millor de sortir d’un bucle que amb un return. Tot i que a vegades no s’utilitzi.
És una cerca seqüencial però que només serveix per a una llista concreta (el que fa que sigui una funció pràcticament inútil).
Si es troba l’element, es retorna, de manera que s’acaba el for tot i no haver iterat fins a l’últim element. Evidentment, això fa que sigui necessari un altre return (o punt de sortida) per si no es troba.
Bucle dins d'una funció amb un únic punt de sortida
Aquí hi ha una versió millorada de la mateixa funció (millorada perquè segueix els preceptes de la programació estructurada, però igual d’inútil que l’anterior).
def en_llista(num): ''' Funció que retorna la posició en que un nombre es troba en la llista [3, 56, 12, -7], i -1 si no hi és. Entrada: int Sortida: int ''' LLISTA = [3, 56, 12, -7] valor = -1 i = 0 while i < len(LLISTA) and num != LLISTA[i]: i += 1 if i < len(LLISTA): valor = i return valor
Funcions amb una sola responsabilitat
Cada funció hauria de tenir una única responsabilitat o realitzar una sola tasca. Això facilita la comprensió de la funció i promou la reutilització del codi.
Si una funció és massa gran o fa massa coses, considereu descompondre-la en funcions més petites i modulars.
A més,
una funció que fa massa coses
acaba ocasionant inconsistències en el codi.
Com a exemple del que no s’ha de fer, us podeu fixar en la funció que s’ha definit al principi, sensacio_termica_MOLT_MALAMENT(). És una funció que fa dues coses: calcular la sensació tèrmica i mostrar-la.
Quan en la descripció de la funció que feu en el docstring necessiteu la partícula i, cal que repenseu la seva funcionalitat perquè és molt possible que no tingui una única finalitat.
Funcions booleanes de validació
Farem una excepció en l’aplicació dels preceptes de la programació estructurada, i serà quan es programin funcions booleanes que tinguin com a objectiu validar o descartar dades que compleixin determinades condicions. En aquests casos s’utilitzarà la mateixa filosofia que vau estudiar en el control d’errors: quan una dada no és bona, s’ha de retirar de seguida perquè no ocasioni errors més endavant.
Recordeu que en el control d’errors es permet l’ús de la partícula exit per interrompre l’execució del programa (concepte totalment oposat a la programació estructurada). En aquestes funcions booleanes es permetrà utilitzar un return en les estructures condicionals perquè la funció acabi la seva feina i les dades dolentes no generin errors ni complicacions en el codi. Qui s’ha d’encarregar d’aturar l’execució del programa amb un exit, però, ha de ser el control d’errors del programa principal, mai la funció de verificació.
Funció booleana amb filosofia de control d'errors
Observeu el codi de la funció valida_dni():
def lletra_dni(dni):
'''
Funció que retorna la lletra del dni
Entrada: int positiu
Sortida: str (la lletra)
'''
LLETRES_DNI = 'TRWAGMYFPDXBNJZSQVHLCKE'
pos = dni % len(LLETRES_DNI)
return LLETRES_DNI[pos]
def valida_dni(cadena):
'''
Funció que diu si una cadena és un dni vàlid o no.
Entrada: str
Sortida: bool
'''
# té la longitud correcta
if len(cadena) != 9:
return False
# els 8 primers son dígits
if not cadena[:-1].isdigit():
return False
# El primer dígit no és el 0
if cadena[0] == '0':
return False
# l'últim és la lletra corresponent (en majúscules o minúscules)
lletra = lletra_dni(int(cadena[:-1]))
return cadena[-1] in [lletra, lletra.lower()]
Si no es descartés una dada amb longitud diferent de 9, per exemple, es generaria un error si l’argument fos la cadena buida i s’intentés accedir a la primera posició (cadena[0]). Per aplicar les idees de la programació estructurada caldria fer una verificació abans de la instrucció, el que aniria en contra de la llegibilitat del codi.
En les funcions booleanes d’aquest estil es considerarà que els beneficis obtinguts per descartar les dades de manera immediata són millors que seguir la programació estructurada. Tal com feu en el bloc del control d’errors d’un programa, que en el fons, també descarta les dades que poden provocar problemes.



