Scrivere del codice pulito non è così comune come possiamo pensare.
In passato mi sono trovato a scrivere codice di cui andavo fiero. È solo rileggendolo dopo tempo che mi sono accorto di quanto fosse illeggibile.
Credevo che scrivere codice in grado di astrarre task complesse, fosse la massima espressione di un programmatore competente.
Mi sbagliavo.
Il mondo è pieno di programmatori che creano applicazioni pazzesche in pochi giorni (ad esempio Alyssa X).
La vera differenza non sta nel “far funzionare l’applicazione” ma nel codice scritto bene: del codice pulito.
Come si riconosce il “codice pulito”?
Esistono un sacco di libri che parlano di codice pulito e di codice NON pulito e ognuno di loro da la propria interpretazione.
Per capire però, credo sia meglio iniziare definendo cos’è il codice NON pulito.
L’immagine seguente mostra la differenza tra del buono e del cattivo codice:

La metrica scelta sono i WTFs/Minute (What The F**ks per Minutes) ovvero quante volte al minuto “imprechiamo” mentre leggiamo il codice.
Quest’interpretazione comica ci da un’idea di cosa voglia dire scrivere del buon codice, ma se dovessimo dirlo più ehm… professionalmente?
Con codice pulito si intende del codice facilmente leggibile e facilmente modificabile
Esempio in Laravel
Supponiamo di avere il seguente controller:
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Models\Certificate;
use Illuminate\Http\Request;
use Illuminate\View\View;
class CertificateController extends Controller
{
/**
* Display a listing of the resource.
*
* @return View
*/
public function index(Certificate $certificate) : View
{
$user = auth()->user();
$certificates = Certificate::all();
if ($user->is_admin)
{
return view("certificates.list",
compact("certificates"));
}
$filteredCertificates = [];
foreach ($certificates as $certificate)
{
$canContinue = false;
foreach ($certificate->yards as $yard)
{
if ($user->yard_id === $yard->id)
{
$canContinue = true;
break;
}
}
if (!$canContinue)
continue;
$filteredCertificates[] = $certificate;
}
$certificates = $filteredCertificates;
return view("certificates.list",
compact("certificates"));
}
}
Cosa fa il nostro controller?
Analizziamo:
- Si chiama CertificatesController dunque gestirà dei Certificati
- Ha un metodo index quindi (secondo gli standard di laravel) renderizzerà una pagina con la lista dei certificati
Abbastanza semplice a primo avviso vero?! Quindi questo è un codice leggibile? È un codice facilmente modificalbile?
ASSOLUTAMENTE NO!
Ci sono infatti una serie di errori in questo codice che lo rendono un codice sporco, vediamo cosa:
- Il metodo index è troppo lungo: 35 righe!
- Una lunghezza ideale si aggira intorno alle 10/15 righe
- Alcune scuole di pensiero dicono che un metodo deve essere visualizzabile nella sua interezza senza ricorrere a scroll alcuno (gli schermi verticali non contano!)
- Nel metodo ci sono 2 punti di uscita (return)
- È buona pratica avere 1 solo punto di uscita in fondo al metodo
- Una casistica nel quale è ammesso avere dei return è all’inizio del metodo in combo con un if che controlla i “permessi” di accesso al metodo, ma non è questo il caso
- Il metodo index si occupa di troppe cose: verificare se l’utente è admin, recuperare tutti i certificati se è admin, recuperare solo i certificati che hanno una relazione con il cantiere (yard) di appartenenza dell’utente.
- Ci sono una serie di foreach e if annidati che creano difficoltà di lettura
- Tutta questa parte centrale può essere semplificata grazie all’utilizzo della programmazione funzionale
Refactoring #1: snelliamo il metodo index
Vediamo adesso una possibile soluzione per snellire il codice di cui sopra:
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Models\Certificate;
use Illuminate\Http\Request;
use Illuminate\View\View;
class CertificateController extends Controller
{
/**
* Display a listing of the resource.
*
* @return View
*/
public function index(Certificate $certificate) : View
{
$user = auth()->user();
$certificates = Certificate::all();
if (!$user->is_admin)
{
$certificates = array_filter($certificates,
fn($certificate) => $certificate->yards->contains($user->yard));
}
return view("certificates.list",
compact("certificates"));
}
}
Abbiamo fatto un po’ di refactoring e il codice adesso risulta più snello e leggibile.
Vediamo cosa è stato fatto:
- Abbiamo diminuito il numero di righe: soltano 11 righe!
- Soltanto un punto di uscita in fondo al metodo!
- Abbiamo rimosso gli if e foreach annidati e abbiamo un solo if!
Ottimo, siamo stati davvero bravi ma… si c’è un ma!
Il metodo index si occupa ancora di troppe cose ovvero recuperare i certificati in base al tipo di utente e visualizzarli.
Inoltre recuperare i certificati in base alla tipologia dell’utente potrebbe essere utile anche ad altre classi o in altri metodi: forse è meglio centralizzarlo in un punto raggiungibile a tutto il codice.
Vediamo come migliorare ancora.
Refactoring #2: Dependency Injection
Il prossimo step per pulire il nostro codice è quello di utilizzare la Dependency Injection (DI) per injectare il nostro “servizio” che gestirà i certificati.
La prima cosa che vogliamo fare è creare un’interfaccia per rappresentare il “servizio” che ci ritornerà tutti i certificati.
Laravel ci permette di utilizzare la DI direttamente con le classi finali ma per principi SOLID non vogliamo dipendere dalle implementazioni ma, bensì, dalle interfacce (Dependency Inversion): questo ci permetterà in futuro di sostituire il servizio con uno che implementa la stessa interfaccia senza curarci di andare a sostituire la nuova implementazione ovunque sia stata utilizzata.
Creiamo un Contract
Per convenzione, Laravel chiama le interfacce “Contracts” quindi creiamo una cartella app/Contracts se già non esiste ed aggiungiamo la nostra classe:
<?php
declare(strict_types=1);
namespace App\Contracts;
use App\Models\Certificate;
interface CertificateContract
{
/**
* Returns all the certificates the user has access to.
* An admin has access to all the certificate,
* any other user only to those
*
* @return Certificate[]
*/
function getAccessibleCertificates() : iterable;
}
Implementiamo il Contract con un Service
Adesso è il turno dell’implementazione.
Se già non esiste, creiamo la cartella app/Services e posizioniamoci il nostro Service:
<?php
declare(strict_types=1);
namespace App\Services;
use App\Contracts\CertificateContract;
use App\Models\Certificate;
class CertificateService implements CertificateContract
{
/**
* Returns all the certificates the user has access to.
* An admin has access to all the certificate,
* any other user only to those
*
* @return Certificate[]
*/
function getAccessibleCertificates() : iterable
{
$user = auth()->user();
$certificates = Certificate::all();
if (!$user->is_admin)
$certificates = array_filter($certificates,
fn($certificate) => $certificate->yards->contains($user->yard));
return $certificates;
}
}
Indichiamo alla nostra applicazione come risolvere il nostro Contract
Perfetto! Adesso abbiamo il nostro servizio pronto ad essere utilizzato ovunque sia utile!
Andiamo a registrarlo perché sia injectabile da Laravel ovunque sia necessario.
Per fare ciò, creeremo un ServiceProvider all’interno della cartella app/Providers:
<?php
declare(strict_types=1);
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Contracts\CertificateContract;
use App\Services\CertificateService;
class CertificateServiceProvider extends ServiceProvider
{
/**
* Register the application services.
*
* @return void
*/
public function register() : void
{
$this->app->bind(CertificateContract::class, CertificateService::class);
}
}
La funzione bind dell’oggetto app altri non fa che istruire la nostra applicazione che ognidove una classe abbia bisogno di un CertificateContract, un’istanza di CertificateService verrà passata.
E per finire ecco il nostro controller nuovo di zecca!
Ok, ricapitolando abbiamo:
- Creato la nostra interfaccia
- Creato la sua implementazione
- Istruito la nostra applicazione che ogni richiesta di CertificateContract deve essere soddisfatta passando un CerficateService
cos’altro manca dunque?
Beh, manca ancora il nostro controller!
Vediamo come si presenterà adesso:
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Models\Certificate;
use App\Contracts\CertificateContract;
use Illuminate\Http\Request;
use Illuminate\View\View;
class CertificateController extends Controller
{
/**
* Display a listing of the resource.
*
* @return View
*/
public function index(CertificateContract $certificate) : View
{
$certificates = $certificate->getAccessibleCertificates();
return view("certificates.list",
compact("certificates"));
}
}
Conclusioni
E questo è tutto!
Questo è codice pulito, leggibile e mantanibile/modificabile, che dite?!
Il metodo index è molto più corto, soltanto 4 linee, e si occupa di una cosa soltanto: renderizzare i certificati!
Qualcuno potrebbe pensare che “per diminuire le righe del Controller abbiamo creato tanti file e tante più righe” e come dargli torto!
L’obiettivo però non sono le righe totali a livello di progetto ma le righe totali di un metodo e di una classe e la loro leggibilità!
Semplificando il discorso, sono meglio 5 classi da 30 righe l’una (150 righe) che 1 classe da 100 righe!
Inoltre, creando altre classi abbiamo reso disponibile e riutilizzabile parte del codice creato andando ad evitare futuri copia incolla.
Se quest’articolo vi è piaciuto e volete approfondire di più, vi consiglio di studiare i principi SOLID.