* Claudio Curci  /  Php developer

3. lezione: dal codice rigido al design flessibile


Tanti anni fa, quando ero agli inizi della mia carriera da programmatore, lessi su una rivista eccezionale, DEV, un'intervista a Bjarne Stroustrup. Mi colpì una frase:

            Pensate i programmi a librerie e innalzate il livello d'astrazione
        
Onestamente, non la trovavo molto comprensibile visto che all'epoca gli strumenti a disposizione dei programmatori web erano quasi procedurali, tra javascript e php3. Oggi le cose stanno diversamente e quella frase è più attuale che mai, anche in ambito PHP.
Del resto quando parla Stroustrup è difficile che il tempo non gli dia ragione.
Veniamo alla nostra vita di tutti i giorni, scandita da decine di righe di questo tipo
            if($metodoPagamento == 'bonifico')
            {
                $commissione = 1.50;
            }
            elseif($country == 'paypal')
            {
                $commissione = 0.75;
            }
            else
            {
                $commissione = 0;
            }
        
oppure
            switch($metodoPagamento)
            {
                case 'bonifico':
                    $commissione = 1.50;
                break;
                case 'paypal':
                    $commissione = 0.75;
                break;
                default:
                    $commissione = 0;
                break;
        
Tutto funzionale ma anche rigido. Ogni nuovo possibile valore equivale ad un'altra riga di codice e non sono infrequenti errori sintattici, dovuti ad esempio a copia/incolla maldestri.

Match Expression

Finalmente PHP ci regala un costrutto moderno e più sintetico:

                $commissione = match ($metodoPagamento) {
                    'bonifico' => 1.50,
                    'paypal' => 0.75,
                    default => 0,
                };
            
Default è una parola chiave (non va messo tra apici) e non è obbligatorio. Se però viene omesso e la funzione riscontra un valore non presente nell'elenco, ritorna un'eccezione di tipo UnhandledMatchError.
Alla fine non è che questo costrutto risolva tutti i problemi (ogni nuovo possibile valore è comunque un'aggiunta di codice) ma la sintassi è più robusta ed elegante.

Polimorfismo avanzato

Proviamo a risolvere il problema degli if/switch eseguendo un salto concettuale e spostandoci sugli oggetti.
Iniziamo dichiarando il comportamento necessario attraverso un'interfaccia, che definisce il contratto del comportamento:

                interface MetodoPagamento
                {
                    public function getCommissione(): float;
                }
            
Implementiamo con le classi:
                class Bonifico implements MetodoPagamento
                {
                    public function getCommissione(): float
                    {
                        return 1.50;
                    }
                }
            
                class Paypal implements MetodoPagamento
                {
                    public function getCommissione(): float
                    {
                        return 0.75;
                    }
                }
            
e finalmente utilizziamo la nostra interfaccia:
                function checkout(MetodoPagamento $metodoPagamento)
                {
                    return $metodoPagamento->getCommissione();
                }
                            
            
Siamo cosi "liberi" da if e switch infiniti, e l'aggiunta successiva di un nuovo metodo di pagamento non ci costringerà a cambiare una sola virgola di codice, solo ad aggiungere la nuova classe.
Chiaramente vale sempre la regola del buon senso. Qui si tratta sempre di oggetti, mentre costrutti come IF e SWITCH sono nativi del linguaggio ed hanno un impatto prestazionale quasi nullo. Ben vengano gli oggetti, ma se la situazione è quella di gestire un if su condizioni che, a priori, sappiamo che non cambieranno mai... teniamoci pure i nostri if.

SOLID

Mettendo assieme quanto appreso in queste prime tre lezioni, possiamo guardare al codice PHP con criteri diversi.
La stringa "SOLID", facilmente memorizzabile, ci ricorda dei punti chiave con cui valutare le nostre soluzioni.
S = Single Responsibility Principle. Una classe deve avere un solo motivo di cambiamento. Se ad esempio creiamo una classe "User" e dentro mettiamo il metodo che invia le email e quello che stampa l'ordine in pdf, la classe è esposta con il tempo a più modifiche. Potrebbe essere necessario cambiare il template html delle email, cosi come quello del pdf oppure la libreria usata per l'invio dei messaggi. Questo è un anti-pattern. E' importante quindi separare le varie funzionalità, cosi che esista una classe apposita per l'email ed un'altra per i pdf. La modifica dei formati delle email o dei pdf, interesserà solo la relativa classe.
O = Open/Closed Principle. Il codice deve essere estendibile senza modifiche: è qui che gli if/switch vengono meno.
L = Liskov Substitution Principle. Un oggetto deve poter essere sostituito da un suo derivato senza rompere il sistema.
I = Interface Segregation Principle. Meglio interfacce piccole e specifiche. Il che ricalca il principio visto alla lettera "S".
D = Dependency Inversion Principle. Dipendere da astrazioni, non da implementazioni concrete.
Quest'ultimo principio è spesso frainteso. Ecco un esempio pratico.

                class PaymentService
                {
                    private StripeGateway $stripe;

                    public function __construct()
                    {
                        $this->stripe = new StripeGateway();
                    }
                }
            
Questo approccio è sbagliato visto che lega indissolubilmente Payment a Stripe, in una sortad di dipendenza hard-coded. La soluzione corretta è questa
                interface PaymentGateway
                {
                    public function pay(float $amount): void;
                }
            
                class StripeGateway implements PaymentGateway
                {
                    public function pay(float $amount): void {}
                }
            
                class PaymentService
                {
                    public function __construct(
                        private PaymentGateway $gateway
                    ) {}
                }
                $service = new PaymentService(new StripeGateway());
            
A questo punto Payment potrà usare Stripe come qualsiasi altro gateway, compreso uno fittizio per i test. Si parla di "inversion" perchè da un approccio di questo tipo
                business → dettagli (DB, mail, API)
            
si passa a
                business → astrazione ← dettagli
            

Dal 1997, il Php a Roma!

Claudio Curci
Da quasi trent'anni mi dedico alla programmazione, con oltre 20 anni di esperienza come freelance. Credo in un approccio al lavoro in cui la competenza si sposa con la serenità, dove le urgenze sono vizi, non virtù. Mi impegno a supportare i clienti con risposte chiare e soluzioni concrete, evitando inutili tecnicismi. "La vera efficienza si trova nella serenità." – Henry David Thoreau
Infocurci Questo sito non utilizza cookie, non mostra nessuna pubblicità e non profila nulla. Navigate serenamente, siete i benvenuti.

Ambienti / piattaforme di sviluppo

Amazon Booking.com CodeIgniter Joomla Magento Moodle Kelkoo Kigo PayPal Symfony Wordpress Airbnb FormaLms Laravel Prestashop Shopify Whatsapp