Lenguajes no tipados

Hay muchos defensores de los lenguajes no tipados (y tipado dinámico), @dhh creador de Ruby on Rails, es uno de ellos. Hoy quiero explicar el porqué otros pensamos que está equivocado.

Pongamos un ejemplo sencillo.

Tenemos una aplicación que devuelve por API recetas, para ello usaremos un DTO.

final readonly class RecipeDto
{
    public function __construct(
        $id,
        $name,
        $calories,
        $protein,
        $carbs,
        $fats,
        $ingredients
    )    
    {
    }
 }

Todo funciona correctamente, pero de pronto un día la app empieza a fallar. No hemos tocado nada, pero falla.

Resulta que un usuario ha puesto en proteínas un valor decimal (lo cual puede ser correcto, pero no usual). La app que consume el API si está tipada (la mayoría de las plataformas para móvil lo son), y al llegar un valor no esperado, pues falla.

Así que para solucionar este problema hacemos un redondeo en el API.

A la semana nos llaman diciendo que hay recetas en las que las calorías están mal. no se corresponden (las calorías se calculan con una formula en base a las proteínas, carbohidratos y grasas).

El redondeo hace que los datos no cuadren. Decidimos quitar calorías del DTO y calcularlo en cada petición, es un cálculo sencillo.

final readonly class RecipeDto
{
    public function __construct(
        $id,
        $name,
        $protein,
        $carbs,
        $fats,
        $ingredients
    )    
    {
    }
 }

Y… se lía pardisima, otra vez la aplicación bloqueada, parece que en $ingredientes, en lugar de llegar un array, está llegando un valor entero.

Parece que cuando se hizo el cambio, no se modificó todas las creaciones del dto:

//En lugar de hacer esto
return new RecipeDto($id, $name, $protein, $carbs, $fats, $ingredients);

//Se hace esto
return new RecipeDto($id, $name, $calories, $protein, $carbs, $fats, $ingredients);

Esto no falla por ejemplo en PHP, Javascript, etc…

En este punto, mucha gente puede pensar… a mi esto no me pasa por que tengo validaciones en el DTO, tengo validaciones al enviar en el API, y validaciones en el formulario, y validaciones everywhere, y muchos tests, tests de todo tipo, test de mutación, tests con todas las opciones.

Vamos ahora a la versión tipada y estricta, y decidid que es mejor.

final readonly class RecipeDto
{
    public function __construct(
        string $id,
        string $name,
        int $calories,
        int $protein,
        int $carbs,
        int $fats,
        array $ingredients
    )    
    {
    }
 }
 
 // y para consstruir el DTO
 return new RecipeDto(
     id: $id,
     name: $name,
     calories: $calories,
     protein: $protein,
     carbs: $carbs,
     fats: $fats,
     ingredients: $ingredients);

Con esta forma de trabajar, los errores son mínimos, las validaciones casi no son necesarias, los refactoring son mucho más sencillo, y si usas un analizador de código estático como PHPStan, te avisa de cualquier incongruencia.

Pero aún se puede mejorar, aunque esta versión no está ampliamente aceptada

final readonly class RecipeDto
{
    /**
    * @param array<IngredintDto> $ingredients
    */
    public function __construct(
        RecipeId $id,
        string $name,
        int $calories,
        RecipeMacroInfo $macroInfo,
        array $ingredients
    )    
    {
    }
 }

En esta versión usamos ValueObjects, el error con esto es mínimo, es muy, muy complicado que salten errores inesperados.

Nosotros usamos Sentry para registrar los errores, y prácticamente no tenemos errores en producción.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *