Les quatre lliçons anteriors d'aquest mòdul van donar a BiblioTech un estil consistent, un
vocabulari de patrons de disseny, dependències desacoblades mitjançant interfícies, i una
bateria de proves unitàries que verifiquen el seu comportament sense necessitar una base de dades
real. Aquesta última lliçó del Mòdul 8 tanca el cercle: ensenya què busca una bona revisió de
codi, cataloga els code smells (senyals d'un disseny que pot millorar) més comuns, i
refactoritza un mètode de Biblioteca que barreja diverses responsabilitats —recolzant-se,
precisament, en les proves unitàries de la lliçó anterior com a xarxa de seguretat—. Amb això,
BiblioTech arriba al Mòdul 9 (Projecte Final) amb un codi no només funcional, sinó revisat,
provat i depurat segons tot el que s'ha vist en aquest mòdul.
Contingut
- Què busca una bona revisió de codi
- Checklist de revisió
- Code smells comuns
- Refactorització: extreure mètode i extreure classe
- Les proves unitàries com a xarxa de seguretat abans de refactoritzar
- Exemple: refactoritzant un mètode llarg de
Biblioteca - Tancament del Mòdul 8 i enllaç amb el Projecte Final
- Què busca una bona revisió de codi
Una revisió de codi (code review) és el procés pel qual una altra persona (o, en un
projecte individual, un mateix amb perspectiva fresca) llegeix un canvi abans d'incorporar-lo
definitivament al projecte, buscant problemes que l'autor original, immers en el detall, pot no
haver vist. Una bona revisió no busca imposar gustos personals d'estil —això ja ho automatitza
EditorConfig (Lliçó 1)—, sinó verificar quatre aspectes concrets:
| Aspecte | Pregunta que es fa el revisor |
|---|---|
| Llegibilitat | S'entén què fa aquest codi sense haver-lo d'executar mentalment pas a pas? |
| Proves | Existeixen proves unitàries (Lliçó 4) que cobreixin el comportament nou o modificat? |
| Adherència als estàndards | Segueix les convencions de nomenclatura, documentació i nul·labilitat ja establertes (Lliçó 1)? |
| Disseny | Alguna classe o mètode ha crescut fins a barrejar responsabilitats que s'haurien de separar (Lliçó 2, responsabilitat única)? |
Una revisió centrada en aquests quatre aspectes, i no en preferències personals d'estil, és més ràpida, més objectiva, i genera menys fricció entre qui escriu el codi i qui el revisa.
- Checklist de revisió
Una checklist concreta ajuda al fet que una revisió no depengui només de la intuïció del revisor. Una checklist raonable per a un canvi a BiblioTech:
- [ ] El nom de cada mètode i variable nous segueix les convencions de la Lliçó 1
(
PascalCase/camelCase, prefixosI/_, sufixAsync)? - [ ] Hi ha algun mètode que faci més d'una cosa clarament separable (validar, persistir, notificar...)?
- [ ] Les dependències externes (persistència, serveis) es reben injectades, o es creen
directament amb
newdins de la classe (Lliçó 3)? - [ ] El canvi inclou proves unitàries noves, o modifica proves existents que ja no reflecteixen el comportament nou (Lliçó 4)?
- [ ] La nul·labilitat (
?) reflecteix fidelment què pot retornarnulli què no? - [ ] Els comentaris expliquen decisions no evidents, o només repeteixen el que el codi ja diu (Lliçó 1)?
Aquesta llista no és exhaustiva ni universal —cada equip ajusta la seva amb el temps—, però serveix com a punt de partida concret, més útil que una revisió sense cap criteri explícit.
- Code smells comuns
Un code smell (literalment, "olor de codi") és una senyal superficial en el codi que suggereix, sense ser en si mateixa un error, que el disseny subjacent es podria millorar. Quatre code smells especialment comuns:
| Code smell | Com es reconeix | Per què és un problema |
|---|---|---|
| Mètode llarg | Un mètode de desenes de línies, amb diversos blocs clarament separables | Difícil de llegir d'un sol cop, difícil de provar de forma aïllada (cada bloc necessitaria la seva pròpia prova) |
| Duplicació | El mateix fragment de lògica, copiat (potser amb petites variacions) en diversos llocs | Un canvi d'aquesta lògica obliga a recordar actualitzar cada còpia; és fàcil oblidar-ne alguna |
| Classe amb massa responsabilitats | Una classe que barreja, per exemple, lògica de domini, persistència i presentació alhora | Viola el principi de responsabilitat única (Lliçó 1); canviar una responsabilitat arrisca trencar les altres sense relació |
| Paràmetres excessius | Un mètode amb sis o més paràmetres, diversos d'ells relacionats entre si | Suggereix que aquests paràmetres s'haurien d'agrupar en un objecte propi (per exemple, un record, Mòdul 3) |
Cap d'aquests smells és, per si sol, un error que impedeixi compilar o executar el programa —d'aquí que s'anomenin "olors" i no "errors"—; són senyals que convé investigar, no regles absolutes que obliguin sempre a refactoritzar de seguida.
- Refactorització: extreure mètode i extreure classe
Refactoritzar significa canviar l'estructura interna del codi sense canviar el seu comportament observable: el programa continua fent exactament el mateix des de fora, però el seu codi intern queda millor organitzat. Dues tècniques de refactorització cobreixen la majoria dels smells de l'apartat anterior:
- Extreure mètode: agafar un fragment d'un mètode llarg i convertir-lo en un mètode propi, amb un nom que expliqui què fa aquest fragment. Resol directament el smell de "mètode llarg".
- Extreure classe: quan una classe barreja diverses responsabilitats (el smell de "massa
responsabilitats"), moure part dels seus membres a una classe nova dedicada a aquesta
responsabilitat —exactament el que ja va fer la Lliçó 3 en extreure
IRepositoriBibliotecai les seves implementacions fora deBiblioteca.
// Abans d'"extreure metode": un fragment de logica de format barrejat amb una altra logica
Console.WriteLine($"Prestec #{prestec.Llibre.Titol}: prestat el {prestec.DataPrestec:dd/MM/yyyy}" +
(prestec.DataDevolucio is not null ? $", retornat el {prestec.DataDevolucio:dd/MM/yyyy}" : ", en curs"));
// Despres d'"extreure metode": el fragment te ara un nom propi
string DescriurePrestec(Prestec prestec)
{
string estat = prestec.DataDevolucio is not null
? $"retornat el {prestec.DataDevolucio:dd/MM/yyyy}"
: "en curs";
return $"Prestec #{prestec.Llibre.Titol}: prestat el {prestec.DataPrestec:dd/MM/yyyy}, {estat}";
}
Console.WriteLine(DescriurePrestec(prestec));DescriurePrestec no canvia el missatge mostrat per consola —el comportament observable és
idèntic—, però ara té un nom que explica el seu propòsit, i pot reutilitzar-se a qualsevol altre
punt del programa que necessiti el mateix format, sense duplicar la lògica.
- Les proves unitàries com a xarxa de seguretat abans de refactoritzar
La pregunta que hauria de sorgir sempre abans de refactoritzar és: com sé que no he trencat
res? Sense proves, l'única resposta és "executant l'aplicació sencera a mà i confiant a no
haver passat res per alt" —lent, i poc fiable. Amb les proves unitàries de la Lliçó 4 ja
escrites sobre Prestec.RegistrarDevolucio() i Biblioteca.PrestarLlibreAsync, refactoritzar
deixa de ser un salt de fe:
flowchart LR
A["Proves existents en verd"] --> B["Refactoritzar el codi intern"]
B --> C{"Les proves segueixen en verd?"}
C -->|"Si"| D["El comportament no ha canviat: refactoritzacio segura"]
C -->|"No"| E["Alguna cosa ha canviat sense voler: revisar abans de continuar"]
Aquest és el paper exacte de les proves unitàries com a xarxa de seguretat: no impedeixen cometre un error en refactoritzar, però el detecten de seguida —en segons, en tornar a executar el mateix conjunt de proves— en comptes de descobrir-ho molt més tard, potser ja en producció. Refactoritzar codi sense cap prova que ho recolzi no és impossible, però és força més arriscat: cada canvi depèn únicament de l'atenció del programador en aquell moment.
- Exemple: refactoritzant un mètode llarg de
Biblioteca
BibliotecaImagina que, amb les presses d'anar afegint funcionalitat mòdul a mòdul, Biblioteca va acabar
amb un mètode que barreja validació, registre del préstec i notificació, tot junt:
// Abans: un metode llarg que valida, registra i notifica, tot barrejat
public async Task GestionarPrestecAsync(Llibre llibre, Soci soci)
{
// Validacio
if (llibre is null)
{
throw new ArgumentNullException(nameof(llibre));
}
if (soci is null)
{
throw new ArgumentNullException(nameof(soci));
}
if (!llibre.Disponible)
{
throw new InvalidOperationException($"'{llibre.Titol}' no esta disponible per a prestec.");
}
// Espera simulada
await Task.Delay(1000);
// Registre
llibre.Prestar();
Prestec prestec = new Prestec(llibre, soci);
Prestecs.Add(prestec);
PrestecRegistrat?.Invoke(prestec);
// Persistencia
_repositori.GuardarCataleg(Cataleg);
// Notificacio per consola
Console.WriteLine($"'{llibre.Titol}' prestat correctament a {soci.Nom}.");
}Aquest mètode funciona, i les proves de la lliçó anterior probablement ja el cobririen amb algun ajust menor —però barreja, en un únic bloc de codi, quatre responsabilitats diferents (validar, esperar, registrar+persistir, mostrar un missatge), cosa que el fa difícil de llegir d'un cop d'ull i difícil de provar de forma aïllada. Aplicant "extreure mètode" sobre cada bloc:
// Despres: cada responsabilitat te el seu propi metode, amb un nom que l'explica
public async Task GestionarPrestecAsync(Llibre llibre, Soci soci)
{
ValidarPrestec(llibre, soci);
await Task.Delay(1000); // simulacio d'una verificacio lenta, Modul 4
Prestec prestec = RegistrarIPersistirPrestec(llibre, soci);
Console.WriteLine($"'{llibre.Titol}' prestat correctament a {soci.Nom}.");
}
private void ValidarPrestec(Llibre llibre, Soci soci)
{
ArgumentNullException.ThrowIfNull(llibre);
ArgumentNullException.ThrowIfNull(soci);
if (!llibre.Disponible)
{
throw new InvalidOperationException($"'{llibre.Titol}' no esta disponible per a prestec.");
}
}
private Prestec RegistrarIPersistirPrestec(Llibre llibre, Soci soci)
{
llibre.Prestar();
Prestec prestec = new Prestec(llibre, soci);
Prestecs.Add(prestec);
PrestecRegistrat?.Invoke(prestec);
_repositori.GuardarCataleg(Cataleg);
return prestec;
}El comportament observable no ha canviat en absolut: les mateixes excepcions es llancen en
els mateixos casos, el mateix missatge es mostra per consola, el mateix esdeveniment es dispara i
el mateix repositori s'invoca. El que ha canviat és que ara GestionarPrestecAsync es llegeix
gairebé com una llista de passos amb nom propi (ValidarPrestec, RegistrarIPersistirPrestec),
i cadascun d'aquests passos es podria provar per separat si calgués més granularitat en el
futur. En tornar a executar les proves de la Lliçó 4
(PrestarLlibreAsync_AmbLlibreDisponible_GuardaElCataleg i
PrestarLlibreAsync_AmbLlibreNoDisponible_LlancaExcepcioINoGuarda, adaptades al nou nom del
mètode) sobre aquesta versió refactoritzada, haurien de continuar passant exactament igual que
abans: aquesta és la confirmació concreta que la refactorització va ser segura.
- Tancament del Mòdul 8 i enllaç amb el Projecte Final
Amb aquesta lliçó es tanca el Mòdul 8 (Millors Pràctiques i Patrons de Disseny). Al llarg de les seves cinc lliçons, BiblioTech no ha guanyat ni una sola funcionalitat de domini nova —l'objectiu explícit d'aquest mòdul era un altre: fer un pas enrere i consolidar tot el que s'ha construït en els mòduls anteriors. El recorregut complet:
| Lliçó | Què va aportar |
|---|---|
| 1. Estàndards de codificació | Nomenclatura consistent, EditorConfig, responsabilitat única, comentaris útils, documentació XML, nul·labilitat consistent |
| 2. Patrons de disseny | Vocabulari comú (Singleton, Factory Method, Adapter, Decorator, Strategy, Observer) i reconeixement que PrestecRegistrat ja era un Observer |
| 3. Injecció de dependències | IRepositoriBiblioteca desacoblant Biblioteca de la persistència concreta, i el contenidor de DI d'ASP.NET Core en profunditat |
| 4. Proves unitàries | xUnit, Arrange-Act-Assert, i mocks de Moq substituint IRepositoriBiblioteca a les proves |
| 5. Revisió i refactorització | Checklist de revisió, code smells, i refactorització d'un mètode llarg recolzada en les proves ja existents |
El Mòdul 9 (Projecte Final) retoma ara tot el que s'ha construït en el curs —el domini complet des del Mòdul 2, la persistència del Mòdul 5, les cinc interfícies del Mòdul 7, i les pràctiques d'aquest Mòdul 8— per construir la versió final i completa de BiblioTech: se'n definiran els requisits amb precisió, se'n planificarà la implementació, es construirà seguint els estàndards i patrons ja apresos, es provarà amb la mateixa disciplina de proves unitàries vista aquí, i finalment es desplegarà. Res del que s'ha après en aquest mòdul queda de banda: és, precisament, el conjunt de pràctiques amb què es construirà aquesta versió final.
Errors Comuns i Consells
- Refactoritzar sense cap prova que recolzi el canvi: sense proves prèvies, no hi ha manera objectiva de confirmar que el comportament no ha canviat; en aquest cas, escriu primer almenys les proves més importants sobre el comportament actual, i refactoritza després.
- Canviar comportament "de passada" mentre es refactoritza: refactoritzar i corregir un error real són dues activitats diferents; barrejar-les en el mateix canvi dificulta saber, si alguna cosa falla després, si va ser la refactorització o la correcció la causa.
- Revisar codi fixant-se només en l'estil: una revisió centrada únicament en espais, noms o
format (automatitzable amb
EditorConfig, Lliçó 1) desaprofita l'oportunitat de detectar problemes de disseny, absència de proves, o riscos reals. - Extreure mètodes fins a l'extrem: dividir un mètode en fragments tan petits que cal saltar entre deu mètodes diferents per entendre un flux senzill també dificulta la lectura; l'objectiu és claredat, no fragmentació per si mateixa.
- Consell: si dubtes si un mètode necessita refactoritzar-se, pregunta't si el podries explicar en una frase curta; si la resposta requereix un "i també..." diverses vegades, probablement barreja més d'una responsabilitat.
Exercicis
-
Identifica, en el mètode
GestionarPrestecAsync"abans" de l'apartat 6, quin code smell de l'apartat 3 descriu millor el seu problema principal, i explica en una frase per què. -
El següent mètode d'un
Socibarreja registrar una sanció amb mostrar un missatge per consola. Refactoritza'l amb "extreure mètode", separant les dues responsabilitats:public void AplicarSancio(decimal import) { SaldoPendent += import; Console.WriteLine($"S'ha aplicat una sancio de {import:C} a {Nom}. Saldo pendent: {SaldoPendent:C}"); }
Solucions
El code smell principal és mètode llarg (amb una responsabilitat barrejada addicional, "massa responsabilitats" a nivell de mètode): un únic mètode valida, espera, registra, persisteix i notifica, tot en el mateix bloc de codi, cosa que en dificulta la lectura i la prova per parts.
public void AplicarSancio(decimal import)
{
RegistrarSancio(import);
NotificarSancio(import);
}
private void RegistrarSancio(decimal import)
{
SaldoPendent += import;
}
private void NotificarSancio(decimal import)
{
Console.WriteLine($"S'ha aplicat una sancio de {import:C} a {Nom}. Saldo pendent: {SaldoPendent:C}");
}
Conclusió
En aquesta lliçó has vist què busca una bona revisió de codi (llegibilitat, proves, adherència
als estàndards, disseny), una checklist concreta per aplicar-la, els code smells més comuns
(mètode llarg, duplicació, classes amb massa responsabilitats, paràmetres excessius), dues
tècniques de refactorització (extreure mètode i extreure classe), i com les proves unitàries de
la lliçó anterior actuen com a xarxa de seguretat en refactoritzar un mètode llarg de
Biblioteca sense canviar el seu comportament observable.
Amb això es tanca el Mòdul 8 al complet. BiblioTech arriba al Mòdul 9 —el Projecte Final del curs— amb un domini sòlid, persistència desacoblada, una bateria de proves unitàries, i un codi revisat segons els estàndards i patrons apresos en aquestes cinc lliçons: tot el necessari per construir, planificar, provar i desplegar la versió completa i definitiva de BiblioTech.
Curs de Programació en C#
Mòdul 1: Introducció al C#
- Introducció al C#
- Configuració de l'Entorn de Desenvolupament
- Programa Hola Món
- Sintaxi i Estructura Bàsica
- Variables i Tipus de Dades
- Arrays i Cadenes de Text
Mòdul 2: Estructures de Control
Mòdul 3: Programació Orientada a Objectes
- Classes i Objectes
- Mètodes
- Constructors i Destructors
- Herència
- Polimorfisme
- Encapsulació
- Abstracció
- Structs i Records: Tipus per Valor i per Referència
Mòdul 4: Conceptes Avançats de C#
- Interfícies
- Delegats i Esdeveniments
- Pattern Matching i Característiques Modernes de C#
- Genèrics
- Col·leccions
- LINQ (Consulta Integrada en el Llenguatge)
- Programació Asíncrona
Mòdul 5: Treballant amb Dades
- Entrada/Sortida de Fitxers
- Serialització
- Connectivitat amb Bases de Dades
- Entity Framework
- Treball amb JSON i Consum d'APIs REST
Mòdul 6: Temes Avançats
- Reflexió
- Atributs
- Programació Dinàmica
- Gestió de Memòria i Recollida d'Escombraries
- Multifil i Programació Paral·lela
Mòdul 7: Construcció d'Aplicacions
Mòdul 8: Bones Pràctiques i Patrons de Disseny
- Estàndards de Codificació i Bones Pràctiques
- Patrons de Disseny
- Injecció de Dependències i Inversió de Control
- Proves Unitàries
- Revisió i Refactorització de Codi
