Leccion 64 de 75 15 min de lectura

Codigo Limpio

El codigo limpio es codigo que es facil de leer, entender y modificar. Un buen programador no solo escribe codigo que funciona, sino codigo que otros (y tu mismo en el futuro) pueden mantener facilmente.

Nombres significativos

Los nombres son la forma mas importante de comunicar la intencion de tu codigo. Un buen nombre elimina la necesidad de comentarios.

Variables descriptivas

PHP
<?php

declare(strict_types=1);

// MAL: nombres crípticos
$d = 30;
$u = getUsuarios();
$t = 0;
foreach ($u as $x) {
    if ($x['a'] > $d) {
        $t++;
    }
}

// BIEN: nombres que revelan intencion
$diasDeInactividad = 30;
$usuarios = getUsuarios();
$usuariosInactivos = 0;
foreach ($usuarios as $usuario) {
    if ($usuario['diasSinLogin'] > $diasDeInactividad) {
        $usuariosInactivos++;
    }
}

Funciones con nombres de accion

PHP
<?php

declare(strict_types=1);

// MAL: nombres vagos
function procesar(array $datos): void { }
function hacer(string $cosa): void { }
function data(): array { }

// BIEN: verbos que describen la accion
function enviarEmailBienvenida(Usuario $usuario): void { }
function calcularDescuento(Pedido $pedido): float { }
function obtenerUsuariosActivos(): array { }

Booleanos como preguntas

PHP
<?php

declare(strict_types=1);

// MAL
$flag = true;
$status = false;

// BIEN: se leen como preguntas
$estaActivo = true;
$tienePermiso = false;
$puedeEditar = $usuario->esAdmin();

// En metodos
class Usuario
{
    public function esAdmin(): bool { }
    public function tieneAcceso(string $recurso): bool { }
    public function puedePublicar(): bool { }
}

Funciones pequenas y enfocadas

Una funcion debe hacer una sola cosa y hacerla bien. Si tu funcion hace varias cosas, dividela en funciones más pequeñas.

PHP
<?php

declare(strict_types=1);

// MAL: funcion que hace demasiadas cosas
function procesarPedido(array $datos): void
{
    // Validar datos
    if (empty($datos['email']) || !filter_var($datos['email'], FILTER_VALIDATE_EMAIL)) {
        throw new Exception('Email invalido');
    }
    if (empty($datos['productos'])) {
        throw new Exception('No hay productos');
    }

    // Calcular total
    $total = 0;
    foreach ($datos['productos'] as $producto) {
        $total += $producto['precio'] * $producto['cantidad'];
    }

    // Aplicar descuento
    if ($total > 100) {
        $total *= 0.9;
    }

    // Guardar en base de datos
    $db = new PDO('...');
    $stmt = $db->prepare('INSERT INTO pedidos...');
    $stmt->execute([...]);

    // Enviar email
    mail($datos['email'], 'Pedido confirmado', '...');
}
PHP
<?php

declare(strict_types=1);

// BIEN: funciones pequenas y enfocadas
class ProcesadorPedidos
{
    public function procesar(array $datos): void
    {
        $this->validarDatos($datos);
        $total = $this->calcularTotal($datos['productos']);
        $totalConDescuento = $this->aplicarDescuento($total);
        $this->guardarPedido($datos, $totalConDescuento);
        $this->enviarConfirmacion($datos['email']);
    }

    private function validarDatos(array $datos): void
    {
        if (!$this->esEmailValido($datos['email'] ?? '')) {
            throw new InvalidArgumentException('Email invalido');
        }
        if (empty($datos['productos'])) {
            throw new InvalidArgumentException('No hay productos');
        }
    }

    private function esEmailValido(string $email): bool
    {
        return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
    }

    private function calcularTotal(array $productos): float
    {
        return array_reduce(
            $productos,
            fn(float $total, array $p) => $total + ($p['precio'] * $p['cantidad']),
            0.0
        );
    }

    private function aplicarDescuento(float $total): float
    {
        return $total > 100 ? $total * 0.9 : $total;
    }

    private function guardarPedido(array $datos, float $total): void { }
    private function enviarConfirmacion(string $email): void { }
}

Evitar la anidacion excesiva

El codigo muy anidado es dificil de seguir. Usa early returns para reducir niveles de indentacion.

PHP
<?php

declare(strict_types=1);

// MAL: piramide de la muerte
function procesarUsuario(?array $usuario): string
{
    if ($usuario !== null) {
        if (isset($usuario['activo']) && $usuario['activo']) {
            if (isset($usuario['email'])) {
                if (filter_var($usuario['email'], FILTER_VALIDATE_EMAIL)) {
                    return "Usuario valido: {$usuario['email']}";
                } else {
                    return 'Email invalido';
                }
            } else {
                return 'Sin email';
            }
        } else {
            return 'Usuario inactivo';
        }
    } else {
        return 'Usuario no encontrado';
    }
}

// BIEN: early returns (clausulas de guarda)
function procesarUsuario(?array $usuario): string
{
    if ($usuario === null) {
        return 'Usuario no encontrado';
    }

    if (!($usuario['activo'] ?? false)) {
        return 'Usuario inactivo';
    }

    if (!isset($usuario['email'])) {
        return 'Sin email';
    }

    if (!filter_var($usuario['email'], FILTER_VALIDATE_EMAIL)) {
        return 'Email invalido';
    }

    return "Usuario valido: {$usuario['email']}";
}

Evitar números mágicos

Los numeros sin contexto en el codigo son dificiles de entender. Usa constantes con nombres descriptivos.

PHP
<?php

declare(strict_types=1);

// MAL: que significa 86400? y 3? y 0.21?
if (time() - $ultimoAcceso > 86400) {
    bloquearCuenta();
}

if ($intentosFallidos > 3) {
    bloquearCuenta();
}

$precioFinal = $precio * 1.21;

// BIEN: constantes descriptivas
class Configuracion
{
    public const SEGUNDOS_POR_DIA = 86400;
    public const MAX_INTENTOS_LOGIN = 3;
    public const IVA = 0.21;
}

if (time() - $ultimoAcceso > Configuracion::SEGUNDOS_POR_DIA) {
    bloquearCuenta();
}

if ($intentosFallidos > Configuracion::MAX_INTENTOS_LOGIN) {
    bloquearCuenta();
}

$precioFinal = $precio * (1 + Configuracion::IVA);

Comentarios: cuando y como

El mejor comentario es el codigo que no necesita comentarios. Usa comentarios solo cuando el código no puede explicarse por si mismo.

PHP
<?php

declare(strict_types=1);

// MAL: comentarios obvios o redundantes
// Incrementar contador
$contador++;

// Obtener usuarios de la base de datos
$usuarios = $this->obtenerUsuarios();

// Si el usuario es admin
if ($usuario->esAdmin()) {
    // Dar acceso total
    $usuario->darAccesoTotal();
}

// BIEN: sin comentarios innecesarios
$contador++;
$usuarios = $this->obtenerUsuarios();

if ($usuario->esAdmin()) {
    $usuario->darAccesoTotal();
}

Comentarios utiles son aquellos que explican el por que, no el que:

PHP
<?php

declare(strict_types=1);

// BIEN: explica decisiones de negocio o tecnicas no obvias

// Limite impuesto por la API de pagos externa
public const MAX_MONTO_TRANSACCION = 10000;

// Usamos sleep porque la API tiene rate limiting de 1 req/seg
sleep(1);

// Formato requerido por el sistema legacy de contabilidad
$fecha = $pedido->fecha->format('Ymd');

// TODO: Refactorizar cuando migremos a PostgreSQL
$query = "SELECT * FROM usuarios WHERE BINARY nombre = ?";

DRY: No te repitas

Si ves codigo duplicado, probablemente puedas extraerlo en una funcion o clase reutilizable.

PHP
<?php

declare(strict_types=1);

// MAL: logica duplicada
function crearUsuario(array $datos): void
{
    if (empty($datos['email']) || !filter_var($datos['email'], FILTER_VALIDATE_EMAIL)) {
        throw new InvalidArgumentException('Email invalido');
    }
    // ...
}

function actualizarEmail(int $id, string $email): void
{
    if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
        throw new InvalidArgumentException('Email invalido');
    }
    // ...
}

// BIEN: logica extraida
function validarEmail(string $email): void
{
    if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
        throw new InvalidArgumentException('Email invalido');
    }
}

function crearUsuario(array $datos): void
{
    validarEmail($datos['email'] ?? '');
    // ...
}

function actualizarEmail(int $id, string $email): void
{
    validarEmail($email);
    // ...
}

Ejercicios

Ejercicio 1: Refactorizar nombres

Mejora los nombres en el siguiente código:

PHP
<?php

declare(strict_types=1);

function calc(array $arr): float
{
    $t = 0;
    foreach ($arr as $i) {
        $t += $i['p'] * $i['q'];
    }
    if ($t > 100) {
        $t = $t * 0.9;
    }
    return $t;
}
Ver solucion
PHP
<?php

declare(strict_types=1);

function calcularTotalCarrito(array $productos): float
{
    $total = 0.0;

    foreach ($productos as $producto) {
        $total += $producto['precio'] * $producto['cantidad'];
    }

    $minimoParaDescuento = 100;
    $porcentajeDescuento = 0.9;

    if ($total > $minimoParaDescuento) {
        $total *= $porcentajeDescuento;
    }

    return $total;
}

Ejercicio 2: Eliminar anidacion

Refactoriza este código usando early returns:

PHP
<?php

declare(strict_types=1);

function puedeComprar(array $usuario, array $producto): bool
{
    if ($usuario['activo']) {
        if ($usuario['verificado']) {
            if ($producto['stock'] > 0) {
                if ($usuario['saldo'] >= $producto['precio']) {
                    return true;
                }
            }
        }
    }
    return false;
}
Ver solucion
PHP
<?php

declare(strict_types=1);

function puedeComprar(array $usuario, array $producto): bool
{
    if (!$usuario['activo']) {
        return false;
    }

    if (!$usuario['verificado']) {
        return false;
    }

    if ($producto['stock'] <= 0) {
        return false;
    }

    if ($usuario['saldo'] < $producto['precio']) {
        return false;
    }

    return true;
}

Ejercicio 3: Extraer funcion

Divide esta función larga en funciones más pequeñas:

PHP
<?php

declare(strict_types=1);

function registrarUsuario(string $nombre, string $email, string $password): array
{
    // Validar nombre
    $nombre = trim($nombre);
    if (strlen($nombre) < 2) {
        throw new InvalidArgumentException('Nombre muy corto');
    }

    // Validar email
    $email = strtolower(trim($email));
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        throw new InvalidArgumentException('Email invalido');
    }

    // Validar password
    if (strlen($password) < 8) {
        throw new InvalidArgumentException('Password muy corta');
    }

    // Crear usuario
    return [
        'nombre' => $nombre,
        'email' => $email,
        'password' => password_hash($password, PASSWORD_DEFAULT),
        'creado' => date('Y-m-d H:i:s'),
    ];
}
Ver solucion
PHP
<?php

declare(strict_types=1);

function validarNombre(string $nombre): string
{
    $nombre = trim($nombre);

    if (strlen($nombre) < 2) {
        throw new InvalidArgumentException('Nombre muy corto');
    }

    return $nombre;
}

function validarEmail(string $email): string
{
    $email = strtolower(trim($email));

    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        throw new InvalidArgumentException('Email invalido');
    }

    return $email;
}

function validarPassword(string $password): void
{
    if (strlen($password) < 8) {
        throw new InvalidArgumentException('Password muy corta');
    }
}

function registrarUsuario(string $nombre, string $email, string $password): array
{
    $nombreValidado = validarNombre($nombre);
    $emailValidado = validarEmail($email);
    validarPassword($password);

    return [
        'nombre' => $nombreValidado,
        'email' => $emailValidado,
        'password' => password_hash($password, PASSWORD_DEFAULT),
        'creado' => date('Y-m-d H:i:s'),
    ];
}

Te está gustando el curso?

Tenemos cursos premium con proyectos reales y soporte personalizado.

Descubrir cursos premium