BiblioTech ha crescut, mòdul a mòdul, fins a tenir un domini complet (MaterialBibliotecari,
Llibre, Revista, Soci, Prestec, Biblioteca), persistència en quatre formes diferents
(text, JSON, SQLite, Entity Framework Core) i cinc interfícies d'usuari diferents. Amb aquest
mòdul, el curs fa un pas enrere: en lloc d'afegir funcionalitat nova, toca consolidar i polir
tot aquest codi. Aquesta primera lliçó se centra en la base de qualsevol procés de polit: els
estàndards de codificació. Un estàndard de codificació no és una qüestió estètica menor —és el
que permet que qualsevol persona (inclòs el teu propi "jo" d'aquí a sis mesos) llegeixi codi que
no va escriure i l'entengui sense esforç—. Repassaràs i ampliaràs convencions que ja coneixes des
del Mòdul 1, i en veuràs algunes de noves que s'apliquen al projecte com a conjunt, no només a una
línia solta.
Contingut
- Per què importen els estàndards de codificació en un projecte que creix
- Convencions de nomenclatura .NET: repàs i ampliació
EditorConfigi analitzadors de codi- Principi de responsabilitat única a nivell de mètode i de classe
- Comentaris útils davant de comentaris soroll
- Documentació XML (
///) a l'API pública deBiblioteca - Nul·labilitat consistent a tot el projecte
- Exemple integrat: abans i després d'un fragment de
Biblioteca
- Per què importen els estàndards de codificació en un projecte que creix
A les primeres lliçons del curs, amb programes de poques línies, qualsevol estil d'escriptura
"funcionava": el codi era tan curt que s'entenia igual, es digués x o numeroPagines a una
variable. BiblioTech ja no és així: té un domini de diverses classes, diverses formes de
persistència i cinc interfícies d'usuari, repartides en un projecte real que, en una empresa,
mantindrien diverses persones alhora. En aquest context, un estàndard de codificació consistent
deixa de ser una preferència personal i esdevé una necessitat:
| Sense estàndard consistent | Amb estàndard consistent |
|---|---|
| Cada classe "es llegeix" diferent; cal reaprendre l'estil a cada fitxer | El codi es llegeix de forma uniforme a tot el projecte |
| Revisar codi d'altri costa més temps (Lliçó 5 d'aquest mòdul) | Les revisions se centren en la lògica, no en discutir l'estil |
| Els errors de nul·labilitat o de noms confusos es colen més fàcilment | Molts errors s'eviten només per seguir la convenció |
| Incorporar algú nou al projecte és lent | El codi nou "encaixa" de seguida amb la resta |
No es tracta d'imposar un estil per caprici, sinó d'eliminar decisions repetitives —majúscula o minúscula inicial? on va el comentari?— per poder dedicar l'atenció al que de veritat importa: que el codi faci el correcte.
- Convencions de nomenclatura .NET: repàs i ampliació
La lliçó de Sintaxi Bàsica i Estructura (Mòdul 1) va introduir les dues convencions centrals de .NET:
| Convenció | Regla | On s'usa |
|---|---|---|
| PascalCase | Cada paraula comença en majúscula | Classes, mètodes, propietats (Biblioteca, RegistrarPrestec, Titol) |
| camelCase | La primera paraula en minúscula | Variables locals i paràmetres (titol, idSoci) |
A això s'hi afegeixen ara tres convencions addicionals, ja aplicades de passada a BiblioTech però sense anomenar-les formalment fins ara:
- Prefix
Ia les interfícies:IPrestable,ICercable(Mòdul 4). El prefixIanticipa, només llegint el nom, que es tracta d'un contracte i no d'una classe concreta —una convenció que veuràs portada més lluny a la Lliçó 3 d'aquest mòdul, ambIRepositoriBiblioteca. - Prefix
_als camps privats:_socisPerId(Mòdul 4, aBiblioteca). Distingeix d'un cop d'ull un camp privat d'una propietat pública o d'una variable local, sense necessitat de mirar la seva declaració. - Sufix
Asyncals mètodes asíncrons:PrestarLlibreAsync(Mòdul 4),ObtenirMetadadesPerIsbnAsync(Mòdul 5). Avisa qui crida el mètode que ha de fer servirawait, sense haver de consultar la signatura completa.
class Biblioteca
{
private Dictionary<int, Soci> _socisPerId = new Dictionary<int, Soci>(); // _prefix: camp privat
public List<MaterialBibliotecari> Cataleg { get; } = new List<MaterialBibliotecari>(); // PascalCase: propietat publica
public async Task PrestarLlibreAsync(Llibre llibre, Soci soci) // sufix Async: metode asincron
{
// ...
}
}Aquestes tres convencions no són caprichos del propi curs: són les que segueix tota la biblioteca estàndard de .NET i la immensa majoria del codi C# publicat, la qual cosa significa que seguir-les fa que BiblioTech "encaixi" de seguida amb qualsevol altre projecte .NET que algú llegeixi després.
EditorConfig i analitzadors de codi
EditorConfig i analitzadors de codiRecordar una convenció de memòria i aplicar-la a mà a cada línia és propens a errors. Dues eines n'automatitzen bona part:
EditorConfig(fitxer.editorconfiga l'arrel del projecte): un fitxer de text que declara regles de format —indentació, ús d'espais davant de tabuladors, salt de línia final, i també convencions de nomenclatura de C#— que editors com Visual Studio o VS Code apliquen automàticament en escriure i donar format al codi.- Analitzadors de codi (Roslyn analyzers): eines que examinen el codi a la recerca de
problemes —des d'estil fins a errors potencials— i mostren avisos directament a l'editor, abans
fins i tot de compilar. .NET inclou un conjunt d'analitzadors activat per defecte a qualsevol
projecte modern (
dotnet new), i es poden ampliar instal·lant paquets NuGet addicionals (per exemple,Microsoft.CodeAnalysis.NetAnalyzersamb regles més estrictes).
# .editorconfig (fragment il·lustratiu)
root = true
[*.cs]
indent_style = space
indent_size = 4
dotnet_naming_rule.interfaces_should_be_prefixed_with_i.severity = warningAquesta lliçó no aprofundeix en la sintaxi completa de .editorconfig ni en la configuració
d'analitzadors —cada equip sol adoptar un conjunt ja preparat—, però és important saber que
existeixen: automatitzen precisament les convencions vistes a l'apartat anterior, de manera que
un oblit de nomenclatura es detecta com un avís a l'editor, no en una revisió de codi dies
després.
- Principi de responsabilitat única a nivell de mètode i de classe
El principi de responsabilitat única (una de les idees centrals dels patrons de disseny, que la lliçó següent retoma amb més detall) diu, de forma senzilla: cada mètode i cada classe hauria de tenir una única raó per canviar. Aplicat al dia a dia, significa que un mètode ha de fer una cosa, i fer-la bé, en comptes de barrejar diverses responsabilitats no relacionades.
// Abans: un metode amb dues responsabilitats barrejades
public void ProcessarPrestec(Llibre llibre, Soci soci)
{
if (!llibre.Disponible)
{
Console.WriteLine("No disponible.");
return;
}
llibre.Prestar();
Console.WriteLine($"'{llibre.Titol}' prestat a {soci.Nom}."); // responsabilitat de presentacio
// ... aqui es podria colar tambe logica de desat, de validacio, de notificacio...
}// Despres: cada metode te una unica responsabilitat
public bool IntentarPrestar(Llibre llibre, Soci soci)
{
if (!llibre.Disponible)
{
return false;
}
llibre.Prestar();
return true;
}
// La decisio de que mostrar per consola (o una altra interficie) viu fora, on correspon
if (biblioteca.IntentarPrestar(llibre1, soci1))
{
Console.WriteLine($"'{llibre1.Titol}' prestat a {soci1.Nom}.");
}
else
{
Console.WriteLine("No disponible.");
}IntentarPrestar ara només decideix si el préstec és possible i actualitza l'estat del
llibre; què fer amb aquest resultat (mostrar-ho per consola, per una finestra WPF, o retornar-ho
com a JSON des d'un endpoint d'ASP.NET Core) és responsabilitat de qui el crida, no del mètode en
si. Aquesta separació és exactament la que ja vas veure, sense anomenar-la així, a les cinc
interfícies del Mòdul 7: la mateixa Biblioteca serveix Windows Forms, WPF, ASP.NET Core, Blazor
i MAUI precisament perquè la seva lògica no assumeix res sobre com es presenten els seus
resultats.
- Comentaris útils davant de comentaris soroll
No tots els comentaris aporten valor. Un comentari que repeteix el que el codi ja diu amb claredat és soroll: ocupa espai, i amb el temps pot quedar desactualitzat respecte al codi real, cosa que és pitjor que no tenir cap comentari.
// Soroll: el comentari no diu res que el codi no digui ja
// Incrementa i en 1
i++;
// Suma el preu al total
total += preu;// Util: explica el "per que", no el "que" (el codi ja ho diu)
// Es revalida la disponibilitat despres del retard simulat, perque podia haver canviat
// mentre s'esperava la resposta del servei de sancions (Modul 4).
if (!llibre.Disponible)
{
throw new InvalidOperationException($"'{llibre.Titol}' no esta disponible per a prestec.");
}| Tipus de comentari | Quan escriure'l? |
|---|---|
| Explica el "què" fa una línia òbvia | Mai: si cal, el nom de la variable o el mètode està mal triat |
| Explica el "per què" d'una decisió no evident | Sí: una regla de negoci, una limitació externa, una raó històrica |
| Adverteix d'un efecte secundari no obvi | Sí: per exemple, que un mètode modifica un objecte rebut per paràmetre |
| Queda desactualitzat respecte al codi actual | Mai: pitjor que no comentar, perquè indueix a error |
Regla pràctica: si sents la necessitat de comentar què fa una línia de codi, primer intenta canviar el nom de variables o extreure un mètode amb un nom més descriptiu; reserva el comentari per al que el codi, per si sol, no pot expressar.
- Documentació XML (
///) a l'API pública de Biblioteca
///) a l'API pública de BibliotecaEl Mòdul 1 va introduir breument els comentaris de documentació (///), basats en etiquetes XML,
sense fer-los servir encara al model de BiblioTech. Ara que Biblioteca té una API pública
consolidada, és el moment de documentar-la:
class Biblioteca
{
/// <summary>
/// Intenta registrar el prestec d'un llibre a un soci.
/// </summary>
/// <param name="llibre">El llibre que es vol prestar.</param>
/// <param name="soci">El soci que sollicita el prestec.</param>
/// <returns>
/// <c>true</c> si el prestec es va registrar correctament; <c>false</c> si el llibre
/// no estava disponible.
/// </returns>
public bool IntentarPrestar(Llibre llibre, Soci soci)
{
if (!llibre.Disponible)
{
return false;
}
llibre.Prestar();
return true;
}
}<summary> descriu breument què fa el membre; <param> documenta cada paràmetre; <returns>
explica el significat del valor retornat. El benefici no és només per a qui llegeix el codi
font: qualsevol editor modern (Visual Studio, VS Code amb l'extensió de C#) mostra aquest text
automàticament com a ajuda contextual en escriure una crida a IntentarPrestar, igual com passa
amb els mètodes de la pròpia biblioteca estàndard de .NET (Console.WriteLine, per exemple, té
la seva pròpia documentació XML). Documentar així tota una classe petita seria excessiu; la
pràctica habitual és reservar /// per a l'API pública de les classes centrals del projecte
—exactament el cas de Biblioteca— i ometre-ho en detalls interns que ja s'expliquen sols pel
seu nom.
- Nul·labilitat consistent a tot el projecte
La lliçó de Pattern Matching i Característiques Modernes (Mòdul 4) va presentar #nullable enable i va advertir d'un error comú: activar-ho a mitja projecte i no atendre els avisos que
genera. Ara que BiblioTech és un projecte complet, aquesta recomanació esdevé una regla
d'estàndard de codificació: #nullable enable s'ha d'aplicar de forma consistent a tots els
fitxers del projecte, no només als que es van tocar més recentment.
#nullable enable
class Biblioteca
{
public MaterialBibliotecari? CercarPerTitol(string titol)
{
return Cataleg.FirstOrDefault(material => material.Titol == titol);
// FirstOrDefault pot retornar null; el "?" al tipus de retorn ho fa explicit
}
public List<MaterialBibliotecari> Cataleg { get; } = new List<MaterialBibliotecari>();
}| Sense nul·labilitat consistent | Amb nul·labilitat consistent |
|---|---|
Alguns mètodes avisen que poden retornar null, altres no, sense cap criteri |
Tot tipus de referència que pot ser null ho declara amb ?, a tot el projecte |
El compilador només avisa als fitxers on està activat #nullable enable |
El compilador avisa de forma uniforme a qualsevol fitxer |
Risc de NullReferenceException en fitxers "oblidats" |
El risc es concentra on de veritat pot passar, marcat amb ? |
En un projecte nou, la forma més senzilla d'aconseguir aquesta consistència és activar
<Nullable>enable</Nullable> una única vegada al fitxer de projecte (.csproj), que aplica
#nullable enable a tots els fitxers .cs automàticament, sense haver de repetir la directiva a
cadascun.
- Exemple integrat: abans i després d'un fragment de
Biblioteca
BibliotecaUnint tot l'anterior, així es veu un fragment de Biblioteca sense cuidar els estàndards vistos
en aquesta lliçó, i la seva versió revisada:
// Abans: noms poc clars, responsabilitats barrejades, sense documentacio, sense nul·labilitat
class Biblioteca
{
public List<MaterialBibliotecari> llista = new List<MaterialBibliotecari>();
public MaterialBibliotecari Obtenir(string t)
{
foreach (var x in llista)
{
if (x.Titol == t)
{
return x;
}
}
return null;
}
public void Proc(string t, Soci s)
{
var m = Obtenir(t);
if (m != null && m.Disponible)
{
m.Prestar();
Console.WriteLine($"'{m.Titol}' prestat a {s.Nom}.");
}
else
{
Console.WriteLine("No s'ha pogut prestar.");
}
}
}// Despres: noms descriptius, responsabilitats separades, documentat, nul·labilitat explicita
#nullable enable
class Biblioteca
{
public List<MaterialBibliotecari> Cataleg { get; } = new List<MaterialBibliotecari>();
/// <summary>
/// Cerca un material del catalog per el seu titol exacte.
/// </summary>
/// <param name="titol">El titol a cercar.</param>
/// <returns>El material trobat, o <c>null</c> si cap material coincideix.</returns>
public MaterialBibliotecari? CercarPerTitol(string titol)
{
return Cataleg.FirstOrDefault(material => material.Titol == titol);
}
/// <summary>
/// Intenta prestar el material amb el titol indicat.
/// </summary>
/// <param name="titol">El titol del material a prestar.</param>
/// <param name="soci">El soci que sollicita el prestec.</param>
/// <returns><c>true</c> si el prestec es va registrar; <c>false</c> en cas contrari.</returns>
public bool IntentarPrestarPerTitol(string titol, Soci soci)
{
MaterialBibliotecari? material = CercarPerTitol(titol);
if (material is null || !material.Disponible)
{
return false;
}
material.Prestar();
return true;
}
}CercarPerTitol i IntentarPrestarPerTitol són ara dos mètodes amb una única responsabilitat
cadascun, amb noms que diuen exactament què fan, documentats amb ///, i amb la nul·labilitat de
CercarPerTitol explícita a la seva signatura (MaterialBibliotecari?). Cal notar que la
decisió de què mostrar per consola ja no viu dins de Biblioteca: queda, com a l'apartat 4,
en mans de qui crida el mètode, sigui la consola, un formulari o un endpoint HTTP.
Errors Comuns i Consells
- Barrejar convencions de nomenclatura dins del mateix projecte: fer servir
_campen una classe icampa seques en una altra, sense cap criteri, confon més que no tenir cap convenció. Aplica la mateixa regla a tot el projecte. - Comentar el "què" en comptes de canviar el nom: si necessites un comentari per explicar què fa una línia senzilla, gairebé sempre és preferible millorar el nom de la variable o extreure un mètode amb un nom descriptiu.
- Documentar amb
///cada línia del projecte: és un esforç desproporcionat i amb el temps tendeix a desactualitzar-se. Reserva///per a l'API pública de les classes centrals. - Activar
#nullable enablenomés en fitxers nous: deixa el projecte amb un criteri inconsistent. Activa'l a nivell de projecte (.csproj) perquè s'apliqui de forma uniforme. - Consell: si tens dubtes sobre com anomenar alguna cosa, pregunta't quin nom faria servir la
pròpia biblioteca estàndard de .NET per a un concepte equivalent (
List,Dictionary,HttpClient...); gairebé sempre aquesta intuïció coincideix amb la convenció correcta.
Exercicis
-
Donat el següent mètode de
Biblioteca, identifica tres problemes d'estàndard de codificació (nomenclatura, responsabilitat única, nul·labilitat) i reescriu-lo corregint-los:public Soci Cercar(int i) { foreach (var s in Socis) { if (s.Id == i) return s; } return null; } -
Afegeix documentació XML (
///amb<summary>,<param>i<returns>) al mètode corregit de l'exercici anterior.
Solucions
Problemes: (a) el paràmetre i i el nom Cercar són poc descriptius; (b) el mètode pot
retornar null però la seva signatura no ho reflecteix (Soci en comptes de Soci?); (c) el
nom no distingeix "cercar per id" d'altres possibles cerques futures (per nom, per exemple).
public Soci? CercarSociPerId(int idSoci)
{
return Socis.FirstOrDefault(soci => soci.Id == idSoci);
}
/// <summary>
/// Cerca un soci pel seu identificador.
/// </summary>
/// <param name="idSoci">L'identificador del soci a cercar.</param>
/// <returns>El soci trobat, o <c>null</c> si no existeix cap soci amb aquest identificador.</returns>
public Soci? CercarSociPerId(int idSoci)
{
return Socis.FirstOrDefault(soci => soci.Id == idSoci);
}
Conclusió
En aquesta lliçó has repassat i ampliat les convencions de nomenclatura de .NET
(PascalCase/camelCase, prefixos I/_, sufix Async), conegut EditorConfig i els
analitzadors de codi com a eines que automatitzen aquestes convencions, aplicat el principi de
responsabilitat única per separar lògica de presentació, distingit comentaris útils de soroll,
documentat l'API pública de Biblioteca amb ///, i establert la nul·labilitat consistent com a
estàndard de tot el projecte. Amb aquest fragment de Biblioteca ja més net i llegible, la
lliçó següent fa un pas més enllà de l'estil: els patrons de disseny, solucions ja provades a
problemes de disseny recurrents, que veuràs aplicades directament sobre el propi domini 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
