El principio KISS nos recuerda que la simplicidad es una elección consciente en cada decisión de diseño. En equipos que programan con Java, aplicar KISS significa crear clases con responsabilidades claras, métodos con contratos directos y arquitecturas que resuelven la necesidad actual sin introducir capas innecesarias.
La simplicidad no se logra por accidente: requiere criterios explícitos y una cultura que privilegie el entendimiento compartido sobre la sofisticación técnica. En este tema exploramos cómo plasmar KISS en los distintos niveles de diseño.
Aplicar KISS empieza por definir qué entendemos por simple. Algunas reglas prácticas:
Cuando una clase acumula demasiadas responsabilidades, su legibilidad cae en picada. Usa KISS para dividirlas según el lenguaje del dominio y evita prefijos genéricos como Manager o Helper que esconden la verdadera intención. Una técnica efectiva es crear clases finitas con métodos que respondan a una única pregunta del negocio.
Los objetos de valor (Value Objects) ayudan a encapsular invariantes y a reducir parámetros repetidos en múltiples métodos. De esta manera, el código transmite la estructura del dominio sin ruido.
Los métodos simples siguen un patrón: validan entradas, ejecutan la acción principal y retornan un resultado claro. Si necesitas condicionales complejos, considera extraer métodos privados o aplicar guard clauses. Además, documenta comportamientos excepcionales mediante pruebas unitarias, no con comentarios extensos.
Un buen indicador es la facilidad con la que otra persona del equipo puede leer el método y anticipar sus efectos secundarios. Si requiere diagramas para comprenderlo, probablemente deba refactorizarse.
Revisemos un caso común: un servicio que procesa pagos y terminó mezclando múltiples escenarios. La versión inicial es difícil de mantener porque contiene varias ramas, combinaciones de flags y código repetido.
// Versión compleja: demasiadas rutas en un mismo método
class PaymentService {
private final Gateway gateway;
private final Notifier notifier;
PaymentService(Gateway gateway, Notifier notifier) {
this.gateway = gateway;
this.notifier = notifier;
}
Receipt process(Map<String, Object> payload) {
String type = (String) payload.get("type");
if ("CARD".equals(type)) {
boolean saveCard = Boolean.TRUE.equals(payload.get("saveCard"));
CardData card = CardData.from(payload);
Result result = gateway.charge(card, (BigDecimal) payload.get("amount"));
if (!result.approved()) {
notifier.alertFailure(payload);
return Receipt.failure(result.message());
}
if (saveCard) {
gateway.storeCard(card, (String) payload.get("customerId"));
}
notifier.notifySuccess(payload);
return Receipt.success(result.authorization());
}
if ("WIRE".equals(type)) {
Result result = gateway.scheduleTransfer(payload);
notifier.notifyPending(payload);
return Receipt.pending(result.reference());
}
throw new IllegalArgumentException("Tipo de pago desconocido");
}
}
La refactorización aplica KISS separando comandos por tipo de pago y delegando responsabilidades en clases pequeñas. El método principal orquesta el flujo de manera lineal.
// Versión simple: flujo lineal con comandos especializados
final class PaymentService {
private final PaymentCommandFactory commandFactory;
private final PaymentNotifier notifier;
PaymentService(PaymentCommandFactory commandFactory,
PaymentNotifier notifier) {
this.commandFactory = commandFactory;
this.notifier = notifier;
}
Receipt process(PaymentRequest request) {
PaymentCommand command = commandFactory.createFor(request);
PaymentResult result = command.execute();
notifier.notify(result, request);
return result.toReceipt();
}
}
Las implementaciones concretas de PaymentCommand encapsulan detalles de tarjeta, transferencias u otros medios. Este diseño mantiene el método principal simple y facilita agregar nuevos métodos de pago sin alterar el flujo existente.
La simplicidad arquitectónica nace de elegir el estilo adecuado para el contexto real. Antes de dividir en microservicios, valida si un monolito modular satisface los requisitos. Si necesitas servicios distribuidos, define contratos ligeros y evita crear capas de orquestación innecesarias.
Documenta las decisiones de arquitectura en un registro accesible. Así, los motivos de cada elección quedan claros y evitas que surjan componentes paralelos con propósitos similares.
Mantener KISS es un ejercicio continuo: cuestiona cualquier capa adicional, valida su aporte real y elimina aquello que no contribuye al objetivo del producto. La simplicidad sostenida maximiza la velocidad de entrega y facilita la colaboración entre equipos.