Clean code – funzioni?

Continuiamo a parlare di clean code. Questa volta affronteremo il tema delle funzioni. Uncle Bob le definisce cosi: “Functions are the first line of organization in any program“. Cerchiamo di capire come poterle scrivere ed utilizzare al meglio.

Small!!
Le funzioni devo essere molto piccole. Per piccole intendo che devono avere pochissime righe di codice. Una volta scritte, dobbiamo sempre riflettere se è possibile spezzarle ulteriormente. Se la nostra funzione fa più di una cosa, allora sarà possibile spezzarla. Facciamolo senza nessuna paura. La prima regola che Bob ci dice è : le funzioni dovrebbero essere piccole. La seconda è: le funzioni dovrebbero essere ancora più piccole e cosi via… La leggibilità e la comprensibilità del codice aumenterà notevolmente. Sarà molto più facile da testare e le dipendenze saranno più facile da controllare.

Do One Thing!!
Come accennato sopra, la nostra funzione dovrebbe fare solamente una cosa e dovrebbe farla bene . Sentiamo spesso questa frase.. ma in realtà che significa? come facciamo a sapere se la funzione sta realmente facendo una sola cosa ? La risposta sta nel livello di astrazione degli statement della funzione stessa. Una funzione che ha un solo livello di astrazione (cioè tutti gli statement sono allo stesso livello) fa una cosa sola! Se analizzando la nostra funzione vediamo che potremmo raccogliere degli statement in un’altra funzione, allora la nostra funzione ha due livelli di astrazione. Ad esempio:


$orderForm =  $orderView->getHtml();
return $orderForm.”<p><a href=’…’>Terms of sale</a></p>”

è una funzione che ha due livelli di astrazione, il primo più alto rispetto al secondo che scende nel dettaglio. Ora questo esempio è stupido, però rende bene il concetto di livello di astrazione. Un altro esempio chiaro è quello di inserire in una funzione sia statement che fanno query che statement che prendono i dati ottenuti e li visualizzano.

Quindi per concludere : se una funzione esegue solo statement che sono un livello di astrazione sotto il livello indicato dal nome della funzione, allora la funzione sta facendo solamente una cosa.
Switch Statements
Se una funzione contiene una clausola switch difficilmente farà una cosa sola  (per sua natura lo switch implica la possibilità di fare N cose). Ci sono situazioni nelle quali non è possibile non utilizzare uno switch e proprio in queste situazioni occorre essere sicuri di   utilizzarlo correttamente “seppellendolo” in una classe di livello di astrazione minore. Il polimorfismo ci viene incontro!

Supponiamo di avere:

function calculatePay ($employee)   {
switch($employee->type) {

        case “commissioned”:
             return calculateCommissionedPay($employee);
        break;

        case “hourly”:
             return calculateHourlyPay($employee); 
        break; 

        case “salaried”: 
             return calculateSalariedPay($employee); 
        break;
}

Come vediamo la funzione è grande e se dovremo aggiungere un nuovo tipo di Employee, la funzione crescerà ulteriormente. Vengono violati i principi di SRP e di OCP (rispettivamente Single Responsability Principle e Open/Closed Principle). La soluzione al nostro problema sta nell’utilizzo dell’ Abstract Factory. Questo pattern utilizza la clausola switch  per creare istanze di oggetti che sono derivati di Employee. La classe Employee diventa astratta e l’interfaccia EmployeeFactory si occupa di costruisce l’istanza dell’oggetto richiesta.

abstract class Employee {
    function calculatePay();

interface EmployeeFactory  {
    function makeEmployee($employee);

class EmployeeFactoryImpl implements EmployeeFactory {
    function makeEmployee($employee) {
        switch($employee->type) {

        case “commissioned”:
            return new CommissionedEmployee($employee);
        break;

        case “hourly”:
             return new HourlyEmployee($employee); 
        break; 

        case “salaried”: 
             return new SalariedEmployee($employee); 
        break;

   }

Adesso ogni tipo di Employee avrà la propria implementazione e quindi il proprio metodo calculatePay che non crescerà al crescere dei tipi di Employee. Secondo Uncle Bob lo switch dovrebbe essere utilizzato solo in queste situazioni, ma per sua stessa ammissione ci dice che è difficile rispettare questa regola e che più volte lui stesso l’ha infranta. Non prendiamo per assoluto tutto quello che viene illustrato in questo articolo. Non sempre la teoria è la soluzione più efficace. Dobbiamo sempre calarci nel contesto del nostro progetto. Le cose dette  sono dei semplici tips che ogni programmatore dovrebbe conoscere e applicare nelle giuste situazioni.

Use descriptive name
Abbiamo discusso di questo nel post precedente. Le funzioni devono avere dei nomi che descrivono il loro comportamento. Dobbiamo utilizzare verbi e non dobbiamo avere paura di utilizzare nomi lunghi.

Function Arguments
Uncle Bob dice che il numero ideale di argomenti da passare ad una funzione è : zero! Badate bene che ideale non significa reale quindi  fermiamoci a pensare un attimo prima di “sbrodolare giù” parametri in una funzione. In generale la regola da applicare è questa: zero parametri vince (niladic); un parametro va benissimo (monadic); due parametri vanno bene (dyadic); tre parametri (triadic)solo se strettamente necessario e ben giustificato; più di tre .. forse stiamo sbagliando qualcosa oppure è bene “wrappare” tutto in un oggetto che diventa parametro. Passare tanti parametri ad una funzione aumenta decisamente la sua complessità: prima di tutto diventa difficile intuire il comportamento dal nome; in più molto difficilemente una funzione con molti parametri farà una cosa sola!
Altro aspetto importante sono i test. Una funzione con molti parametri sarà difficile da testare  dato che dovremo sicuramete testare tutti i pattern di comportamento dati dalle differenti combinazioni dei parametri passati.

Soffermiamoci un pò sulle monadic. Ci sono principalmente due modi (giusti) per passare il parametro alla funzione:

  • porre una domanda alla funzione: boolean fileExists(“My file”);
  • modificare il parametro all’interno della funzione e ritornalro

Evitiamo di utilizzare forme diverse da queste se abbiamo un unico parametro.

Un’altra situazione che dovremmo sempre evitare è di passare dei flag come parametro. Se passate un booleano ad esempio, molto probabilmente la vostra funzione non farà una sola cosa, infatti se il flag sarà true allora “pippo” altrimenti “pluto”.

Facciamo un pò di chiarezza sulle altre due forme di passaggio parametri. Nella dyadic, in generale possiamo dire che comprendere il comportamento di una funzione che ha due parametri è più difficile rispetto a una funzione che ne ha uno. Esempio: writeField(name) è più semplice che writeFiled(outputStream, name). A cosa serve il primo parametro? Stiamo passando un parametro di output e questo è male. Non sarebbe meglio trasformarlo in outputStream.writeField(name) ?? Oppure creare una classe FieldWriter che prende come argomento outputStream  nel costruttore e che ha un metedo write ??
Ci sono però delle situazioni nelle quali utilizzare due parametri è giusto. Se i parametri rappresentano le compomponenti di un singolo concetto, allora è più giusto utilizzare due parametri che uno. Esempio : Point point = new Point(0, 0);  è molto più comprensibile di Point point = new Point(0); . Forme dyadic non sono il male, dobbiamo solo essere sicuri che utilizzarle sia vantaggioso.

Have no side effects!!
Dal nome della funzione dovremo capirne il comportamento. Ad esempio checkPassword(User user) ci fa capire che verrà controllata la correttezza della password dell’utente. Se la funzione dopo aver controllato la correttezza della password, inizializza anche la sessione e poi ritorna true, la funzione crea un “danno collaterale” solo perchè nel nome viene nascosta la creazione della sessione. Ad ogni check che verrà effettuato, verrà creata una nuova sessione e distrutta la sessione corrente. Quindi la funzione introduce una dipendenza temporale, dato che il check delle credenziali potrà essere fatto solo in determinati istanti onde evitare la perdita dei dati salvati in sessione.

Le regole dello zio bob sono molto semplici nella comprensione, ma non altrettanto nell’applicazione. Applicatele dove sono necessarie senza dimenticare mai il contesto del vostro progetto. Non costruite un mostro per evitare di passare due parametri ad una funzione… non vi conviene comunque. Le funzioni sono molto importanti dato che ci evitano un sacco di problemi, come la duplicazione del codice (be dry), l’aumento delle dipendenze,  il peggioramento della leggibilità del codice, ecc.. Non abbiate paura di utilizzarle. Se una cosa che avete scritto non vi piace ma non avete il tempo di cambiarla, mettetela in una funzione, cosi intanto l’avete circoscritta e potrà essere modifica facilmente in futuro ..idea di un mio amico :)..

Per ora è tutto.

Questo articolo lo trovi sotto articoli, i post di TCF e taggato come , .

I commenti sono chiusi.