Amb la v0.21 el projecte està documentat, aguanta els errors, té historial i vuit proves que el verifiquen en centèsimes de segon. Falta l'últim, i és el que separa un programa que funciona d'un del qual un se sent orgullós: com està escrit. Perquè un programa s'escriu una vegada i es llegeix desenes: cada vegada que hi tornes, cada vegada que busques un error, cada vegada que algú nou entra al projecte. A TascaFàcil hi ha funcions a interficie.py que han crescut fins a les quaranta línies, el número 52 apareix solt en tres llocs, hi ha noms heretats de quan això era un exercici i blocs de codi que es repeteixen amb dues paraules canviades. Aquesta lliçó ho arregla tot: la guia d'estil oficial de Python, les eines que l'apliquen soles, l'art de posar bons noms, les olors de codi que delaten un problema i un catàleg de refactoritzacions que milloren el codi sense canviar el que fa —recolzant-te, precisament, en les proves que vas escriure a la lliçó anterior.

Contingut

  1. PEP 8: la guia d'estil de Python
  2. Convencions de noms
  3. El Zen de Python
  4. Eines: formatadors i linters
  5. Posar bons noms
  6. Olors de codi
  7. Què és refactoritzar (i què no)
  8. Catàleg de refactoritzacions
  9. TascaFàcil v1.0: revisat, formatat i refactoritzat
  10. Errors habituals i consells
  11. Exercicis
  12. Conclusió

  1. PEP 8: la guia d'estil de Python

PEP 8 és el document oficial que defineix com s'escriu Python. No canvia el comportament de res: defineix l'aspecte, i el seu valor rau en el fet que tothom escriu igual, així que qualsevol codi Python et resulta familiar des del primer minut. Aquestes són les seves regles pràctiques:

Regla Com es fa Exemple
Indentació 4 espais, mai tabuladors return total
Longitud de línia Màxim 79 caràcters (molts projectes fan servir 88) Parteix la línia abans de passar-te'n
Línies en blanc 2 entre funcions o classes de nivell superior, 1 entre mètodes Separa les idees
Espais als operadors Un a cada costat: a = b + c, no a=b+c total = dies * tarifa
Espais als arguments Sense espai al voltant de l'= d'un valor per defecte def f(dies=1):
Comes Espai al darrere, mai al davant f(a, b), no f(a , b)
Imports Un per línia i a dalt del fitxer import json
Ordre dels imports Estàndard → tercers → propis, separats per línia en blanc Vegeu l'exemple
# 1. Biblioteca estandard
import json
import logging
from pathlib import Path

# 2. Paquets de tercers (aqui no n hi ha: TascaFacil no en fa servir cap)
import pytest

# 3. Moduls propis del projecte
from .model import Tasca, PRIORITATS
from .agenda import Agenda

Aquest ordre no és un caprici: en mirar la capçalera d'un fitxer saps d'un cop d'ull què depèn de fora i què és del projecte. I una nota important sobre la longitud de línia: el límit existeix per poder llegir dos fitxers en paral·lel i perquè el diff de Git sigui llegible, no per nostàlgia de les pantalles antigues.

  1. Convencions de noms

El curs fa servir aquestes convencions des de Variables i tipus de dades; ara els posem el seu nom oficial:

Estil Es fa servir per a Exemples del projecte
snake_case Variables, funcions i mètodes dies_restants, resum_per_responsable
PascalCase Classes Tasca, TascaRecurrent, Agenda
MAJUSCULES Constants PRIORITATS, EQUIP, AMPLE
_privat Ús intern; «no toquis això des de fora» _tasques, _normalitzar
modul.py Mòduls i paquets: curts i en minúscula model.py, magatzem.py

El guió baix inicial mereix un aclariment, perquè a Python no impedeix res: agenda._tasques és perfectament accessible. És un acord entre programadors, no una barrera tècnica: significa «això és un detall intern, pot canviar demà i no hi hauries de dependre». Aquest acord és justament el que va permetre a 07-03 canviar per dins la llista d'Agenda sense trencar res a ningú.

  1. El Zen de Python

Escriu import this a l'intèrpret i apareixeran dinou aforismes que resumeixen la filosofia del llenguatge. Aquests cinc són els que més et serviran:

  • «Explicit is better than implicit» (explícit millor que implícit). Per això import format guanya a from format import *, i per això una funció que retorna Tasca | None ho diu a la seva signatura en lloc de deixar que ho descobreixis en fallar.
  • «Simple is better than complex» (simple millor que complex). Una cerca lineal en una llista de 200 tasques és la solució correcta; l'índex i l'arbre binari són una complexitat que ningú no ha demanat.
  • «Readability counts» (la llegibilitat compta). És la raó de ser d'aquesta lliçó sencera: entre dues solucions igual de vàlides, guanya la que es llegeix millor.
  • «Errors should never pass silently» (els errors mai no haurien de passar en silenci). Exactament el que vam dir a 08-02 sobre except: pass.
  • «There should be one obvious way to do it» (hi hauria d'haver una manera òbvia de fer-ho). Per això el projecte sencer fa servir un sol estil de docstring i una sola manera de validar.

No són regles obligatòries sinó criteris per desempatar. Quan dubtis entre dues maneres d'escriure alguna cosa, pregunta't quina de les dues és més explícita, més simple i més llegible; gairebé sempre és la mateixa.

  1. Eines: formatadors i linters

El millor de tot l'anterior és que gairebé res d'això no s'ha de fer a mà. Hi ha dues famílies d'eines: els formatadors, que reescriuen el codi perquè compleixi l'estil, i els linters, que l'analitzen i avisen de problemes.

Eina Tipus Què fa
black Formatador Reformata el fitxer sencer. No es configura: no hi ha discussió possible
ruff Linter (i formatador) Detecta problemes d'estil i de codi. Extremadament ràpid
flake8 Linter El clàssic: comprova PEP 8 i errors freqüents
pylint Linter El més exhaustiu i el més primmirat; puntua el teu codi
mypy Comprovador de tipus Verifica les anotacions de 08-01 sense executar el programa
pip install black ruff mypy
black tascafacil/            # reformata tot el paquet
black --check tascafacil/    # nomes diu si faria canvis, sense tocar res
ruff check tascafacil/       # llista els problemes trobats
ruff check --fix tascafacil/ # arregla els que pot arreglar sol
mypy tascafacil/             # comprova la coherencia dels tipus

La diferència entre els dos tipus és important: black no opina sobre el teu codi, només sobre el seu aspecte; et treu per sempre les discussions sobre cometes, espais i salts de línia. ruff sí que opina: et dirà que has importat alguna cosa que no fas servir, que una variable està definida i mai no es llegeix o que una funció és massa complexa. A VS Code s'integren en dos clics: instal·la l'extensió de Python, tria black com a formatador i activa «Format on Save»; a partir d'aquí l'estil deixa de ser una preocupació teva.

  1. Posar bons noms

És l'única cosa d'aquesta lliçó que cap eina no pot fer per tu, i probablement el que més influeix en la llegibilitat. Un bon nom diu què és o què fa, sense abreviatures críptiques ni informació redundant:

Abans Després Per què
d, x, tmp dies, tasca, pendents Una lletra només val en bucles molt curts
llista_de_tasques_de_l_equip tasques Si el context ja ho diu, sobra
processar(dades) normalitzar_prioritat(text) «Processar» no significa res
flag esta_completada Els booleans es llegeixen com una pregunta
get_t() cercar(titol) El nom ha de dir què retorna
calcular() calcular_dies_restants() Calcular què?

Tres criteris que ho resumeixen tot: els booleans comencen per un verb d'estat (esta_, hi_ha_, te_), les funcions comencen per un verb que diu què fan (cercar, desar, normalitzar) i les col·leccions van en plural (tasques, responsables). I si un nom necessita un comentari al costat per entendre's, el nom està malament.

  1. Olors de codi

Una olor de codi (code smell) no és un error: el programa funciona. És un senyal que alguna cosa donarà problemes aviat. Aquestes són les set que trobaràs una vegada i una altra:

Olor Com es reconeix Solució
Funció massa llarga No cap a la pantalla; fa diverses coses Extreure funció
Massa paràmetres Cinc o més; ningú no recorda l'ordre Agrupar en un objecte o fer servir dataclass
Codi duplicat El mateix bloc amb dues paraules canviades Extreure una funció amb paràmetres
Números i cadenes màgics 52, 0.21, "alta" solts per allà Substituir per constant amb nom
Imbricació profunda Tres o més if ficats els uns dins dels altres Clàusula de guarda
Noms críptics d, tmp2, processar Reanomenar
Comentari explicatiu Un comentari que aclareix un tram confús Extreure aquest tram a una funció ben anomenada

L'últim és el més subtil i el més útil d'aprendre. Un comentari del tipus # aqui calculem el recarrec i apliquem el descompte està assenyalant, sense voler, que aquell bloc és una funció que encara no té nom. Extreu-lo i anomena'l aplicar_recarrec_i_descompte(): el comentari desapareix perquè ja no cal, i de passada el tros esdevé provable de manera aïllada.

  1. Què és refactoritzar (i què no)

Refactoritzar és canviar l'estructura interna del codi sense canviar en absolut el que fa. Aquesta segona part és la definició completa, i d'ella en surten dues conseqüències que convé tenir molt clares:

  • No és refactoritzar afegir una funcionalitat, arreglar un error ni millorar el rendiment. Tot això canvia el comportament; refactoritzar, no.
  • Mai no es fan les dues coses alhora. Si refactoritzes i afegeixes una funció al mateix commit i alguna cosa es trenca, no sabràs què ho ha trencat. Primer una cosa, després l'altra, cadascuna amb el seu commit (08-03).

I d'aquí en surt la regla d'or: abans de refactoritzar cal tenir proves. Les proves de 08-04 són les que responen a l'única pregunta que importa mentre reorganitzes el codi —«continua fent exactament el mateix?»— i les que converteixen l'operació en una cosa segura. El cicle és sempre aquest: proves en verd, un canvi petit, executar les proves, commit; i tornar a començar. Si en algun moment es posen en vermell, saps amb total certesa què ho ha provocat, perquè acabes de fer un sol canvi.

  1. Catàleg de refactoritzacions

Aquestes sis resolen la immensa majoria dels casos. Extreure funció parteix un bloc llarg en peces amb nom:

# ABANS: un bloc que fa tres coses
def mostrar_fitxa(tasca):
    print("=" * 52)
    print(f"{tasca.titol.upper():^52}")
    print("=" * 52)
    barra = "#" * int(tasca.progres() / 10) + "-" * (10 - int(tasca.progres() / 10))
    print(f"Avanc: [{barra}] {tasca.progres():.0f}%")

# DESPRES: cada idea, amb el seu nom
def capcalera(text: str) -> str:
    """Retorna el text centrat entre linies de separacio."""
    return f"{'=' * AMPLE}\n{text.upper():^{AMPLE}}\n{'=' * AMPLE}"

def barra_progres(percentatge: float) -> str:
    """Retorna una barra de 10 caselles per a aquest percentatge."""
    plenes = int(percentatge / 10)
    return "#" * plenes + "-" * (10 - plenes)

Extreure variable explicativa converteix una expressió il·legible en una cosa que s'entén sola, i substituir el número màgic per una constant fa el mateix amb els valors solts:

# ABANS
if tasca.dies > 20 and tasca.prioritat == "alta" and not tasca.completada:
    total *= 0.9

# DESPRES
DIES_PROJECTE_LLARG = 20
DESCOMPTE_LLARG = 0.9

es_projecte_llarg_urgent = (tasca.dies > DIES_PROJECTE_LLARG
                            and tasca.prioritat == "alta"
                            and not tasca.completada)
if es_projecte_llarg_urgent:
    total *= DESCOMPTE_LLARG

Invertir la condició i fer servir una clàusula de guarda aplana la imbricació profunda, que és l'olor que més costa de llegir:

# ABANS: tres nivells d imbricacio
def desar(agenda, ruta):
    if agenda is not None:
        if len(agenda) > 0:
            if ruta.parent.exists():
                escriure_json(agenda, ruta)

# DESPRES: els casos impossibles es descarten primer i se surt
def desar(agenda, ruta):
    if agenda is None or len(agenda) == 0:
        return
    if not ruta.parent.exists():
        raise FileNotFoundError(f"No existeix la carpeta {ruta.parent}")
    escriure_json(agenda, ruta)

Unificar duplicats i substituir una cadena d'if per un diccionari de despatx tanquen el catàleg. La segona és especialment rendible als menús:

# ABANS: una cadena que creix amb cada opcio nova
if opcio == "1":
    crear_tasca(agenda)
elif opcio == "2":
    llistar_tasques(agenda)
elif opcio == "3":
    completar_tasca(agenda)

# DESPRES: un diccionari de despatx (funcions com a valors, 04-05)
ACCIONS = {"1": crear_tasca, "2": llistar_tasques, "3": completar_tasca}

accio = ACCIONS.get(opcio)
if accio is not None:
    accio(agenda)

Fixa't en el que ha guanyat la segona versió: afegir una opció nova és una línia al diccionari en lloc de dues a la cadena, el conjunt d'opcions vàlides és en un únic lloc i ACCIONS.keys() serveix directament per validar l'entrada de l'usuari. És la mateixa idea de les funcions com a valors de 04-05, aplicada al problema real.

  1. TascaFàcil v1.0: revisat, formatat i refactoritzat

Primer, les eines, amb les proves en verd abans de començar:

$ pytest -q
8 passed in 0.05s

$ black tascafacil/ tests/
reformatted tascafacil/interficie.py
reformatted tascafacil/agenda.py
All done! 2 files reformatted, 4 files left unchanged.

$ ruff check tascafacil/
tascafacil/interficie.py:12:1: F401 [*] `csv` imported but unused
tascafacil/interficie.py:48:5: C901 `gestionar_opcio` is too complex (14)
tascafacil/magatzem.py:7:1: E501 Line too long (92 > 88)
Found 3 errors (1 fixable with `--fix`).

ruff ha assenyalat exactament les dues funcions que sabíem que estaven malament: gestionar_opcio, amb la seva cadena de catorze elif, i mostrar_fitxa, amb el bloc de format. La primera s'arregla amb el diccionari de despatx i la segona extraient capcalera() i barra_progres(), tal com acabes de veure. Després de cada canvi, la comprovació que ho justifica tot:

$ pytest -q
8 passed in 0.05s

$ ruff check tascafacil/
All checks passed!

$ git commit -am "Refactoritzar interficie.py: despatx per diccionari i extraccio"
$ git tag -a v1.0 -m "TascaFacil 1.0: documentat, robust, versionat i provat"

Les vuit proves continuen en verd sense haver-ne tocat ni una, i això és la demostració que la refactorització va ser una refactorització de debò: el comportament és idèntic. El projecte arriba així a la v1.0, el número que segons el versionatge semàntic de 08-01 significa «això ja és estable». Mira d'on ve:

Versió Què era TascaFàcil
v0.1 (mòd. 2) Quatre variables i un print amb f-strings
v0.4 (mòd. 3) Un menú en un bucle while amb condicionals
v0.8 (mòd. 4) Funcions, un main() i la lògica separada de la interfície
v0.11 (mòd. 5) Llistes i diccionaris, amb desat en JSON i CSV
v0.13 (mòd. 6) Cerques, ordenacions i consciència del cost
v0.17 (mòd. 7) Classes Tasca i Agenda al paquet tascafacil/
v0.18 Documentat: docstrings, anotacions de tipus i README.md
v0.19 Robust: try/except, TascaInvalida i logging
v0.20 Versionat: repositori Git amb historial i .gitignore
v0.21 Provat: carpeta tests/ amb vuit proves en verd
v1.0 Formatat amb black, revisat amb ruff i refactoritzat

Errors Habituals i Consells

  • Discutir sobre estil. És temps perdut: instal·la black, activa'l en desar i dedica aquesta energia al que sí que importa.
  • Refactoritzar sense proves. És reescriure a cegues. Primer les proves en verd, després el canvi.
  • Refactoritzar i canviar el comportament al mateix commit. Si alguna cosa es trenca, no sabràs què ha estat. Un commit, una intenció.
  • La refactorització gegant. Canvis petits amb les proves executades entre l'un i l'altre; mai una reescriptura de tres dies.
  • Confondre «curt» amb «llegible». Una línia de codi que s'ha de llegir tres vegades no és millor que tres línies clares.
  • Fer cas cec al linter. Avisa, no mana. Si tens una raó per deixar alguna cosa com està, deixa-la (i anota el perquè en un comentari).
  • Consell: aplica la regla del campament. Deixa cada fitxer que toques una mica millor que com el vas trobar: un nom, una constant, una funció extreta. En uns mesos el projecte sencer està net sense haver-te aturat mai a netejar-lo.

Exercicis

Exercici 1: Estil i noms

Reescriu aquest fragment aplicant PEP 8, les convencions de noms i les constants que calguin:

import json,csv
def PROC(l,f):
  r=[]
  for x in l:
    if x['p']=='alta' and x['d']>20:r.append(x)
  return r

Exercici 2: Detectar olors

Enumera les olors de codi d'aquesta funció, digues quina refactorització correspon a cadascuna i reescriu-la:

def informe(tasques, tipus):
    if tipus == "curt":
        for t in tasques:
            print(f"{t.titol[:30]:<30} {t.responsable:<10}")
    elif tipus == "llarg":
        for t in tasques:
            print(f"{t.titol[:30]:<30} {t.responsable:<10} {t.dies:>3} dies")
    elif tipus == "csv":
        for t in tasques:
            print(f"{t.titol},{t.responsable},{t.dies}")

Exercici 3: Clàusula de guarda i despatx

Refactoritza aquesta funció fent servir una clàusula de guarda i un diccionari de despatx, i explica per què el resultat és més fàcil d'ampliar:

def aplicar_accio(tasca, accio):
    if tasca is not None:
        if not tasca.completada:
            if accio == "completar":
                tasca.completar()
            elif accio == "posposar":
                tasca.dies += 1
            elif accio == "urgent":
                tasca.prioritat = "alta"

Solucions

Solució 1.

import csv
import json

DIES_PROJECTE_LLARG = 20
PRIORITAT_URGENT = "alta"


def filtrar_urgents_llargues(tasques: list[dict]) -> list[dict]:
    """Retorna les tasques de prioritat alta que superen els 20 dies."""
    return [
        tasca
        for tasca in tasques
        if tasca["prioritat"] == PRIORITAT_URGENT
        and tasca["dies"] > DIES_PROJECTE_LLARG
    ]

Els canvis són set: indentació de 4 espais, un import per línia i en ordre alfabètic, nom de funció en snake_case i descriptiu (PROC no diu res i a més semblava una classe), variables amb nom complet, les claus 'p' i 'd' llegibles, els números màgics convertits en constants i l'if d'una línia desplegat. Ah, i el paràmetre f no es feia servir: ruff ho hauria detectat a l'acte.

Solució 2.

Hi ha tres olors: codi duplicat (el bucle es repeteix tres vegades idèntic), una cadena d'if sobre un valor (candidata a diccionari de despatx) i cadenes màgiques ("curt", "llarg", "csv" soltes). La refactorització que correspon és extreure el format d'una línia a funcions i despatxar per diccionari:

FORMATS = {
    "curt": lambda t: f"{t.titol[:30]:<30} {t.responsable:<10}",
    "llarg": lambda t: f"{t.titol[:30]:<30} {t.responsable:<10} {t.dies:>3} dies",
    "csv": lambda t: f"{t.titol},{t.responsable},{t.dies}",
}

def informe(tasques: list[Tasca], tipus: str = "curt") -> None:
    """Imprimeix l informe de tasques en el format indicat."""
    formatar = FORMATS.get(tipus)
    if formatar is None:
        raise ValueError(f"Format no valid: {tipus!r}. Fes servir {list(FORMATS)}.")
    for tasca in tasques:
        print(formatar(tasca))

El bucle apareix una sola vegada i l'única cosa que varia —el format de la línia— viu al diccionari. Afegir un format nou és ara una línia, els formats vàlids són en un únic lloc i el missatge d'error els enumera sol. És exactament la idea de les funcions com a valors de 04-05 posada a treballar.

Solució 3.

def aplicar_accio(tasca: Tasca | None, accio: str) -> None:
    """Aplica una accio a una tasca pendent."""
    if tasca is None or tasca.completada:      # clausula de guarda
        return
    ACCIONS[accio](tasca)

ACCIONS = {
    "completar": lambda t: t.completar(),
    "posposar": lambda t: setattr(t, "dies", t.dies + 1),
    "urgent": lambda t: setattr(t, "prioritat", "alta"),
}

La clàusula de guarda descarta al principi els casos en què no hi ha res a fer, i el cos real de la funció queda sense imbricació: es llegeix de dalt a baix sense sostenir condicions al cap. I ampliar és trivial: una acció nova és una entrada a ACCIONS, sense tocar la funció. En un projecte real convé anar un pas més enllà i substituir aquestes lambda amb setattr per mètodes de Tasca (posposar(), marcar_urgent()), perquè el comportament d'una tasca pertany a la classe Tasca: és la lliçó de 07-02 aplicada aquí.

Conclusió

El codi es llegeix moltes més vegades de les que s'escriu, i per això l'estil no és cosmètica. PEP 8 fixa el bàsic —4 espais d'indentació, línies de menys de 79 (o 88) caràcters, línies en blanc que separen idees, espais al voltant dels operadors i imports ordenats en estàndard, tercers i propis— i les convencions de noms posen nom oficial al que el curs venia fent des de 02-01: snake_case per a variables i funcions, PascalCase per a classes, MAJUSCULES per a constants i _privat com a acord entre programadors que Python no imposa. El Zen de Python (import this) aporta els criteris per desempatar: explícit millor que implícit, simple millor que complex, la llegibilitat compta, els errors mai no haurien de passar en silenci i hi hauria d'haver una manera òbvia de fer les coses. Gairebé tot això ho apliquen soles les eines: black reformata sense discussió, ruff, flake8 i pylint detecten problemes, mypy comprova les anotacions de 08-01, i VS Code ho executa en desar. L'única cosa que no es pot automatitzar són els noms: verbs per a les funcions, plural per a les col·leccions, esta_/hi_ha_ per als booleans i mai una abreviatura que necessiti un comentari. Les olors de codi —funció massa llarga, massa paràmetres, duplicació, números i cadenes màgics, imbricació profunda, noms críptics i el comentari que explica un tram confús— assenyalen on actuar. I refactoritzar és millorar l'estructura sense canviar el comportament, mai barrejat amb una altra cosa al mateix commit i sempre amb proves en verd abans de començar: extreure funció, extreure variable explicativa, substituir número màgic per constant, invertir la condició amb una clàusula de guarda, unificar duplicats i substituir la cadena d'if per un diccionari de despatx.

TascaFàcil és la v1.0: formatat amb black, revisat amb ruff, amb interficie.py refactoritzada i amb les mateixes vuit proves en verd, que és la prova que res no va canviar per fora. I amb això es tanca el mòdul 8 i, de fet, tot el recorregut tècnic del curs. Vas començar sense saber què era una variable i ara tens el llenguatge (tipus, operadors, entrada i sortida), les estructures de control, les funcions, les estructures de dades amb la seva persistència en fitxers, els algorismes de cerca i ordenació amb el seu cost, la programació orientada a objectes amb mòduls i paquets, i les cinc eines professionals d'aquest mòdul: documentació, depuració i gestió d'errors, control de versions, proves automatitzades i refactorització. Això és exactament l'equipatge amb què treballa un programador.

Només queda una cosa per fer, i és la més important de totes: construir alguna cosa sencera des de zero, tu sol. Fins ara cada peça de TascaFàcil venia proposada; al mòdul 9 el projecte el tries tu, el dissenyes, el planifiques, l'implementes, el proves i el presentes. Comencem a Definició del projecte, decidint què val la pena construir i —tan important com això— on posar el límit per acabar-lo.

© Copyright 2026. Tots els drets reservats