BiblioTech ha crecido, módulo a módulo, hasta tener un dominio completo (MaterialBibliotecario,
Libro, Revista, Socio, Prestamo, Biblioteca), persistencia en cuatro formas distintas
(texto, JSON, SQLite, Entity Framework Core) y cinco interfaces de usuario diferentes. Con este
módulo, el curso da un paso atrás: en vez de añadir funcionalidad nueva, toca consolidar y
pulir todo ese código. Esta primera lección se centra en la base de cualquier proceso de
pulido: los estándares de codificación. Un estándar de codificación no es una cuestión estética
menor —es lo que permite que cualquier persona (incluido tu propio "yo" dentro de seis meses)
lea código que no escribió y lo entienda sin esfuerzo. Repasarás y ampliarás convenciones que ya
conoces desde el Módulo 1, y verás algunas nuevas que se aplican al proyecto como conjunto, no
solo a una línea suelta.
Contenido
- Por qué importan los estándares de codificación en un proyecto que crece
- Convenciones de nomenclatura .NET: repaso y ampliación
EditorConfigy analizadores de código- Principio de responsabilidad única a nivel de método y de clase
- Comentarios útiles frente a comentarios ruido
- Documentación XML (
///) en la API pública deBiblioteca - Nulabilidad consistente en todo el proyecto
- Ejemplo integrado: antes y después de un fragmento de
Biblioteca
- Por qué importan los estándares de codificación en un proyecto que crece
En las primeras lecciones del curso, con programas de pocas líneas, cualquier estilo de
escritura "funcionaba": el código era tan corto que se entendía igual, se llamara x o
numeroPaginas a una variable. BiblioTech ya no es así: tiene un dominio de varias clases,
varias formas de persistencia y cinco interfaces de usuario, repartidas en un proyecto real que,
en una empresa, mantendrían varias personas a la vez. En ese contexto, un estándar de
codificación consistente deja de ser una preferencia personal y se convierte en una necesidad:
| Sin estándar consistente | Con estándar consistente |
|---|---|
| Cada clase "se lee" distinto; hay que reaprender el estilo en cada fichero | El código se lee de forma uniforme en todo el proyecto |
| Revisar código ajeno cuesta más tiempo (Lección 5 de este módulo) | Las revisiones se centran en la lógica, no en discutir el estilo |
| Los errores de nulabilidad o de nombres confusos se cuelan más fácilmente | Muchos errores se evitan solo por seguir la convención |
| Incorporar a alguien nuevo al proyecto es lento | El código nuevo "encaja" de inmediato con el resto |
No se trata de imponer un estilo por capricho, sino de eliminar decisiones repetitivas —¿mayúscula o minúscula inicial? ¿dónde va el comentario?— para poder dedicar la atención a lo que de verdad importa: que el código haga lo correcto.
- Convenciones de nomenclatura .NET: repaso y ampliación
La lección de Sintaxis Básica y Estructura (Módulo 1) introdujo las dos convenciones centrales de .NET:
| Convención | Regla | Dónde se usa |
|---|---|---|
| PascalCase | Cada palabra empieza en mayúscula | Clases, métodos, propiedades (Biblioteca, RegistrarPrestamo, Titulo) |
| camelCase | La primera palabra en minúscula | Variables locales y parámetros (titulo, idSocio) |
A eso se añaden ahora tres convenciones adicionales, ya aplicadas de pasada en BiblioTech pero sin nombrarlas formalmente hasta ahora:
- Prefijo
Ien interfaces:IPrestable,IBuscable(Módulo 4). El prefijoIanticipa, con solo leer el nombre, que se trata de un contrato y no de una clase concreta —una convención que verás llevada más lejos en la Lección 3 de este módulo, conIRepositorioBiblioteca. - Prefijo
_en campos privados:_sociosPorId(Módulo 4, enBiblioteca). Distingue de un vistazo un campo privado de una propiedad pública o de una variable local, sin necesitar mirar su declaración. - Sufijo
Asyncen métodos asíncronos:PrestarLibroAsync(Módulo 4),ObtenerMetadatosPorIsbnAsync(Módulo 5). Avisa a quien llama al método que debe usarawait, sin tener que consultar la firma completa.
class Biblioteca
{
private Dictionary<int, Socio> _sociosPorId = new Dictionary<int, Socio>(); // _prefijo: campo privado
public List<MaterialBibliotecario> Catalogo { get; } = new List<MaterialBibliotecario>(); // PascalCase: propiedad publica
public async Task PrestarLibroAsync(Libro libro, Socio socio) // sufijo Async: metodo asincrono
{
// ...
}
}Estas tres convenciones no son caprichos del propio curso: son las que sigue toda la biblioteca estándar de .NET y la inmensa mayoría del código C# publicado, lo que significa que seguirlas hace que BiblioTech "encaje" de inmediato con cualquier otro proyecto .NET que alguien lea después.
EditorConfig y analizadores de código
EditorConfig y analizadores de códigoRecordar una convención de memoria y aplicarla a mano en cada línea es propenso a errores. Dos herramientas automatizan buena parte de ese trabajo:
EditorConfig(fichero.editorconfigen la raíz del proyecto): un fichero de texto que declara reglas de formato —indentación, uso de espacios frente a tabuladores, salto de línea final, y también convenciones de nomenclatura de C#— que editores como Visual Studio o VS Code aplican automáticamente al escribir y al dar formato al código.- Analizadores de código (Roslyn analyzers): herramientas que examinan el código en busca de
problemas —desde estilo hasta errores potenciales— y muestran avisos directamente en el editor,
antes incluso de compilar. .NET incluye un conjunto de analizadores activado por defecto en
cualquier proyecto moderno (
dotnet new), y pueden ampliarse instalando paquetes NuGet adicionales (por ejemplo,Microsoft.CodeAnalysis.NetAnalyzerscon reglas más estrictas).
# .editorconfig (fragmento ilustrativo)
root = true
[*.cs]
indent_style = space
indent_size = 4
dotnet_naming_rule.interfaces_should_be_prefixed_with_i.severity = warningEsta lección no profundiza en la sintaxis completa de .editorconfig ni en la configuración de
analizadores —cada equipo suele adoptar un conjunto ya preparado—, pero es importante saber que
existen: automatizan justamente las convenciones vistas en el apartado anterior, de modo que un
olvido de nomenclatura se detecta como un aviso en el editor, no en una revisión de código días
después.
- Principio de responsabilidad única a nivel de método y de clase
El principio de responsabilidad única (una de las ideas centrales de los patrones de diseño, que la próxima lección retoma con más detalle) dice, de forma simple: cada método y cada clase debería tener una única razón para cambiar. Aplicado al día a día, significa que un método debe hacer una cosa, y hacerla bien, en vez de mezclar varias responsabilidades no relacionadas.
// Antes: un metodo con dos responsabilidades mezcladas
public void ProcesarPrestamo(Libro libro, Socio socio)
{
if (!libro.Disponible)
{
Console.WriteLine("No disponible.");
return;
}
libro.Prestar();
Console.WriteLine($"'{libro.Titulo}' prestado a {socio.Nombre}."); // responsabilidad de presentacion
// ... aqui se podria colar tambien logica de guardado, de validacion, de notificacion...
}// Despues: cada metodo tiene una unica responsabilidad
public bool IntentarPrestar(Libro libro, Socio socio)
{
if (!libro.Disponible)
{
return false;
}
libro.Prestar();
return true;
}
// La decision de que mostrar por consola (u otra interfaz) vive fuera, donde corresponde
if (biblioteca.IntentarPrestar(libro1, socio1))
{
Console.WriteLine($"'{libro1.Titulo}' prestado a {socio1.Nombre}.");
}
else
{
Console.WriteLine("No disponible.");
}IntentarPrestar ahora solo decide si el préstamo es posible y actualiza el estado del
libro; qué hacer con ese resultado (mostrarlo por consola, por una ventana WPF, o devolverlo como
JSON desde un endpoint de ASP.NET Core) es responsabilidad de quien lo llama, no del método en sí.
Esta separación es exactamente la que ya viste, sin nombrarla así, en las cinco interfaces del
Módulo 7: la misma Biblioteca sirve a Windows Forms, WPF, ASP.NET Core, Blazor y MAUI
precisamente porque su lógica no asume nada sobre cómo se presentan sus resultados.
- Comentarios útiles frente a comentarios ruido
No todos los comentarios aportan valor. Un comentario que repite lo que el código ya dice con claridad es ruido: ocupa espacio, y con el tiempo puede quedar desactualizado respecto al código real, lo que es peor que no tener comentario alguno.
// Ruido: el comentario no dice nada que el codigo no diga ya
// Incrementa i en 1
i++;
// Suma el precio al total
total += precio;// Util: explica el "por que", no el "que" (el codigo ya lo dice)
// Se revalida la disponibilidad tras el retardo simulado, porque pudo cambiar
// mientras se esperaba la respuesta del servicio de sanciones (Modulo 4).
if (!libro.Disponible)
{
throw new InvalidOperationException($"'{libro.Titulo}' no esta disponible para prestamo.");
}| Tipo de comentario | ¿Cuándo escribirlo? |
|---|---|
| Explica el "qué" hace una línea obvia | Nunca: si hace falta, el nombre de la variable o el método está mal elegido |
| Explica el "por qué" de una decisión no evidente | Sí: una regla de negocio, una limitación externa, una razón histórica |
| Advierte de un efecto secundario no obvio | Sí: por ejemplo, que un método modifica un objeto recibido por parámetro |
| Queda desactualizado respecto al código actual | Nunca: peor que no comentar, porque induce a error |
Regla práctica: si sientes la necesidad de comentar qué hace una línea de código, primero intenta renombrar variables o extraer un método con un nombre más descriptivo; reserva el comentario para lo que el código, por sí solo, no puede expresar.
- Documentación XML (
///) en la API pública de Biblioteca
///) en la API pública de BibliotecaEl Módulo 1 introdujo brevemente los comentarios de documentación (///), basados en etiquetas
XML, sin usarlos todavía en el modelo de BiblioTech. Ahora que Biblioteca tiene una API pública
consolidada, es el momento de documentarla:
class Biblioteca
{
/// <summary>
/// Intenta registrar el prestamo de un libro a un socio.
/// </summary>
/// <param name="libro">El libro que se quiere prestar.</param>
/// <param name="socio">El socio que solicita el prestamo.</param>
/// <returns>
/// <c>true</c> si el prestamo se registro correctamente; <c>false</c> si el libro
/// no estaba disponible.
/// </returns>
public bool IntentarPrestar(Libro libro, Socio socio)
{
if (!libro.Disponible)
{
return false;
}
libro.Prestar();
return true;
}
}<summary> describe brevemente qué hace el miembro; <param> documenta cada parámetro;
<returns> explica el significado del valor devuelto. El beneficio no es solo para quien lee el
código fuente: cualquier editor moderno (Visual Studio, VS Code con la extensión de C#) muestra
este texto automáticamente como ayuda contextual al escribir una llamada a IntentarPrestar,
igual que ocurre con los métodos de la propia biblioteca estándar de .NET (Console.WriteLine,
por ejemplo, tiene su propia documentación XML). Documentar así toda una clase pequeña sería
excesivo; la práctica habitual es reservar /// para la API pública de las clases centrales
del proyecto —exactamente el caso de Biblioteca— y omitirlo en detalles internos que ya se
explican solos por su nombre.
- Nulabilidad consistente en todo el proyecto
La lección de Pattern Matching y Características Modernas (Módulo 4) presentó #nullable enable
y advirtió de un error común: activarlo a mitad de proyecto y no atender a los avisos que genera.
Ahora que BiblioTech es un proyecto completo, esa recomendación se convierte en una regla de
estándar de codificación: #nullable enable debe aplicarse de forma consistente en todos los
ficheros del proyecto, no solo en los que se tocaron más recientemente.
#nullable enable
class Biblioteca
{
public MaterialBibliotecario? BuscarPorTitulo(string titulo)
{
return Catalogo.FirstOrDefault(material => material.Titulo == titulo);
// FirstOrDefault puede devolver null; el "?" en el tipo de retorno lo hace explicito
}
public List<MaterialBibliotecario> Catalogo { get; } = new List<MaterialBibliotecario>();
}| Sin nulabilidad consistente | Con nulabilidad consistente |
|---|---|
Algunos métodos avisan de que pueden devolver null, otros no, sin ningún criterio |
Todo tipo de referencia que puede ser null lo declara con ?, en todo el proyecto |
El compilador solo avisa en los ficheros donde está activado #nullable enable |
El compilador avisa de forma uniforme en cualquier fichero |
Riesgo de NullReferenceException en ficheros "olvidados" |
El riesgo se concentra donde de verdad puede ocurrir, marcado con ? |
En un proyecto nuevo, la forma más simple de lograr esta consistencia es activar
<Nullable>enable</Nullable> una única vez en el fichero de proyecto (.csproj), que aplica
#nullable enable a todos los ficheros .cs automáticamente, sin tener que repetir la directiva
en cada uno de ellos.
- Ejemplo integrado: antes y después de un fragmento de
Biblioteca
BibliotecaUniendo todo lo anterior, así se ve un fragmento de Biblioteca sin cuidar los estándares vistos
en esta lección, y su versión revisada:
// Antes: nombres poco claros, responsabilidades mezcladas, sin documentacion, sin nulabilidad
class Biblioteca
{
public List<MaterialBibliotecario> lista = new List<MaterialBibliotecario>();
public MaterialBibliotecario Get(string t)
{
foreach (var x in lista)
{
if (x.Titulo == t)
{
return x;
}
}
return null;
}
public void Proc(string t, Socio s)
{
var m = Get(t);
if (m != null && m.Disponible)
{
m.Prestar();
Console.WriteLine($"'{m.Titulo}' prestado a {s.Nombre}.");
}
else
{
Console.WriteLine("No se pudo prestar.");
}
}
}// Despues: nombres descriptivos, responsabilidades separadas, documentado, nulabilidad explicita
#nullable enable
class Biblioteca
{
public List<MaterialBibliotecario> Catalogo { get; } = new List<MaterialBibliotecario>();
/// <summary>
/// Busca un material del catalogo por su titulo exacto.
/// </summary>
/// <param name="titulo">El titulo a buscar.</param>
/// <returns>El material encontrado, o <c>null</c> si ningun material coincide.</returns>
public MaterialBibliotecario? BuscarPorTitulo(string titulo)
{
return Catalogo.FirstOrDefault(material => material.Titulo == titulo);
}
/// <summary>
/// Intenta prestar el material con el titulo indicado.
/// </summary>
/// <param name="titulo">El titulo del material a prestar.</param>
/// <param name="socio">El socio que solicita el prestamo.</param>
/// <returns><c>true</c> si el prestamo se registro; <c>false</c> en caso contrario.</returns>
public bool IntentarPrestarPorTitulo(string titulo, Socio socio)
{
MaterialBibliotecario? material = BuscarPorTitulo(titulo);
if (material is null || !material.Disponible)
{
return false;
}
material.Prestar();
return true;
}
}BuscarPorTitulo y IntentarPrestarPorTitulo son ahora dos métodos con una única
responsabilidad cada uno, con nombres que dicen exactamente qué hacen, documentados con ///, y
con la nulabilidad de BuscarPorTitulo explícita en su firma (MaterialBibliotecario?). Nótese
que la decisión de qué mostrar por consola ya no vive dentro de Biblioteca: queda, como en
el apartado 4, en manos de quien llama al método, sea la consola, un formulario o un endpoint
HTTP.
Errores Comunes y Consejos
- Mezclar convenciones de nomenclatura dentro del mismo proyecto: usar
_campoen una clase ycampoa secas en otra, sin ningún criterio, confunde más que no tener convención alguna. Aplica la misma regla en todo el proyecto. - Comentar el "qué" en vez de renombrar: si necesitas un comentario para explicar qué hace una línea sencilla, casi siempre es preferible mejorar el nombre de la variable o extraer un método con un nombre descriptivo.
- Documentar con
///cada línea del proyecto: es un esfuerzo desproporcionado y con el tiempo tiende a desactualizarse. Reserva///para la API pública de las clases centrales. - Activar
#nullable enablesolo en ficheros nuevos: deja al proyecto con un criterio inconsistente. Actívalo a nivel de proyecto (.csproj) para que se aplique de forma uniforme. - Consejo: si tienes duda sobre cómo nombrar algo, pregúntate qué nombre usaría la propia
biblioteca estándar de .NET para un concepto equivalente (
List,Dictionary,HttpClient...); casi siempre esa intuición coincide con la convención correcta.
Ejercicios
-
Dado el siguiente método de
Biblioteca, identifica tres problemas de estándar de codificación (nomenclatura, responsabilidad única, nulabilidad) y reescríbelo corrigiéndolos:public Socio Buscar(int i) { foreach (var s in Socios) { if (s.Id == i) return s; } return null; } -
Añade documentación XML (
///con<summary>,<param>y<returns>) al método corregido del ejercicio anterior.
Soluciones
Problemas: (a) el parámetro i y el nombre Buscar son poco descriptivos; (b) el método
puede devolver null pero su firma no lo refleja (Socio en vez de Socio?); (c) el nombre
no distingue "buscar por id" de otras posibles búsquedas futuras (por nombre, por ejemplo).
public Socio? BuscarSocioPorId(int idSocio)
{
return Socios.FirstOrDefault(socio => socio.Id == idSocio);
}
/// <summary>
/// Busca un socio por su identificador.
/// </summary>
/// <param name="idSocio">El identificador del socio a buscar.</param>
/// <returns>El socio encontrado, o <c>null</c> si no existe ningun socio con ese identificador.</returns>
public Socio? BuscarSocioPorId(int idSocio)
{
return Socios.FirstOrDefault(socio => socio.Id == idSocio);
}
Conclusión
En esta lección has repasado y ampliado las convenciones de nomenclatura de .NET
(PascalCase/camelCase, prefijos I/_, sufijo Async), conocido EditorConfig y los
analizadores de código como herramientas que automatizan esas convenciones, aplicado el
principio de responsabilidad única para separar lógica de presentación, distinguido comentarios
útiles de ruido, documentado la API pública de Biblioteca con ///, y establecido la
nulabilidad consistente como estándar de todo el proyecto. Con este fragmento de Biblioteca ya
más limpio y legible, la siguiente lección da un paso más allá del estilo: los patrones de
diseño, soluciones ya probadas a problemas de diseño recurrentes, que verás aplicadas
directamente sobre el propio dominio de BiblioTech.
Curso de Programación en C#
Módulo 1: Introducción a C#
- Introducción a C#
- Configuración del Entorno de Desarrollo
- Programa Hola Mundo
- Sintaxis y Estructura Básica
- Variables y Tipos de Datos
- Arrays y Cadenas de Texto
Módulo 2: Estructuras de Control
Módulo 3: Programación Orientada a Objetos
- Clases y Objetos
- Métodos
- Constructores y Destructores
- Herencia
- Polimorfismo
- Encapsulamiento
- Abstracción
- Structs y Records: Tipos por Valor y por Referencia
Módulo 4: Conceptos Avanzados de C#
- Interfaces
- Delegados y Eventos
- Pattern Matching y Características Modernas de C#
- Genéricos
- Colecciones
- LINQ (Consulta Integrada en el Lenguaje)
- Programación Asíncrona
Módulo 5: Trabajando con Datos
- Entrada/Salida de Archivos
- Serialización
- Conectividad con Bases de Datos
- Entity Framework
- Trabajo con JSON y Consumo de APIs REST
Módulo 6: Temas Avanzados
- Reflexión
- Atributos
- Programación Dinámica
- Gestión de Memoria y Recolección de Basura
- Multihilo y Programación Paralela
Módulo 7: Construcción de Aplicaciones
- Formularios de Windows
- WPF (Windows Presentation Foundation)
- ASP.NET Core
- Blazor
- Xamarin y .NET MAUI
Módulo 8: Mejores Prácticas y Patrones de Diseño
- Estándares de Codificación y Mejores Prácticas
- Patrones de Diseño
- Inyección de Dependencias e Inversión de Control
- Pruebas Unitarias
- Revisión y Refactorización de Código
