Saltar a contenido

Regla 17: Código Limpio — Tipos Fuertes y Enums — ⚠️ Borrador

Premisa: Evitar validaciones por string mágicos

No validar estados, categorías, modos ni tipos de dominio con cadenas mágicas comparadas con equals, ==, o en switch sobre strings. Usar enums / tipos fuertes que encapsulen los valores válidos y la lógica de decisión. Un dominio cerrado (un conjunto finito y conocido de valores) debe modelarse como tipo, no como string suelto.

El anti-patrón se conoce como stringly-typed: las decisiones de negocio se toman comparando literales de string dispersos por el código, sin compilación que garantice su validez, sin autocompletado y sin refactorización segura.

tags: [obligatorio]

Estructura: Matriz de herramientas lint por lenguaje

Lenguaje Herramienta Detección del anti-patrón
Java Error Prone + Checkstyle StringEquality, StringLiteralEquals, illegalType
JavaScript/TypeScript ESLint no-string-literal, ban-types, reglas custom
Python ruff / pylint comparación de literales (magic-value), typing.Literal como mejora
Rust clippy comparaciones de strings/&str, match sobre strings

tags: [opcional]

Ejemplo: Antipatrón → Patrón correcto

Antipatrón (stringly-typed):

private static String normalizeCanvasMode(final String tool, final String mode) {
    if ("ETHERPAD".equals(tool)) {
        if ("INDEPENDENT".equals(mode) || "MODERATOR_ONLY".equals(mode)) return mode;
        return "COLLABORATIVE";
    }
    return "MODERATOR_ONLY".equals(mode) ? "MODERATOR_ONLY" : "INDEPENDENT";
}

Problemas: strings dispersos, sin validación de compilación, valores inválidos posibles, imposible refactorizar con seguridad.

Patrón correcto (enums + tipos fuertes):

enum Tool {
    ETHERPAD,
    OTHER
}

enum CanvasMode {
    INDEPENDENT,
    MODERATOR_ONLY,
    COLLABORATIVE
}

private static CanvasMode normalizeCanvasMode(final Tool tool, final CanvasMode mode) {
    if (tool == Tool.ETHERPAD) {
        return switch (mode) {
            case INDEPENDENT, MODERATOR_ONLY -> mode;
            case COLLABORATIVE -> CanvasMode.COLLABORATIVE;
        };
    }
    return mode == CanvasMode.MODERATOR_ONLY ? CanvasMode.MODERATOR_ONLY : CanvasMode.INDEPENDENT;
}

Ventajas: el compilador garantiza valores válidos, refactorización segura, autocompletado, y la lógica de transformación queda tipada.

Ejemplo Python (typing.Literal / Enum):

from enum import Enum

class Tool(Enum):
    ETHERPAD = "etherpad"
    OTHER = "other"

class CanvasMode(Enum):
    INDEPENDENT = "independent"
    MODERATOR_ONLY = "moderator_only"
    COLLABORATIVE = "collaborative"

def normalize_canvas_mode(tool: Tool, mode: CanvasMode) -> CanvasMode:
    if tool is Tool.ETHERPAD:
        if mode in (CanvasMode.INDEPENDENT, CanvasMode.MODERATOR_ONLY):
            return mode
        return CanvasMode.COLLABORATIVE
    return CanvasMode.MODERATOR_ONLY if mode is CanvasMode.MODERATOR_ONLY else CanvasMode.INDEPENDENT

tags: [obligatorio]

Comando: Lint para detectar stringly-typed

# Java
mvn verify -DskipTests -Derrorprone   # Error Prone (StringEquality, StringLiteralEquals)
mvn checkstyle:check                  # Checkstyle illegalType

# JavaScript/TypeScript
eslint src/ --rule 'no-string-literal: error'

# Python
uv run ruff check .                   # incluye detección de comparación de literales

# Rust
cargo clippy

Estos linters forman parte del gate make lint (regla 01) y del pre-commit (regla 10).

tags: [opcional]

Restriccion: Restricciones

  • Prohibido comparar con strings mágicos en lógica de decisión: "X".equals(y), y == "X", switch sobre String con literales de dominio.
  • Prohibido pasar strings sueltos entre capas cuando representan un dominio cerrado (estado, categoría, modo, tipo). Modelar con enum/tipo fuerte en la frontera de la capa.
  • Los enums encapsulan los valores válidos. Si hay lógica de transformación (normalización, mapeo), vive en el enum (método) o en una función tipada, nunca en cadenas dispersas.
  • Evitar null como valor mágico para representar "sin valor". Usar Optional<T> o un enum con valor explícito (p.ej. UNKNOWN, DEFAULT).
  • No introducir el anti-patrón en nuevos componentes aunque el código legacy lo tenga. Refactorizar progresivamente al tocar el módulo.
  • El lint (Error Prone/ESLint/ruff/clippy) debe ejecutarse en CI y en pre-commit para bloquear la regresión.

tags: [obligatorio]

Referencia: Referencias

tags: [obligatorio]

Plantilla: Plantillas

tags: [opcional]