Ci vuole Stile

In Digistone, ma certamente in ogni azienda, capita spesso di leggere codice scritto da altri. Generalmente quando si legge codice scritto da estranei diventa molto complicato interpretarlo. In questi casi potrebbe essere molto utile un block notes, una penna multicolore e qualche bicchierino di grappa, per fare reverse engineering e tirar fuori le logiche pensate dall’anonimo programmatore. Quando invece si legge il codice di un collega è tutto diverso: innanzitutto si intuisce chi è l’autore del listato, non perché sia firmato o perché ci sia nella code documentation la chiave @author, ma semplicemente perché ogni programmatore ha il suo stile, e si impara a riconoscerlo, esattamente come quando si legge spesso uno stesso scrittore e  capita di trovare una citazione scritta sul muro del bagno dell’autogrill. Riconosciuti stile e autore, diventa molto più semplice estrapolare la logica sterigrafata nel codice.

Ma cos’è effettivamente uno stile di programmazione?

Formalmente, la definizione calza meglio per il topic di questo blog è la seguente:

Il complesso delle scelte e dei mezzi espressivi che costituiscono l’impronta peculiare di una scuola, di una tradizione letteraria, musicale o artistica, e spec. della personalità dell’autore.

Per intendere meglio: immaginiamo che il programmatore sia un pittore e che il software sia un’opera d’arte. Immaginiamo che a due pittori distinti sia affidato il compito di creare un affresco con lo stesso soggetto, con gli stessi strumenti e materiali, nelle stesse condizioni ambientali dello stesso giorno. Certamente i due pittori realizzeranno un affresco simile, ma le opere non saranno mai identiche: avranno stili differenti. Il software è identico, ma con una sfumatura diversa e un grado di libertà maggiore: lo stesso affresco può essere realizzato con una decina di stili e migliaia di logiche differenti. Per le migliaia di logiche possibili c’è poco da fare: ragioniamo tutti in modo diverso, abbiamo logiche deduttive e induttive differenti e pattern inferenziali che dipendono anche dal nostro stato d’animo. Un buon programmatore dovrebbe essere bravo a riconoscere la presenza di regole non facilmente intuibili nei propri algoritmi,  per prendersi la briga di  documentare meglio il codice e renderlo  più leggibile. Raramente però riconosceremo una personalità nelle logiche degli algoritmi, poiché le logiche possono cambiare molto a seconda dei temi, delle funzionalità, dei tempi di stesura imposti, dallo stato mentale del programmatore. Immaginiamo di dover stendere l’algoritmo che regoli l’animazione di un palloncino d’aria presente nell’intestazione di una pagina web. Poi immaginiamo di dover creare l’algoritmo del calcolo statistico della permanenza media di una macchina all’interno di alcune aree di un’azienda. Certamente gli algoritmi saranno differenti, le logiche ancora di più, ma lo stile sarà uguale. La firma è quindi nello stile! La firma è nella scelta degli spazi tra le righe, dei nomi scelti per le variabili, dei costrutti preferiti ad altri, della padronanza dell’uso di pattern e costrutti standard e conosciuti. Facciamo quindi un esempio pratico, il classico esempio di fondamenti di informatica 1: risolvere in un linguaggio di progammazione a scelta, utilizzando solo l’operatore di somma o di moltiplicazione,  il problema della successione di Fibonacci.

Un certo uomo mette una coppia di conigli in un posto circondato su tutti i lati da un muro. Quante coppie di conigli possono essere prodotte da quella coppia in un anno, se si suppone che ogni mese ogni coppia genera una nuova coppia, che dal secondo mese in avanti diventa produttiva?

Nel caso non si conosca il problema dei conigli di Fibonacci, è possibile approfondire al link: http://www.fibonacci.it/problemaconigli.html . Da una semplice ricerca in rete, si possono trovare infiniti stili di programmazione, vediamone alcuni:

Primo caso: scrittura completa con tutte le parentesi necessarie

public int fibonacci(int n)  {
    if(n == 0){
      return 0;
    }else if(n == 1){
      return 1;
    }else{
      return fibonacci(n - 1) + fibonacci(n - 2);
    }
}
Secondo caso: scrittura leggermente più leggera

public int fibonacci(int n)  {
    if(n == 0)
        return 0;
    else if(n == 1)
      return 1;
    else
      return fibonacci(n - 1) + fibonacci(n - 2);
}
Terzo caso: eliminazione degli if-else annidati
public int fibonacci(int n)  {
   switch(n){
       case 0:
          return 0;
       case 1:
          return 1;
       default:
          return fibonacci(n - 1) + fibonacci(n - 2);
   }
} 
Quarto caso: operatore ternario
public int fibonacci(int n)  {
    return n == 0 ? 0 : n == 1 ? 1 :fibonacci(n - 1) + fibonacci(n - 2);
}

E’ evidente che la stessa funzione java è stata scritta con 4 stili differenti, ognuno dei quali con differenti problemi di mantenibilità e revisione. Se gli autori dei quattro algoritmi fossero tutti differenti, certamente ritroveremmo lo stesso stile in altre sezioni di codice e riconosceremmo ovunque la bestia di satana che ha scritto fibonacci con l’uso dell’operatore ternario annidato.

Ma come si sceglie il proprio stile?

Per esperienza direi che è un processo naturale, ma va innescato, un po’ come imparare ad andare in bicicletta: bisogna imporsi di imparare, ma poi sarà naturale migliorare. Occorre leggere molto codice, per imparare quanti più modi possibili di scrivere la stessa cosa. Occorre soprattutto rileggere il proprio codice, per capire se lo stile utilizzato è leggibile, oppure c’è ancora un margine di miglioramento. Ci sono però degli studi che ci aiutano ad evitare errori comuni, banalmente evitabili. Occorre innanzitutto dire che non è possibile utilizzare lo stesso stile di programmazione per qualunque linguaggio, occorre sempre adattare il proprio stile, cercando il compromesso migliore. Alcuni ambienti di sviluppo aiutano a mantenere lo stesso stile, cercando di mantenere stesso spazio di identazione per gli annidamenti e stesso numero di righe tra corpi di codice semanticamente simili. Alcuni linguaggi di programmazione, ad esempio Python, sono molto restrittivi sullo stile, impongono un certo numero di spazio tra operatori, parametri  di ingresso e dichiarazione di classi. Vediamo però 6 regole generali, comuni a tutti i linguaggi:

 

  1. Variabili: devono sempre mantenere la stessa notazione. Se ad esempio usate il comelCase e poi passate al PascalCase, ad un certo punto non si distingueranno più oggetti da proprietà, funzioni da costruttori e sale da zucchero;
  2. Nomenclatura: chiamate le variabili, gli oggetti e le funzioni con un nome che faccia capire il suo significato intrinseco. Usate sempre una sola lingua, meglio l’inglese, perchè ha parole più brevi dell’italiano;
  3. Coesione: mantenete sempre alta la coesione della vostra funzione, della vostra classe, del vostro package. Immaginate di essere ad un pc nuovo e di dover debuggare il vostro software senza poter scaricare alcun IDE: dovete sapere sempre dove mettere mani;
  4. Lunghezza dei metodi: qualsiasi ragionamento è scomponibile in logiche deduttive/deduttive. Se il metodo di una classe diventa troppo lungo, scomponentelo sempre in logiche più piccole, anche a costo di  diminuire le performance dell’algoritmo;
  5. Commenti: commentate solo codice poco chiaro, eliminate sempre il codice commentato che non serve più;
  6. Identazione: quando l’IDE non aiuta, obbligatevi a identare il codice correttamente. L’identazione è il miglior amico dello stile.

Lascio che sia un grande esperto di stile a terminare per me questo blog:

 

Buon hack,

Sondavide

Grails plugins e submodules

Architetture Grails che prendono vita su più plugin contemporaneamente, ciascuno in un suo repository Git, senza rinunciare a comodità come “l’hot reload” per applicare immediatamente le modifiche al codice senza ricompilare tutto da capo, oppure il versioning per essere sicuri che le modifiche appena fatte a un plugin possano portare ad errori di regressione su altre applicazioni.

Metodologie così potenti richiedono solo uno “sforzo” iniziale per la comprensione e la configurazione, ma usarle alla fine è più semplice di quanto si pensi. Tutto si basa sui medesimi concetti di “commit”, “push” e “pull” che si usano nello sviluppo standard su un singolo repository Git.