Archivi categoria: Javascript

Typescript non mi piaceva (prima di metterci le mani)

Qualche settimana fa ho avuto modo di partecipare a un techbar organizzato da DevMarche e tenuto da Andrea Balducci su TypeScript (le slides), il “nuovo” linguaggio simil-javascript targato Microsoft e usato in AngularJS 2 da Google. Lo stesso Andrea ha ripreso il dicorso la settimana scorsa al MobileCamp, approfondendo alcuni concetti (le slides).
In realtà TypeScript non è nuovo per niente ma – sebbene abbia visto la luce a fine 2012 – soltanto con l’annuncio dell’adozione nella versione 2 di AngularJS ha ridestato l’interesse della comunità ed è salito agli onori della cronaca.

La (finta) concorrenza: CoffeeScript e Dart

Forse perché il Javascript è nato in pochi giorni e con un obiettivo totalmente diverso da quelli odierni, fatto sta che i più odiano alcune sue caratteristiche, e nel corso del tempo si è cercato di nasconderlo dietro qualche altro linguaggio così da mascherarne difetti e brutture.

La prima di queste “maschere” è stata CoffeeScript, un linguaggio “ponte” compilabile in Javascript che prese spunto prevalentemente da Ruby, Haskell e Python. Scopo primo: rendere il codice più compatto e leggibile.

Evidentemente questo “nuovo linguaggio” non deve aver fatto breccia a Mountain View, perché poco dopo è uscito Dart. Lo scopo di Google era abbastanza più sensibile: mandare in soffitta il Javascript come linguaggio del web.

L’esigenza manifestata da Google di “rompere con il passato” non deve però aver convinto quelli di Microsoft, che poco dopo hanno fatto la loro mossa sviluppando TypeScript.
A questo punto Google pensava di sviluppare un ulteriore superset di TypeScript chiamato AtScript – che aggiungeva principalmente le annotations al linguaggio di Microsoft. Fortunatamente Google ha deciso di abbandonare il progetto collaborando con Microsoft allo sviluppo di TypeScript.

Cos’è TypeScript

TypeScript è un “Superset tipizzato di Javascript che compila in Javascript”, ovvero è un Javascript ampliato con alcuni costrutti (il cui uso è facoltativo), che può essere compilato in Javascript da un compilatore anche questo scritto in TypeScript.
La novità è che il compilato è né più né meno che un bel Javascript, tendenzialmente identico a come l’avremmo scritto noi, e questo succede perché molte delle nuove “funzionalità” non vanno a generare un bel niente, ma servono soltanto al compilatore per una verifica statica del codice.
Naturalmente essendo il prodotto del compilatore del “codice Javascript”, tra i parametri di configurazione del compilatore si può specificare anche la versione di “Javascript” (o più propriamente EcmaScript), e i costrutti utilizzati vengono tradotti oppure no a seconda della versione di EcmaScript scelta.

Qualche nota sul compilatore

Installazione

Typescript è una libreria presente su npm che installa il compilatore TypeScript da usare globalmente, installiamolo quindi con:
npm install -g typescript

Esecuzione

Il compilatore si richiama con il comando:
tsc nomefile.ts
Il risultato è la creazione nella stessa directory del file .ts di un file Javascript con lo stesso nome ma con estensione js. Aggiungendo il parametro -w (o --watch) viene creato uno watcher sul file che lo ricompila a ogni modifica.
Per visualizzare una guida del compilatore eseguire il comando tsc -h (o tsc --help).

Configurazione

Di recente è stato introdotto il file di configurazione tsconfig.json, la cui presenza marca la directory come un progetto TypeScript. Se il file è vuoto vengono usate le configurazioni di default e la compilazione viene eseguita su tutti i file ts trovati (basta lanciare il tsc da solo, o eventualmente mettendolo in “watch”).
Nel repository GitHub di TypeScript è presente una guida esaustiva, elenco qui giusto alcune configurazioni principali:

  • “files”: è un array opzionale contenente i file che devono essere considerati dal compilatore, in sua assenza vengono compilati tutti i file ts presenti nella directory e nelle subdirectories.
  • “target” (“compilerOptions”): specifica la versione di EcmaScript di destinazione (“es3”, “es5” o “es6”)
  • “sourceMap” (“compilerOptions”): specifica se devono essere generati i source map per poter eseguire il debug direttamente dello script TypeScript
  • “module” (“compilerOptions”): specifica il formato dei moduli, il default è “commonjs”, il formato usato in NodeJS

Il “superset” di istruzioni

Sono pigro, e credo fermamente che re-inventare sempre la ruota non serve. Prendo quindi gli esempi del workshop di cui sopra cercando di analizzarli per quanto mi è possibile.

Any? Un po’ troppo generico

function sortByName(a){
	var result = a.slice(0);
	result.sort(function(x,y){
		return x.name.localCompare(y.name);
	})
}
sortByName(5);

In questo esempio vediamo una semplice funzione di ordinamento, e la sua invocazione (sbagliata). Dichiarando qualche tipo i bug saltano fuori in un attimo, specialmente utilizzando une editor come Visual Studio Code, che sembra tagliato su misura per sviluppare in TypeScript…

function sortByName(a:any[]){
    var result = a.slice(0);
    result.sort(function(x,y){
        return x.name.localeCompare(y.name);
    })
}
sortByName([{'name' : '5'}]);
function sortByName(a:{'name':string}[]){
    var result = a.slice(0);
    result.sort(function(x,y){
        return x.name.localeCompare(y.name);
    })
}
sortByName([{'name' : '5'}]);
interface Named {
	'name' : string
}
function sortByName(a:Named[]):Named[]{
	var result = a.slice(0);
	result.sort(function(x,y){
		return x.name.localeCompare(y.name);
	});
	return result;
}
var data = [{'name' : '5'}];
var sorted = sortByName(data);
console.log(sorted);
function sortByName(a) {
    var result = a.slice(0);
    result.sort(function (x, y) {
        return x.name.localeCompare(y.name);
    });
    return result;
}
var data = [{ 'name': '5' }];
var sorted = sortByName(data);
console.log(sorted);

Come si può vedere da questo semplice esempio il compilatore usa buona parte di quello che scriviamo per fare controllo statico del codice, e il risultato non è minimamente alterato dalla definizione di qualche contratto sui tipi.

Classi e interfacce per farci sentire a casa

In quest’esempio vediamo una semplice classe con una proprietà privata (level), due proprietà pubbliche (name e bio) e un metodo pubblico (train). Il dichiarare come pubblici i parametri del costruttore li identifica automaticamente come proprietà pubbliche della classe.

export interface Named {
	name: string;
}
export class Jedi implements Named {
	private level:number = 0;
	
	constructor(public name: string, public bio: string) {
		this.level = 15;
	}
	
	train(levelUp : number){
		this.level += levelUp;
		return this.level;
	}
}
var Jedi = (function () {
    function Jedi(name, bio) {
        this.name = name;
        this.bio = bio;
        this.level = 0;
        this.level = 15;
    }
    Jedi.prototype.train = function (levelUp) {
        this.level += levelUp;
        return this.level;
    };
    return Jedi;
})();
exports.Jedi = Jedi;

Come si può vedere il risultato non aggiunge niente al javascript, trattasi sempre di una funzione Jedi che nel prototype ha la funzione dichiarata come metodo pubblico.
Naturalmente così come si può implementare un’interfaccia con implements si può anche estendere un’altra classe con extends.
L’uguaglianza tra tipi si basa su un confronto delle interfacce tra i vari oggetti (interfacce intese come proprietà e metodi esposti), quindi su questo fronte potrebbero esserci dei fraintendimenti se si è abituati a lavorare con linguaggi tipizzati come Java e C#.

I Generics per svincolarci dai tipi

Il TypeScript è nato principalmente per rendere più facile la vita degli sviluppatori Java/C#/simili nel mondo Javascript, e in quei linguaggi per aumentare la riusabilità del codice esistono i generics, giocoforza dovevano gestirli anche qui.
Una guida esaustiva ai generics in TypeScript la potete trovare qui, mi limito a riportare un esempio preso dalla guida perché l’argomento è lungo e complesso.

interface Lengthwise {
    length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}
loggingIdentity({length: 10, value: 3});
loggingIdentity([3]);
loggingIdentity(3);  // Errore di compilazione

Decorators per una facile estendibilità

Tra tutte forse la novità che ha colpito maggiormente l’interesse degli sviluppatori è quella dei decorators. Il pattern Decorator è uno dei più utilizzati e permette di “aumentare” le funzionalità di un oggetto a runtime “avvolgendolo”, ovvero funzionando da wrapper.
I decorators TypeScript sono delle annotations applicabili – eventualmente in sequenza – a delle dichiarazioni di classi, funzioni, proprietà (di classe) e argomenti (di funzione) per estenderne il comportamento.
Una serie di articoli molto completa e interessante che spiega come funzionano i Decorators in TypeScript potete trovarla qui. Per arrivare in fondo a quest’articolo prima dell’inverno anche qui eviterò di dilungarmi, riportando giusto il classico esempio usato da tutti: il log di una chiamata di funzione.

export function log(target: Function, key: string, descriptor: any) {
	var original = descriptor.value;
	descriptor.value = function(...args: any[]) {
		var a = args.map(a => JSON.stringify(a)).join();
		var result = original.apply(this, args);
		var r = JSON.stringify(result);
		console.log(`call: ${key}(${a}) => ${r}`);
		return result;
	}

	return descriptor;
}
import {log} from "./decorators"

export class Jedi {
	private level:number = 0;
	constructor(public name: string, public bio: string) {
		this.level = 15;
	}
	
	@log
	train(levelUp : number){
		this.level += levelUp;
		return this.level;
	}
}
if (typeof __decorate !== "function") __decorate = function (decorators, target, key, desc) {
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") return Reflect.decorate(decorators, target, key, desc);
    switch (arguments.length) {
        case 2: return decorators.reduceRight(function(o, d) { return (d && d(o)) || o; }, target);
        case 3: return decorators.reduceRight(function(o, d) { return (d && d(target, key)), void 0; }, void 0);
        case 4: return decorators.reduceRight(function(o, d) { return (d && d(target, key, o)) || o; }, desc);
    }
};
var decorators_1 = require("./decorators");
var Jedi = (function () {
    function Jedi(name, bio) {
        this.name = name;
        this.bio = bio;
        this.level = 0;
        this.level = 15;
    }
    Jedi.prototype.train = function (levelUp) {
        this.level += levelUp;
        return this.level;
    };
    Object.defineProperty(Jedi.prototype, "train",
        __decorate([
            decorators_1.log
        ], Jedi.prototype, "train", Object.getOwnPropertyDescriptor(Jedi.prototype, "train")));
    return Jedi;
})();
exports.Jedi = Jedi;

Facciamola breve: la funzione __decorate prende il prototype della classe contenente la funzione che vogliamo decorare e “avvolge” la funzione scelta con le funzioni “decoratrici” passate nell’array, in sequenza. Basta annotare cioè due volte la stessa funzione con lo stesso decorator @log per ritrovarci con il decorators_1.log due volte nell’array, e per vedere due volte il log dell’invocazione della funzione.
L’Object.defineProperty sovrascrive la funzione originaria con quella decorata ritornata dal __decorate.
La prima parte può spaventare un po’, ma in realtà sta soltanto definendo la funzione __decorate in modo da farle usare il Reflect.decorate se disponibile o farle implementare la logica di decorazione in caso contrario.

Modules, per “modularizzare” meglio il codice

Sistemi per rendere più modulare il nostro codice Javascript c’erano anche senza scomodare TypeScript, ma i moduli sono comunque un’opportunità da non ignorare.

module Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }
}

module Validation {
    var lettersRegexp = /^[A-Za-z]+$/;
    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }
}

In sostanza un modulo serve ad aggregare funzioni affini in contenitori appropriati per evitare collisioni di nomi e altri errori. Lo stesso modulo può essere “riempito” anche da file diversi, così ad esempio se abbiamo tutta una serie di funzioni di validazione e non vogliamo creare file enormi possiamo spezzettare il modulo in più file.
Anche in questo caso ho tutt’altro che approfondito, si può trovare una guida esaustiva qui.

Utility varie, anche un po’ di zucchero non guasta

Let

test1 = "a"; // errore, la variabile non è stata ancora dichiarata
let test1;
if (true){
	let test2;
	test2="a";
}
test2="b"; // errore, la variabile è stata dichiarata internamente al blocco if

Il compilato Javascript di questo blocco è identico ma usa i var al posto dei let (se non stiamo compilando il EcmaScript 6 o superiori). Questo significa che il javascript funzionerebbe comunque effettuando l’hoisting delle variabili.

For of

for (var c of characters) {
	console.log(c);
}
// il compilato di questo ciclo è il seguente:
for (var _i = 0; _i < characters.length; _i++) {
    var c = characters[_i];
    console.log(c);
}

Questa cosa è di una banalità estrema e non snellisce nemmeno di tanto il codice, ma confesso che a me almeno una volta è successo di aver dimenticato che usando ‘in’ la variabile viene valorizzata con l’indice e non con il valore…

Operatore freccia

class Test {
	private acc = "";
	conc = (arg:string)=>{
		this.acc = arg + " " + this.acc;
		return this.acc;
	}
}
// il compilato è:
var Test = (function () {
    function Test() {
        var _this = this;
        this.acc = "";
        this.conc = function (arg) {
            _this.acc = arg + " " + _this.acc;
            return _this.acc;
        };
    }
    return Test;
})();

Quest’ultimo esempio rende l’idea di come alcuni trabocchetti possano essere schivati usando certi costrutti, che aiutano anche a rendere più leggibile il codice e non riducono minimamente la retrocompatibilità del nostro codice.

Definizione di tipi per librerie esistenti?

Chi più chi meno tutti usiamo delle librerie/plugin e certo per utilizzare con profitto un linguaggio che si chiama “TypeScript” sarebbe bellissimo avere a disposizione le definizione dei tipi utilizzati in queste librerie.
Poco meno di tre anni fa tale Boris Yankov ebbe la bella idea di crearsi un repository su GitHub chiamato DefinitelyTyped e inizio a buttarci dentro le definizioni di alcune librerie Javascript.
Ebbene questo progetto ha da poco superato i 10000 commits e i 1000 contributori, e dentro ci si può trovare di tutto…

Conclusioni

Vero che per il teorema di Bohm-Jacopini qualunque algoritmo può essere scritto usando soltanto iterazione, struttura condizionale e sequenza, ma certo se si vuole realizzare grossi sistemi qualche costrutto un po’ più avanzato ce lo vuole.
Almeno se chi sviluppa ha una buona conoscenza informatica il sistema guadagna molto in eleganza e manutenibilità strutturandolo a dovere, e TypeScript da questo punto di vista può aiutare moltissimo laddove il Javascript (almeno inteso come Ecmascript 5 e inferiori) ha delle grosse lacune.

Per la maggior parte i costrutti aggiunti comportano solo lavoro per il compilatore e lasciano inalterato il codice risultante, in altri casi vengono tradotti esattamente come li tradurremmo noi se conoscessimo il linguaggio alla perfezione, cosa spesso non vera.

Benefici apportati dall’utilizzo di questo compilatore:

  • l’architetto che può sfruttare costrutti avanzati per evitare di insozzare il codice
  • chi sviluppa può farlo senza timori di pestare una mina a ogni riga perché il compilatore segnala gran parte degli errori di codifica senza dover eseguire il codice, rendendo più facile il refactoring (anche grazie a strumenti più avanzati) e limitando i bug da disattenzione
  • l’interprete si trova a dover eseguire un codice migliore e ottimizzato, e può quindi raggiungere prestazioni migliori

Aspetti negativi di questo strumento:

  • si deve aggiungere uno “strato” di codice, e questo potrebbe portare a qualche problemino con sdk particolari
  • non mi viene in mente nient’altro

Ma a parte tutto secondo me il maggior merito di TypeScript (e della Microsoft) è un altro. Da più fronti si stava cercando di attaccare il Javascript per sostituirlo – sul web in particolare – senza dargli il tempo di crescere nelle varie versioni di EcmaScript. TypeScript sta riuscendo a spingere nella standardizzazione dei costrutti velocizzando i rilasci delle versioni EcmaScript ma dando l’opportunità a chi non può evolvere verso le nuove versioni di sfruttare già oggi tutti quei costrutti, e mantenendo una non trascurabile retrocompatibilità. Per me può bastare.

Memoization: metti il turbo alle tue funzioni Javascript

La memoizzazione – da non confondere con memorizzazione – è una tecnica usata allo scopo di evitare che una stessa funzione, onerosa dal punto di vista computazionale, venga eseguita più volte con gli stessi valori. In pratica si fa in modo che il valore di ritorno corrispondente a certi parametri finisca in una sorta di cache, cosicché eventuali successive chiamate alla stessa funzione con gli stessi parametri non comportino delle nuove esecuzioni della funzione stessa.
In javascript questo simpatico giochetto si può implementare in un paio di modi, a seconda che la funzione accetti parametri oppure no.

Funzione senza parametri, caso più “semplice”

Ipotizziamo che nel nostro programma da qualche parte ci sia una funzione che non si aspetti alcun parametro in ingresso e che durante l’esecuzione del programma potrebbe dover essere eseguita più volte. In questo caso – non molto frequente ma nemmeno raro – mettere il risultato in una “cache” non ha proprio senso perché non si ha nemmeno una “chiave” con cui riandarlo a pescare. Si possono quindi sfruttare gli scope e il fatto che una funzione è un oggetto come tutti gli altri (e quindi memorizzabile in una variabile).

// funzione originale
var getSomething = function(){
  var something;
  something = {}; // logica onerosa
  return something;
};

// funzione modificata
var getSomethingMem = function(){
  var something;

  something = {}; // logica onerosa

  getSomethingMem = function(){
    return something;
  };
  return getSomethingMem();
};

Si ha una funzione anonima getSomething che esegue tutta una serie di calcoli onerosi e ritorna un risultato. Per farne il memoize è sufficiente – una volta eseguita la computazione e salvato il risultato in una variabile – assegnare alla variabile contenente questa funzione una nuova funzione che ritorni direttamente il valore calcolato. La variabile something è inclusa nello scope di questa nuova funzione “fasulla” getSomethingMem, e questa funzione dopo la prima esecuzione non farà altro che ritornare il valore calcolato la prima volta.

Funzione con parametri, la cache

Il “trucco” illustrato sopra non si può applicare al caso di funzione che si aspetta dei parametri in ingresso, ma la flessibilità del javascript ci permette di assegnare una cache a qualunque funzione.
Spiegarla a parole non è proprio semplice lascio quindi a voi il piacere di capirla, in fondo sono solo poche righe di codice.
In questo caso la “cache” è un modulo CommonJs, la sua funzione create prende in ingresso una funzione (ad esempio una list) e ritorna un oggetto avente una sua implementazione “estesa” con la cache della stessa funzione list, più eventuali altre funzioni di utilità.

var list = function(params){
  // logica onerosa, ma stateless e indipendente dal tempo
  return {};
};

list = require('Cache').create(list).get;
exports.create = function(f, params){
  params = params || {};
  
  var cache, get, reset;
  
  cache = {};
  
  get = function(){
    var key = JSON.stringify(Array.prototype.slice.call(arguments));
    if (key in cache){
      console.log('From cache: ' + key);
      return cache[key];
    } else {
      console.log('Not in cache: ' + key);
      return cache[key] = f.apply(this, arguments);
    }
  };
  
  reset = function(){
    cache = {};
  };
  
  return {
    'get' : get,
    'reset' : reset
  };
};

Detto che la funzione di memoize può essere implementata in vari modi (quella sopra è una versione modificata di una che trovai da qualche parte sul web), è chiaro che si possono trovare librerie che ci alleggeriscono del compito. Librerie -> Javascript -> ovviamente qualcosa c’è anche in Underscore.js.

In sintesi

È la memoization la cura per ogni male? Chiaramente no. Può essere usata sempre? No, la risposta è quasi sempre no quando in una domanda compaiono dei quantificatori universali come “ogni” o “sempre”…
Queste tecniche potrebbero risolvervi grossi problemi di prestazioni ad esempio in presenza di funzioni lente perché causa di molte letture su disco, a discapito di un maggior utilizzo della memoria, ma questo divario si riduce di molto se i dischi sono SSD.
In presenza di funzioni stateful (il male, almeno quando si usano linguaggi funzionali), chiaramente non si può pensare di bypassare l’esecuzione della funzione andando a pescare il valore in una cache, così come non si può fare troppo affidamento su una cache se i risultati cambiano allo scorrere del tempo (ad esempio perché le elaborazioni sono basate su dati aggiornati periodicamente).
Il caso limite è quello di una funzione molto complessa che fa molte query su un database i cui dati cambiano ma non troppo spesso. In un caso del genere il suo utilizzo ci potrebbe stare ma bisognerebbe implementare adeguate logiche di “pulizia” della cache dei risultati a ogni aggiornamento dei dati.

A usarla bene il vostro smartphone/tablet potrebbe anche spiccare il volo, ma facendolo con leggerezza i risultati delle vostre funzioni saranno sbagliati… “Da un grande potere derivano grandi responsabilità”.

Qualche chiarimento sui moduli NodeJS

Un tempo quando nel nostro codice c’era bisogno di qualche funzione “non innovativa” semplicemente si cercava qua e là, si copiava il file/la libreria/il pezzo di codice nel nostro programma e si andava avanti. Spesso – per mancanza di tempo o per semplice pigrizia – nemmeno si studiava bene nemmeno cosa facesse quel codice altrui: era il Cargo Cult Programming.
Con il passare degli anni in ogni settore dello sviluppo software si è cercato di standardizzare e modularizzare codice e librerie in modo da rendere la programmazione più un gioco di incastri che non un lavoro da sartoria, e ora i moduli CommonJS sono un po’ dappertutto… e il Cargo Cult Programming se possibile è anche diventato più forte.

Moduli NodeJS: installazione

Con la nascita di NodeJS e il conseguente lancio del package manager npm il mondo Javascript ha subito una brusca accelerazione dal punto di vista della riusabilità e ora è tutto un’installazione di moduli, ma c’è chi non ha capito bene come funziona. Lo so perché anch’io all’inizio installavo moduli e librerie un po’ a caso, e ancora spesso ho dei dubbi sulla correttezza di quello che sto facendo.

Installazione locale o globale?

Tanto per cominciare ci sono “due modi” di installare un modulo presente su npm: localmente o globalmente. Mentre per installare un modulo localmente (ovvero nella directory corrente da cui si esegue il comando) bisogna scrivere “npm install nome_modulo“, per installare un modulo globalmente bisogna aggiungere l’apposito parametro, facendo diventare il comando “npm install --g nome_modulo

A differenza dell’installazione locale, che come già detto installa il modulo localmente e più precisamente nella directory node_modules/nome_modulo, l’installazione globale scarica il modulo all’interno di una directory di sistema presente sul PATH delle variabili di sistema.
Se non avete cambiato le impostazioni di default di Node, su Windows la directory di installazione dei moduli globali è C:\Users\nome_utente\AppData\Roaming\npm.
Il motivo per cui i moduli globali vengono installati dove sono accessibili da console è abbastanza evidente… sono installati con questo scopo. Non solo i moduli installati globalmente sono richiamabili da console, sono richiamabili solo da console. Infatti devono essere installati globalmente i moduli che servono come “strumenti” (Bower, Grunt, Cordova, …), al contrario delle “librerie” che invece devono essere installate localmente (Underscore, Express, …).

C’è poi un terzo caso, in cui parlare di installazione è improprio: il modulo creato da noi e non presente su npm.

L’utilizzo/inclusione del modulo, ovvero la require

La require non solo rende possibile utilizzare la libreria scelta all’interno del proprio script, ma ne effettua anche il caching in modo che – se all’interno dell’applicazione si richiede nuovamente la stessa libreria – Node non ha bisogno di andarla a leggere di nuovo su filesystem.
Includere un modulo all’interno del nostro script così da poterlo usare è semplice, ma non è così scontato capire dove Node va a cercare i file quando scriviamo require('nome_modulo').
Premessa: non serve specificare l’estensione del file (.js) perché Node cerca di default i file aggiungendovi quest’estensione.

Require con specifica del path

var customModule1 = require('./custom_module1');
var customModule2 = require('../../custom_module2');
var customModule3 = require('/home/custom_module3');

Forse il caso più semplice, Node importa il modulo specificato andandolo a cercare nel percorso assoluto specificato, o in quello relativo rispetto allo script in cui è stata chiamata la require.

Require con specifica del solo nome della libreria

var http = require("http");
var _ = require("underscore");

Qui la situazione si complica.
Per prima cosa Node cerca il modulo tra quelli “core”, cioè i moduli rilasciati di default con Node (es: http, fs, path, …). Se il modulo non è tra quelli preinstallati (es: underscore) Node inizia a ricercare il modulo all’interno della prima directory node_modules che riesce a trovare partendo dalla directory corrente (quella dello script) e risalendo la gerarchia fino ad arrivare alla directory root.
C’è da dire anche che il “modulo” può essere semplicemente un file javascript nome_modulo.js ma anche una directory nome_modulo contenente un index.js piuttosto che un nome_modulo.js e tante altre risorse, compresa un’eventuale directory node_modules in cui sono contenute tutte le dipendenze del modulo.
Lo so, è un po’ un casino e forse ho sintetizzato troppo, il punto è che capirete meglio andando a scartabellare la directory node_modules globale e provando a installare qualche modulo locale qua e là vedendo come e quando Node lancia l’errore “Cannot find module ‘nome_modulo'” se la require fallisce.

Scrittura di un semplice modulo: exports

Un piccolo esempio pokeristico, sperando che conosciate questi termini. Se non li conoscete va bene uguale ma mi dispiace per voi.

var fold = function(){
  console.log("fold");
};
module.exports = fold;

Il modulo “weak” è estremamente basilare, ed esporta una sola funzione.

var bet = function(){
  console.log("bet");
};

exports.bet = bet;
exports.raise = function(){
  console.log("raise");
};
exports.when = "When I'm strong and/or in position and/or I want to convince my opponents that I'm strong";

Il modulo “strong” utilizza l’exports un po’ di più, collegandovi non una ma due funzioni, e una stringa tanto per fare mucchio. La prima funzione – bet – viene dichiarata e definita in una variabile che poi viene esportata, a differenza della seconda – raise – che viene assegnata direttamente all’omonima proprietà dell’oggetto exports. Il risultato è esattamente lo stesso.

var weak = require('./weak');
var strong = require('./strong');

weak();
strong.bet();
strong.raise();

console.log(weak);
console.log(strong);

Qui possiamo vedere che il modulo “weak” nient’altro non è che la funzione esportata, che infatti può essere richiamata direttamente. Strong è invece un oggetto Javascript contenente tre proprietà: una stringa e due funzioni – bet e raise – che in quanto tali possono essere eseguite.

Dubbi amletici: exports o module.exports?

I più attenti avranno notato una differenza tra weak.js e strong.js… e non serve nemmeno essere troppo attenti considerato che c’è scritto sul titolo. Che differenza c’è tra la variabile “exports” e la “module.exports”?
In pratica nessuna, perché exports è semplicemente un riferimento a module.exports. Si tratta di zucchero sintattico che serve a velocizzare la scrittura del modulo e a evitare che per sbaglio rendiamo la nostra vita amara andando ad assegnare qualcosa a module.exports, rompendo così l’interfaccia tra il modulo e i suoi utilizzatori.

Conclusioni

Modulare è bello, modulare è “c’è un sacco di roba gratis e si trova tutta lì”, modulare è veloce. Però è importante capire bene quali sono le regole perché altrimenti i pezzi non si incastrano e bisogna fare come gli astronauti dell’Apollo 13 con i filtri per l’anidride carbonica.
Parlando di installazioni ho omesso che quando si installa un modulo bisognerebbe anche preoccuparsi di quale versione installare, perché la pappa pronta è buona ma qualcuno potrebbe aver fatto delle modifiche alla ricetta dall’ultima volta, e potremmo ritrovarci con un’intolleranza. E poi bisogna spiegare al cliente che siamo stati sfortunati.

Concatenare stringhe in Javascript con la funzione join

La concatenazione di stringhe per molti potrà sembrare un argomento di poco conto… e infatti lo è anche per me. Ciononostante vorrei affrontare l’argomento perché c’è una tecnica che aiuta a mantenere il codice pulito ed è anche efficiente.
In Java per ottimizzare l’utilizzo delle memoria quando si lavora con molte stringhe c’è la classe StringBuilder, qui non abbiamo classi particolari ma la funzione join dell’array produce un risultato molto simile allo StringBuilder.

Vediamo prima un classico esempio di concatenazione di stringhe con carattere separatore tra una e l’altra.

var arr, i, sum, beginTime;
 
arr = new Array(100000);
for (i=arr.length-1;i>0;i--){ arr[i] = ""+i; }
 
// first
sum = "";
beginTime = +new Date();
for (i in arr){
  if (sum){ sum += ","; }
  sum += "" + arr[i];
}
console.log(new Date() - beginTime);

// second
sum = "";
beginTime = +new Date();
for (i in arr){ sum += "" + arr[i]; }
sum = sum.slice(0, sum.length - 1);
console.log(new Date() - beginTime);

// third
sum = "";
beginTime = +new Date();
sum = arr.join(",");
console.log(new Date() - beginTime);

I primi due snippet utilizzano la concatenazione classica con +, ma mentre nel primo caso il carattere separatore viene inserito con un’istruzione condizionale, nel secondo viene inserito sempre rimuovendo il carattere eccedente aggiunto in fondo. Ebbene la tecnica dello slice è sempre più prestante, e il codice più bello.
Il terzo esempio utilizza invece la funzione join degli Array, come promesso nel titolo. In questo caso il codice è estremamente più pulito, perché si prende l’array così com’è e si specifica (l’eventuale) carattere separatore. Il bello è che non si guadagna soltanto in leggibilità e manutenibilità, ma anche la velocità è notevolmente più alta, specialmente su SpiderMonkey.

In altri casi capita di dover comporre delle stringhe avendone dei pezzi, caso tipico una query sql. Lasciamo stare per il momento le sql injection supponendo che i parametri siano opportunamente parsati e la query utilizzi dei placeholder.

var sum =
  " SELECT ... " +
  " FROM ... " +
  " JOIN ... " +
  " JOIN ... " +
  " WHERE ... " +
  " ORDER BY ...";

sum = [
  " SELECT ... ",
  " FROM ... ",
  " JOIN ... ",
  " JOIN ... ",
  " WHERE ... ",
  " ORDER BY ..."
].join();

I volumi in gioco sono così insignificanti che c’è da vergognarsi a confrontarne le prestazioni, e in questo caso specifico la scelta della concatenazione tradizionale o di quella con join è più un fatto di gusti. Resta il fatto che a me la seconda forma piace di più, anche perché si ha sempre il vantaggio di poter specificare un carattere separatore da usare sempre tra un pezzo e l’altro.

Conclusioni

Inutile dilungarcisi troppo: cercate di usare sempre il join quando dovete concatenare delle stringhe contenute in un array, e se non è questo il vostro caso forse è il caso di farcelo diventare comunque, perché tutti quei + in giro per il codice non sono il massimo e ricondurre “gruppi” di stringhe ad array può essere comunque una buona idea.

Cicli in Javascript, ovvero non badate alle performance che è tempo sprecato

Qualche tempo fa avevo seguito un corso online di Javascript, recente e ben fatto, tanto per verificare che le mie conoscenze dello strumento che uso tutti i giorni fossero corrette e aggiornate. Una delle puntate riguardava le performance, e poneva particolare enfasi sull’importanza di scrivere bene i cicli per far evitare all’interprete letture inutili e ottimizzarne così le prestazioni, e con mia grande gioia ho appurato di essere allineato al “professore”. Poi però mi sono ricordato di come un mio amico molto esperto mi disse che secondo lui tutte queste considerazioni sono più dannose che inutili grazie all’efficienza degli ultimi interpreti Javascript, V8 e SpiderMonkey su tutti.

Forse qualcuno potrebbe considerare anacronistico porsi questi problemi nel 2015, specialmente parlando di un linguaggio usato generalmente ad alto livello, ma per me quando si scrive codice bisogna sempre cercare di farlo nel modo migliore, quindi ecco un semplice test.

var arr, i, sum, beginTime, iLimit;

arr = new Array(100000);
for (i=arr.length-1;i>0;i--){ arr[i] = i; }

// first
sum = 0;
beginTime = +new Date();
for (i in arr){ sum += arr[i]; }
console.log(new Date() - beginTime);

// second
sum = 0;
beginTime = +new Date();
for (i=0;i<arr.length;i++){ sum += arr[i]; }
console.log(new Date() - beginTime);

// third
sum = 0;
beginTime = +new Date();
for (i=0, iLimit=arr.length;i<iLimit;i++){ sum += arr[i]; }
console.log(new Date() - beginTime);

// fourth
sum = 0;
beginTime = +new Date();
for (i=arr.length-1;i>0;i--){ sum += arr[i]; }
console.log(new Date() - beginTime);

Sarei tentato di lasciarvi senza responso per farvi copiare e incollare questo codice sulle console di Chrome e Firefox, ma lungi da me essere crudele vi anticipo il verdetto (che potete immaginare leggendo il titolo dell’articolo): scriveteli come vi rimane più comodo.
Le due forme centrali, cioè il ciclo for normale “dall’inizio alla fine” e quello “dall’inizio alla fine con memorizzazione della dimensione”, sono su entrambe le console i più lenti. Ma mentre per il secondo esempio ce lo si poteva aspettare così non è per il terzo, che a detta del “professore” dovrebbe essere quanto meno più veloce del secondo, eppure così non è.

Forse non è corretto includere in questo test il for-in del primo esempio, perché ha una semantica e un utilizzo diversi rispetto agli altri, ma nonostante la sua maggior versatilità per V8 è proprio questo il vincitore, mentre per SpiderMonkey è il “ciclo for a rovescio” a vincere.

Conclusioni

In sostanza se non sapete dove sarà eseguito il vostro codice non avete elementi per stabilire quale forma è migliore, e anche se lo sapeste tutto sarebbe vanificato dal progresso dagli avanzatissimi algoritmi di calcolo messi in campo da Google e Mozilla. Quindi lasciate perdere e scrivete i vostri cicli come vi rimane più comodo, possibilmente a rovescio se il vostro algoritmo ve lo consente, ma evitando inutili complicanze come quella di salvarsi la dimensione dell’array… che io faccio sempre.

Difendersi dai parametri cattivi in Javascript

È da un po’ di tempo che sono alla disperata ricerca dell’ispirazione: di cosa potrei scrivere nel mio primo articolo su questo blog? Probabilmente qualcosa a proposito del linguaggio Javascript, su cui sto lavorando più o meno ininterrottamente da mesi, ma cosa?
Il web è inondato di sviluppatori che scrivono di nuove tecnologie, patterns, librerie e strumenti più o meno complessi, ma purtroppo spesso si tende a bistrattare la sintassi e la pulizia del codice.
Un “trucco” che mi aveva colpito la prima volta che me ne sono venuto a conoscenza è l’inizializzazione delle variabili con valori di default utilizzando gli operatori logici, vedremo quindi di cosa si tratta, come si potrebbe utilizzare questa tecnica e come non dovrebbe essere utilizzata.

Sul campo di battaglia come mamma ci ha fatto

Tutti sappiamo quanto – in questo linguaggio – sia importante cautelarsi controllando che i parametri passati alle nostre funzioni abbiano i valori che ci aspettiamo, e chi più chi meno tutti cerchiamo di avere un approccio difensivo.

var example1 = function(param1, param2, onComplete){
	//...
	
	onComplete();
};

Quando la nostra funzione sarà eseguita non potremo avere certezza che tutti i tre parametri gli saranno passati così come ci aspettiamo, né che questi saranno valorizzati. Essendo inoltre il Javascript un linguaggio a tipizzazione dinamica, non saremo nemmeno sicuri che i tipi delle variabili saranno quelli attesi.

Proteggersi è importante e non lo si fa mai abbastanza

var example2 = function(param1, param2, onComplete){
	param1 = param1 || &amp;amp;quot;Lorem ipsum dolor...&amp;amp;quot;;
	param2 = param2 || false;
	onComplete = _.isFunction(onComplete) ? onComplete : function(){return;};

	//...

	onComplete();
};

Per i primi due parametri abbiamo usato l’operatore OR in cortocircuito, che in questo contesto forza l’assegnazione della variabile al secondo valore (quello dopo il ||) qualora il primo sia “falsy” (false, undefined, null, “”, 0, NaN).
Nel caso del parametro “onComplete” invece non ci siamo accontentati di assegnargli una funzione fittizia nel caso in cui questa variabile non sia stata impostata, ma abbiamo usato la comoda funzione isFunction della libreria underscore, in modo da avere la certezza che onComplete sia – alla fine – una funzione, così da evitare pericolosi errori a runtime.

Le scorciatoie possono condurre alla rovina

L’operatore logico OR in cortocircuito usato per parametri booleani può nascondere dei tranelli, e com’è lecito aspettarsi il più subdolo che mi è capitato di incontrare ha a che fare proprio con valori logici.

var example3 = function(booleanParam){
	booleanParam = booleanParam || true;
	//...
	return booleanParam;
};
console.log(example3(false));

Chi ha scritto questa funzione magari lo ha fatto pensando di assegnare true come valore di default per la funzione booleanParam, in modo da cautelarsi in caso di mancato passaggio di parametri o di arrivo di un null/undefined. La triste realtà è che seppure nelle nostre intenzioni booleanParam dovrebbe avere come valore solo true o false, con true come default, questa variabile sarà sempre valorizzata con true. Questo perché l’assegnazione viene fatta al secondo valore qualora il primo sia “falsy”, e false lo è. In questi casi diffidate quindi di queste scorciatoie e utilizzate tecniche più verbose ma anche più sicure, come ad esempio la funzione isBoolean della libreria underscore, già usata in precedenza.

Conclusioni

Tutti gli esempi mostrati finora soffrono di una debolezza strutturale: se dovessi avere necessità di modificare la firma della funzione aggiungendo dei parametri, rischierei di fare danni a causa del “poco supporto” (per usare un eufemismo) che il compilatore (assente) ci da in questi casi. Nel dubbio io ho cominciato a sfruttare gli object literals per il passaggio dei parametri, così da rendere le mie funzioni un po’ più facili da manutenere e modificare.

var example4 = function(params){
	params = params || {};
	params.param1 = params.param1 || &amp;amp;quot;Lorem ipsum dolor...&amp;amp;quot;;
	params.param2 = params.param2 || false;
	params.onComplete = _.isFunction(params.onComplete) ? params.onComplete : function(){return;};

	//...
	
	params.onComplete();
};
example4({'param2' : true, 'param1' : 'string'});

Secondo la mia esperienza definendo le vostre funzioni in questo modo vi risparmierete alcuni grattacapi quando andrete a modificare la firma delle vostre funzioni, e state tranquilli che prima o poi succederà.
Purtroppo la chiamata della funzione risulterà un po’ più verbosa che non esplicitando tutti i parametri direttamente, ma se è soltanto questo il costo della flessibilità conviene pagare questo dazio e non pensarci più.