Código limpio: Organiza tus clases

Código limpio: Organiza tus clases

En la última entrada realizamos una introducción a las pruebas automáticas. En esta entrada vamos a ver cómo organizar correctamente tus clases.

Esta es la undécima entrega de una serie en la que analizamos el libro clean code de Robert C Martin publicado en el año 2008. Si quieres puedes ir a ver el índice de la serie. También puedes ver la versión en video de esta entrada en nuestro canal de youtube.

Elige tu estilo

En mi opinión no tan importante que reglas utilices para organizar tus clases. Lo que sí es muy importante, es que elijas unas y te comprometas con ellas. También es muy importante que no seas dogmático y que estés dispuesto a añadir o modificar tus reglas si es que aparecen otras mejores.

No hay nada más frustrante que tratar de explicar a un desarrollador una forma mejor de hacer las cosas, y que no sea capaz de salir de sus trece, pero que tampoco sea capaz de argumentar un motivo.

Dentro de las reglas que elijas ten en cuenta que nada debe quedar al azar, si no que se note que todo está de una forma determinada por un motivo determinado y no simplemente por que ha quedado así.

Nuestro estilo

Nosotros utilizamos la guia de estilo oficial para symfony ya que es el framework con el que trabajamos. Además es muy sencillo configurar el editor para que las aplique automáticamente.

A continuación vamos a explicar el resto de reglas que utilizamos nosotros y que no son exactamente iguales a las que describe el autor en el libro. Pero que vamos a argumentarte los motivos que hemos tenido en cuenta para elegirlas.

Para ello vamos a analizar la clase Rectangle que ya conocemos de entradas anteriores.

Casi todo lo que vamos a ver son esos pequeños detalles que forman parte de nuestra política de no dejar nada al azar, y que probablemente no supongan una gran diferencia por sí sólos, pero que en conjunto y adoptadas por todo el equipo, nos permiten generar una base de código de calidad en todos nuestros proyectos.

Vamos a describir las reglas leyendo la clase de arriba a abajo.

Cabecera

La cabecera es normalmente un fragmento de código comentado que se pone encima de cada fichero. Debería indicar a que proyecto pertenece, que tipo de licencia aplica y que personas han intervenido en ella.

Lo más habitual es utilizar alguna herramienta para asegurar que todos los ficheros tienen la cabecera correcta y esta está actualizada a la última versión.

/*
 * This file is part of the clean code example package.
 *
 * Copyright (c) 2016-2020 Devtia Soluciones.
 * All rights reserved.
 *
 * @author Daniel González <daniel@devtia.com>
 */
final class Rectangle
{
    //..
}

La definición de la clase

Nosotros intentamos por defecto ser lo más restrictivos posible, y luego ir abriendo la visibilidad según sea necesario. Por eso siempre declaramos nuestras clases por defecto como final. Una palabra reservada que nos permite indicar que la clase no puede extenderse.

Las constantes de clase

Nosotros habitualmente separamos las constantes en clases independientes. Por ejemplo, si quisiéramos poner los colores como constantes, es mejor crear una clase diferente para contenerlos. De esta forma mejoramos la semántica del dominio.

final class Color
{
    const WHITE = 'FFFFFF';
    const BLACK = '000000';
}

Atributos

Como norma general declaramos todos atributos como private. Si fuera necesario cuando realizamos un diseño, algunos de estos los declaramos como protected y evitamos siempre los atributos public salvo cuando se trata de DTOs.

Si tenemos atributos de diferente visibilidad dentro de una clase los agrupamos según su visibilidad.

Dentro de cada grupo, ordenamos los atributos por orden de importancia. Los más importantes arriba. También tratamos de agruparlos por tipo, para que estén todas las fechas juntas, todos las colecciones juntas, ...

final class Rectangle
{
    /* @var Point */
    private $topLeft;

    /** @var Color */
    private $backgroundColor;

    /** @var Color */
    private $borderColor;

    /* @var float */
    private $border;

    /* @var float */
    private $height;

    /* @var float */
    private $width;

    //..
}

El constructor y el resto métodos mágicos

A continuación de los atributos escribimos el constructor de la clase. Una clase no debería encontrarse en una situación inconsistente. Por eso el constructor debería recibir todo aquello que la clase necesita para existir. De esta forma evitamos por ejemplo la existencia de un Rectangle cuya altura está sin definir.

final class Rectangle
{
    //..
    public function __construct(Point $topLeft, float $height, float $width)
    {
        $this->topLeft = $topLeft;
        $this->height = $height;
        $this->width = $width;
    }
    //..
}

Los metodos mágicos son métodos que están definidos para todas las clases en php, y que son invocados cuando ocurre una circustancia concreta. Nosotros los definimos por orden alfabético después del constructor.

Los métodos públicos

Nosotros ordenamos los métodos agrupándolos por visibilidad. Arriba del todos los métodos public luego todos los protected y finalmente todos los private.

El autor nos indica que se deberían ordenar según su importancia y mantener los métodos relacionados cerca entre sí. Nosotros creemos que esto podría tener sentido en el momento de publicar el libro, pero es una regla dificil de aplicar, cuando por ejemplo un mismo método se llama desde diferentes métodos, ¿junto a cual lo pones?

Los editores modernos nos permiten navegar con facilidad entre métodos aúnque no estén cercanos entre sí, asi que nosotros simplemente los ordenamos alfabeticamente y configuramos el editor para que haga esto automáticamente por nosotros.

El resto de métodos

Los métodos que no son public deberían declararse private a menos que exista una buena razón para ello. Los ordenamos también por orden alfabético y dejamos que el editor se encarge de esta tarea.

Bloques de código

Para explicar esto es necesario traer un ejemplo un poco más complejo. Veamos un ejemplo real de uno de nuestros proyectos.

final class ProposalController extends AbstractProposalController
{
    //..
    public function copyAction(Project $proposal)
    {
        $this->throwNotFoundExceptionIfNotIsPublicProposal($proposal);

        $copyManager = $this->get('app.service.project_copy_manager');
        $publicProposal = $copyManager->generatePublicProposal(
            $proposal,
            null,
            \AppBundle\Entity\Project\Type::PUBLIC_PROPOSAL_CLIENT_VERSION
        );

        $manager = $this->get('app.service.project_manager');
        $manager->update($publicProposal);

        $this->addFlash('success', 'proposal successfully updated');

        return $this->redirectToRoute('_public.proposal.view', ['hash' => $publicProposal->getHash()]);
    }
    //..
}

Si ves el ejemplo anterior puedes ver que cada concepto está separado por un salto de línea. Lo primero que hacemos es la validación o cláusulas de guarda, de las que hablaremos en otra entrada más adelante.

Lo siguiente que hacemos es obtener una copia de la propuesta a través del servicio correspondiente. Después actualizamos esa copia, mensaje de éxito y redirección a la nueva propuesta.

Cada concepto está separado por un salto de línea facilitando su entendimiento al lector.

Ni un salto de línea de más

El último detalle. Ya hemos visto donde sí debemos poner un salto de línea. En nuestra opinión no se deben añadir saltos de línea adicionales, porque dan la sensación de estar ahí por que sí, en lugar de porque tienen que estar.

¿Quieres ser una bestia del desarrollo de software?
¡Continúa con nosotros en YouTube!

Todas las semanas un nuevo vídeo sobre desarrollo de software en tu bandeja de entrada.

Tranquilo, no te vamos a enviar spam.