Clean Code & SOLID Flashcards
¿Cuáles son los objetivos principales del diseño de código?
- Mantenibilidad,
- simplicidad,
- claridad,
- flexibilidad y
- legibilidad.
Nombres: ¿Cómo tienen que ser?
Significativos y pronunciables
¿Qué deben revelar los nombres?
La intención
¿Por qué se deben evitar valores fijos en el código?
Los valores fijos no son buscables, es preferible usar constantes con nombres claros.
¿Qué principio sigue el enfoque de usar polimorfismo en lugar de banderas (flags)?
Principio de responsabilidad única, ya que separa las condiciones en funciones específicas en lugar de manejar múltiples casos en una sola función.
¿Qué significa el principio DRY?
“Don’t Repeat Yourself”, es decir, evitar la duplicación de código.
¿Qué dice la “Regla del Boy Scout” en el desarrollo de software?
Deja el código en mejor estado del que lo encontraste, siempre mejorando aunque sea un poco.
¿Qué significa el “Principio de la menor sorpresa”?
El código debe comportarse de manera que sea intuitivo y no sorprenda a quienes lo leen o lo mantienen.
¿Qué tipo de comentarios son buenos?
Comentarios legales, informativos, que expliquen la intención o clarifiquen el código, advertencias de consecuencias, y comentarios TODO.
¿Qué significa el acrónimo F.I.R.S.T. en pruebas unitarias?
Fast, Independent, Repeatable, Self-Validating, Timely.
¿Qué principios se deben seguir al escribir casos de prueba?
Prueba una sola cosa por caso de prueba, usa un único método de assert, y utiliza nombres descriptivos.
¿Cuál es el principio de “Single Responsibility”?
Una función o clase debe tener una única responsabilidad o propósito.
¿Cuál es el beneficio principal del “Single Responsibility Principle”?
Facilita el mantenimiento y reduce el acoplamiento al tener clases enfocadas en un solo propósito.
¿En qué consiste el “Open/Closed Principle” (OCP)?
Las clases deben estar abiertas para extensión, pero cerradas para modificación.
¿Cómo se puede cumplir con el “Open/Closed Principle”?
Mediante el uso de abstracciones, como interfaces o clases base, que permiten extender el comportamiento sin modificar el código existente.
¿En qué consiste el “Liskov Substitution Principle” (LSP)?
Los objetos de una subclase deben poder reemplazar a los de su clase base sin alterar el correcto funcionamiento del programa.
¿Qué problemas se pueden evitar aplicando el “Liskov Substitution Principle”?
Evita violaciones en el comportamiento esperado del programa cuando se utilizan subclases, asegurando la compatibilidad.
¿En qué consiste el “Interface Segregation Principle” (ISP)?
Los clientes no deberían estar forzados a depender de interfaces que no utilizan. Es preferible tener interfaces específicas y pequeñas.
¿Cómo ayuda el “Interface Segregation Principle” en el diseño de software?
Minimiza el acoplamiento al permitir que las clases dependan solo de los métodos que realmente necesitan, evitando la implementación de métodos innecesarios.
¿En qué consiste el “Dependency Inversion Principle” (DIP)?
Los módulos de alto nivel no deben depender de los de bajo nivel. Ambos deben depender de abstracciones (interfaces o clases abstractas), y las abstracciones no deben depender de detalles.
¿Qué principio se aplica?
Antes:
public class Reporte { public String generarReporte() { // Generar contenido del reporte return "Contenido del reporte"; } public void imprimirReporte() { // Imprimir el reporte en consola System.out.println(generarReporte()); } }
Despues:
// Clase responsable solo de generar el contenido del reporte public class Reporte { public String generarReporte() { return "Contenido del reporte"; } } // Clase responsable solo de imprimir el reporte public class ImpresoraReporte { public void imprimir(String contenidoReporte) { System.out.println(contenidoReporte); } }
Single Responsability Principle
¿Qué principio se aplica?
Antes:
public class CalculadoraDeArea { public double calcularArea(Object forma) { if (forma instanceof Circulo) { Circulo circulo = (Circulo) forma; return Math.PI * circulo.radio * circulo.radio; } else if (forma instanceof Rectangulo) { Rectangulo rectangulo = (Rectangulo) forma; return rectangulo.ancho * rectangulo.alto; } return 0; } } class Circulo { public double radio; } class Rectangulo { public double ancho; public double alto; }
Despues:
// Interfaz que define un contrato para las formas public interface Forma { double calcularArea(); } // Implementación de un Círculo que calcula su propia área public class Circulo implements Forma { public double radio; @Override public double calcularArea() { return Math.PI * radio * radio; } } // Implementación de un Rectángulo que calcula su propia área public class Rectangulo implements Forma { public double ancho; public double alto; @Override public double calcularArea() { return ancho * alto; } } // Calculadora de área que no necesita ser modificada public class CalculadoraDeArea { public double calcularArea(Forma forma) { return forma.calcularArea(); } }
Open/Closed principle.
Podemos usar polimorfismo. Creamos una interfaz para las formas geométricas y cada nueva forma implementa su propio método para calcular el área. De esta forma, podemos añadir nuevas formas sin modificar la clase CalculadoraDeArea.
¿Qué principio se aplica?
Antes:
class Rectangulo { protected int ancho; protected int alto; public void setAncho(int ancho) { this.ancho = ancho; } public void setAlto(int alto) { this.alto = alto; } public int getArea() { return ancho * alto; } } class Cuadrado extends Rectangulo { @Override public void setAncho(int ancho) { this.ancho = ancho; this.alto = ancho; // Fuerza a que alto sea igual a ancho } @Override public void setAlto(int alto) { this.ancho = alto; this.alto = alto; // Fuerza a que ancho sea igual a alto } }
Despues:
class Rectangulo { protected int ancho; protected int alto; public void setAncho(int ancho) { this.ancho = ancho; } public void setAlto(int alto) { this.alto = alto; } public int getArea() { return ancho * alto; } } class Cuadrado { private int lado; public void setLado(int lado) { this.lado = lado; } public int getArea() { return lado * lado; } }
Principio de Sustitución de Liskov. Si se sustituyera un Rectangulo por un Cuadrado, el comportamiento sería inesperado. El comportamiento del Cuadrado rompe las expectativas de la clase base Rectangulo porque cambia las reglas del rectángulo (ancho ≠ alto).
Luego del cambio, Cuadrado y Rectangulo tienen comportamientos coherentes sin romper el principio de sustitución. Se puede tratar a cada clase de acuerdo a su naturaleza sin sorpresas inesperadas.
¿Qué principio se aplica?
// Clase de bajo nivel que envía correos electrónicos public class NotificadorEmail { public void enviarCorreo(String mensaje) { // Lógica para enviar un correo electrónico System.out.println("Enviando correo: " + mensaje); } } // Clase de alto nivel que utiliza NotificadorEmail para enviar notificaciones public class UsuarioServicio { private NotificadorEmail notificadorEmail; public UsuarioServicio() { this.notificadorEmail = new NotificadorEmail(); } public void notificarUsuario(String mensaje) { notificadorEmail.enviarCorreo(mensaje); } }
Aplicado:
// Interfaz que define el contrato para la notificación public interface Notificador { void notificar(String mensaje); } // Clase de bajo nivel que implementa la interfaz para enviar correos electrónicos public class NotificadorEmail implements Notificador { @Override public void notificar(String mensaje) { System.out.println("Enviando correo: " + mensaje); } } // Nueva clase de bajo nivel que implementa la interfaz para enviar SMS public class NotificadorSMS implements Notificador { @Override public void notificar(String mensaje) { System.out.println("Enviando SMS: " + mensaje); } } // Clase de alto nivel que depende de la abstracción (Notificador) y no de la implementación concreta public class UsuarioServicio { private Notificador notificador; public UsuarioServicio(Notificador notificador) { this.notificador = notificador; } public void notificarUsuario(String mensaje) { notificador.notificar(mensaje); } }
Principio de Inversión de Dependencias (DIP) o Dependency Inversion Principle.
¿Qué principio se aplica?
Antes:
class Servicio { public void ejecutar() { // Lógica de servicio } } class Controlador { private Servicio servicio = new Servicio(); public void hacerAlgo() { servicio.ejecutar(); } }
Despues:
interface IServicio { void ejecutar(); } class Servicio implements IServicio { public void ejecutar() { // Lógica de servicio } } class Controlador { private IServicio servicio; public Controlador(IServicio servicio) { this.servicio = servicio; } public void hacerAlgo() { servicio.ejecutar(); } }
Principio de Inversión de Dependencias. Antes, Controlador está acoplado directamente a Servicio, dificultando pruebas y la posibilidad de cambiar la implementación de Servicio.
Ahora, Controlador depende de la interfaz IServicio, lo que permite inyectar diferentes implementaciones de Servicio y facilita las pruebas. Esto reduce el acoplamiento y mejora la flexibilidad.