Pattern giornalieri in Javascript: gestisci il caos delle applicazioni

Pattern giornalieri in Javascript: gestisci il caos delle applicazioni

N.B. Questo contenuto è stato prodotto e pubblicato la prima volta da Flowing, società che da luglio 2022 è confluita in Claranet Italia. Insieme a essa sono confluite riflessioni, temi, metodologie e spunti, ampiamente condivisi e orgogliosamente riproposti all’interno di questo blog. ©claranet

PUBBLICATO IL 02/12/2014 DA

Ricardo

Partner

IN SINTESI

In questo articolo vorrei parlarti di alcuni semplici pattern che ci potrebbero aiutare nell’organizzazione e nella scrittura di codice Javascript per le nostre applicazioni. Pattern facili da applicare e utili sia in applicazioni sviluppate interamente in Javascript che in applicazioni backend dove Javascript viene utilizzato per migliorare l’esperienza utente.

Single Var Pattern

Questo pattern ci dice di dichiarare tutte le variabili all’inizio della nostra funzione mettendole tutte in un unico var. Cosi facendo, definiamo un’unica ‘zona’ dove poter trovare tutte le variabili locali favorendone il reperimento, la leggibilità e prevenendo possibili errori di utilizzo di una variabile prima della sua dichiarazione (vedi hoisting). Inoltre questo pattern ci aiuta a ricordare di definire le variabili evitando l’utilizzo inconsapevole di globali.

var changeTotal = function (editStatusButton) {    var editStatusButton = $(editStatusButton),        orderId = editStatusButton.attr('data-order-id'),        total = 0;        ...};

Vediamo ora due pattern fondamentali che ci aiutano a organizzare il nostro codice e circoscrivere il ‘caos’.

Namespace Pattern

Lo scopo di questo pattern è quello di ridurre l’utilizzo di globali, evitare naming collision (anche con librerie di terze parti) e l’utilizzo di prefissi. Javscript non supporta nativamente i namespace e quindi vedremo un modo per implementare questo pattern.

Partiamo da questo semplice esempio:

var pippo = 3,    pluto = function() {        // … fa cose vede gente …    },    paperino = function() {        // … inventa situazioni, coccola startup    };

Possiamo fare refactoring di questo codice utilizzando il namespace pattern. Creando un unico oggetto globale (ad esempio ‘ideato’) e attaccando tutte le nostre funzioni, variabili e oggetti su questo oggetto, facciamo si che diventino proprietà del singolo oggetto globale.

var ideato = {}ideato.pippo = 3ideato.pluto = function() {// … fa cose vede gente …}ideato.paperino = function() {// … inventa situazioni, coccola startup}

Un altro modo per definire il namespace è mostrato nel seguente esempio:

var ideato = ideato || {};

Come puoi vedere, se l’oggetto ideato esiste allora viene assegnato alla variabile ideato altrimenti viene inizializzato con un oggetto vuoto. Questo idioma è utile a preservare l’eventuale esistenza di un namespace definito precedentemente nella nostra applicazione.

Module Pattern

Con questo pattern possiamo suddividere il nostro codice in moduli che rappresentano oggetti – i quali definiscono proprietà – metodi e quindi comportamento. L’obiettivo del pattern è quello di emulare il concetto di ‘classe’ e quindi di visibilità di proprietà e metodi.

Con il module pattern riusciamo ad incapsulare la logica all’interno del modulo e ad esporre un interfaccia pubblica utilizzabile dal resto dell’applicazione. Riusciamo quindi ad organizzare il codice in maniera pulita, separando le responsabilità tra i vari moduli ed abbassando la probabilità di avare conflitti tra i nomi delle nostre funzioni e quelli di altre funzioni definite altrove.

Come dicevamo Javascript non è in grado di definire la visibilità di una proprietà o metodo. Per emulare questo concetto utilizziamo lo scope delle funzioni. Tutte le variabili e/o metodi dichiarati localmente all’interno del modulo sono visibili solo al modulo stesso mentre quelle definite dentro l’oggetto di ritorno vengono rese disponibili all’esterno. Chiariamo con un po’ di codice:

var pomodoro = (function () {   var seconds = 1500;   return {     decrease: function () {      return --seconds;    },     reset: function () {      seconds = 1500;      console.log(seconds);    }  }; })();  console.log(pomodoro.decrease()); // 1499pomodoro.reset(); // 1500

La variabile seconds è visibile solo all’interno della funzione pomodoro che rappresenta il modulo. pomodoro espone i metodi decrease e reset tramite la restituzione di un oggetto. Questi metodi posso essere utilizzati dall’esterno.

Revealing Module Pattern

Una variante del module pattern è il ‘revealing module’ pattern.

La parte di revealing è puramente sintattica in quanto nel modulo tutte le variabili sono create privatamente e tramite la restituzione di un object literal mostriamo al mondo esterno solo proprietà e metodi che vogliamo.

Riprendiamo l’esempio precedente. Definiamo namespace e modulo tutto all’interno di un singolo file:

var ideato = ideato || {};ideato.pomodoro = ideato.pomodoro || {};(function(namespace) { namespace.create = function() {     var seconds = 1500,         decrease,         reset;     decrease = function() {         return --seconds;     };     reset = function() {         seconds = 1500;         console.log(seconds);     };     return {         decrease: decrease,         reset: reset     }; };})(ideato.pomodoro);

Tramite l’utilizzo della keyword var abbiamo definito seconds, decrease e reset in modo tale che siano visibili solo all’interno del modulo. Successivamente il modulo ritorna un oggetto che espone la sua interfaccia pubblica rivelando al mondo esterno solo i metodi che vuole.

Come puoi vedere con pochissimo sforzo riusciamo a suddividere logicamente il codice in veri e propri moduli utilizzabili in ogni punto dell’applicazione.

Per utilizzare il modulo basta includere il file e:

var myPomodoro = ideato.pomodoro.create();console.log(myPomodoro.decrease()); // 1499myPomodoro.reset(); // 1500console.log(myPomodoro.seconds) // undefined

Organizzare il codice in questa maniera ci permette di facilitare il passaggio delle dipendenze tra moduli, mitigare l’utilizzo di oggetti globali e proteggere i metodi da sovrascritture involontarie.

Callback Pattern

Continuando il discorso sulla gestione delle dipendenze, un altro pattern che potrebbe essere nostro amico è il ‘callback pattern’ con il quale riusciamo a disaccoppiare le responsabilità tra i nostri oggetti.

In Javascript le funzioni sono oggetti. Questo significa che una funzione f1 può essere passata come parametro della funzione f2 ed utilizzata al suo interno.

Vediamo un esempio:

var f2 = function(callback){    // … fa cose vede gente …    callback()    // … fa cose vede gente …} var f1 = function(){    // … fa cose vede gente …} f2(f1);

f1 viene passata senza parentesi dato che non vogliamo eseguirla ma passare una referenza alla funzione stessa. Sarà poi f2 che eseguirà f1.

Come puoi vedere, invece di ‘harcodare’ la logica di f1 dentro f2, possiamo racchiuderla in una funzione che f2 eseguirà in maniera agnostica disaccoppiando le responsabilità delle due funzioni. Il testing si semplifica e il riutilizzo aumenta.

Ecco un altro esempio:

var myPomodoro,    initApp;myPomodoro = ideato.pomodoro.create();initApp = function (resetPomodoro) { // … fa cose vede gente …   resetPomodoro();};initApp(myPomodoro.reset);

Patterns per la creazione di oggetti

Chiudiamo con alcuni consigli sulla creazione degli “oggetti”.

In Javascript ci sono diversi modi di creare oggetti. I più comuni sono:

  • creazione con object literal
var movie = { title: "back to the future", year: "1985", ...}
  • creazione tramite new con costruttore custom
var movie = new Movie("Back to the future");

Personalmente preferisco il primo approccio perché enfatizza il fatto che un oggetto può essere visto come un hash chiave:valore dove la chiave definisce il nome della proprietà e il valore ne definisce il contenuto. Nel caso in cui il contenuto sia una funzione allora potremmo parlare di metodo.

Utilizzando librerie o comunque lavorando su codice di altri mi capita di trovare l’utilizzo del new e quindi trovo utile comunque conoscere meglio il significato di questo approccio:

var Movie = function(title){    this.title = title;    this.printSummary = function(){        return "Summary for " + this.title + " ...";    }}var movie = new Movie("Back to the future");movie.printSummary();

Con la keyword new invochiamo il costruttore Movie, che al suo interno genera un oggetto this al quale potremo attaccare proprietà e metodi. Il this rappresenta l’oggetto che verrà implicitamente restituito alla fine del costruttore.

Il metodo printSummary viene aggiunto ad ogni nuova istanza dell’oggetto Movie occupando memoria che potrebbe essere risparmiata dato che il metodo printSummary non cambia per ogni singola istanza.

Un modo migliore per gestire questo comportamento è descritto nel codice seguente:

Movie.prototype.printSummary = function(){    return "Summary for " + this.title + " ...";}

Attaccando il metodo al prototype dell’oggetto, printSummary verrà creata una sola volta in memoria.

Un altro aspetto pericoloso dell’utilizzo del new è il seguente:

var movie = Movie("Back to the future");

Hai visto? Probabilmente no dato che nessun errore o segnalazione vi verrà notificata. Ho appena dichiarato un oggetto globale dato che mi sono dimenticato di scrivere la parola chiave new. this dentro Movie verrà attaccato all’oggetto window se siamo nel browser e questo potrebbe causarvi tutte quelle simpatiche cose che possono succedere con gli oggetti globali (comportamenti inaspettati, namespace pollution, ecc).

C’è un pattern che ci aiuta ad evitare questo problema:

function Movie(title) {     "use strict";     if (!(this instanceof Movie)) {     return new Movie();     }     this.title = title;     this.printSummary = function(){        return "Summary for " + this.title + " ...";     }     return this;}

Lo ‘strict mode’ scatena un eccezione del tipo “Uncaught TypeError: Cannot set property ‘title’ of undefined” se proviamo ad attaccare e valorizzare una proprietà al this. Il controllo sul tipo ci garantisce di istanziare un oggetto Movie con l’utilizzo del new.

In conclusione

Nonostante sia uno sviluppatore backend, in tutte le applicazioni che scrivo c’è sempre una bella fetta di codice Javascript.

L’utilizzo di questi pattern mi evita semplicemente di spargere snippets di codice Javascript in giro per i template dell’applicazione, aiutandomi a contenere il caos che comunque si verrebbe a formare  anche in applicazioni dove la UI è più o meno semplice.

Li ho chiamati ‘pattern giornalieri’ appunto perché ogni qual volta che scrivo codice Javascript applico alcuni di questi pattern. Mi trovo spesso a programmare applicazioni PHP che utilizzano Javascript nei template per l’arricchimento della UI. Spesso non mi serve un framwork Javascript ma mi basta utilizzare le librerie più comuni per il raggiungimento degli obiettivi (jquery, underscore, moment, ecc).

Applicare questi pattern è utile per migliorare design, leggibilità, testabilità e manutenibilità del codice. Non ho mai riscontrato casi in cui l’applicazione di questi pattern fosse un overkill o comunque un over-ingegnerizzazione del codice applicativo.

Se vuoi approfondire l’argomento, ti consiglio queste due risorse:

Javascript patterns di Stoyan Stefanov

Learning JavaScript Design Patterns di Addy Osmani


Contatta i nostri esperti

per parlare di come possiamo aiutare te e la tua azienda ad evolvere

Contattaci