Regroupez ce qui partage un sens commun

Les lignes de code d'une méthode forment naturellement des blocs logiques pouvant partager un sens commun, regardons le code suivant :

class OrderProcessor 
{
    public function process(Order $order): void
    {
        if (! $order->isValid()) {
            throw new Exception('Commande non valide');
        }

        $shippingCost = $this->calculateShippingCost($order->getWeight(), $order->getDestination());

        $discount = 0;

        if ($order->hasDiscount()) {
            $discount = $order->getDiscount();
        }

        $total = $order->getTotal() + $shippingCost - $discount;
        
        $order->setTotal($total);
        $order->setStatus('processed');

        $message = "Merci pour votre commande #" . $order->getId();

        mail($order->getCustomerEmail(), 'Confirmation de votre commande', $message);
    }
}

Actuellement, cette méthode n’offre pas de découpage et mélange un ensemble de besoins. Par exemple, la variable $shippingCost n’est pas directement liée à $message : les deux variables ont des objectifs différents.

Ces objectifs peuvent facilement être découpés et encapsulés dans des méthodes distinctes afin de faciliter la compréhension et la lecture en diagonale de votre code.

La lecture en diagonale est la capacité d'une méthode à être instinctivement claire sur ses intentions

À travers ces méthodes supplémentaires, vous gagnerez en consistance et en lisibilité grâce à un nommage approprié et révélateur des intentions de chacun des blocs.

Ce découpage permettra également de cloisonner les différents contextes de votre code en isolant les lignes répondant à une intention de celles répondant à une implémentation :

class OrderProcessor 
{
    public function process(Order $order): void
    {
        $this->validateOrder($order)
        $this->calculateAndUpdateOrder($order);

        $this->sendConfirmationEmail($order);
    }

    private function calculateAndUpdateOrder(Order $order): void
    {
        $shippingCost = $this->calculateShippingCost($order);

        $discount = $this->applyDiscount($order);

        $total = $this->calculateTotal($order, $shippingCost, $discount);

        $this->updateOrderStatus($order, $total);
    }

    // [...]
}

Il n'existe pas de taille idéale pour une méthode, mais multiplier les lignes au sein d'une même méthode revient à prendre le risque de multiplier ses responsabilités et ses niveaux d'abstraction : une méthode ne devrait faire qu'une seule et unique chose.

L'ajout d'une méthode supplémentaire au sein d'une classe ne doit pas vous effrayer car cela répond à des enjeux de découpage et de respect des différents contextes.

Trop de méthodes au sein d'une même classe sera révélateur d'un problème de conception sous-jacent. Les méthodes ne sont pas problématiques en soi, mais leur accumulation dans une classe est probablement un signe que celle-ci contient trop de responsabilités.

Dans notre exemple, envoyer un mail n'est clairement pas une responsabilité technique de OrderProcessor, cela devrait être encapsulé dans un service dédié :

class OrderProcessor 
{
    public function __construct(
        private OrderMailer $mailer,
    ) {}

    // [...]
}

Grace à ce service, vous réduirez les responsabilités et le nombre de méthodes au sein de la classe : Inquiétez-vous des responsabilités de vos classes plutôt que du nombre de méthodes qu'elles contiennent.

If code requires effort to understand, extract it into a function named after its purpose.