Refactoring del codice jQuery

Refactoring del codice jQuery

Si prospettano tempi duri per quanto riguarda il codice che ho scritto un anno fa, ossia tempi di refactoring. Cambiare il codice interno di un applicativo jQuery è sicuramente qualcosa che spesso ci spaventa. Infatti l'applicazione funziona, ma nel frattempo i tool a disposizione sono cambiati, inclusa jQuery, e quindi una sola modifica può avere effetti imprevisti sull'intero codice. In questo articolo vorrei esporvi l'approccio che intendo seguire nel futuro.

Verifica preliminare

L'applicazione App è uno showcase in cui le animazioni jQuery vengono invocate per ciascuna slide. Ciascuna slide presenta effetti simili ma leggermente diversi l'uno dall'altro.

A questo si aggiunge lo scorrimento sulle slide attraverso i bottoni di controllo. I tool usati, oltre a jQuery, sono:

  1. Il plugin Easing.
  2. Gli effetti drop e blind di jQuery UI.

La versione di jQuery usata è la 1.5. Quindi occorre porsi alcune domande:

  1. Se aggiorniamo la versione di jQuery, cosa succederà alle sue dipendenze?
  2. Dato che l'applicazione dovrà funzionare sempre anche con Internet Explorer 8-, conviene aggiornare jQuery?

Occorre quindi fare dei test:

  1. Scarichiamo l'ultima versione del plugin Easing.
  2. Scarichiamo l'ultima versione degli effetti di jQuery UI.
  3. Scarichiamo l'ultima versione di jQuery.
  4. Creiamo un set composto da sole tre slide, ma complete.
  5. Testiamo scorrimento ed animazioni nei vari browser.

Se i risultati del test effettuato al punto 5 sono positivi, possiamo aggiornare le librerie in nostro possesso. Altrimenti, manteniamo inalterata la versione delle librerie esistenti, ossia non le cambiamo.

Analisi

Quello che dobbiamo fare è cercare di semplificare il codice evitando le ridondanze ed eliminando il codice superfluo. Dobbiamo anche eliminare gli errori di design presenti nel codice. Dall'analisi emerge il seguente quadro:

Scarsa flessibilità

Il singleton usato per creare il namespace dell'applicazione ha un solo metodo pubblico che carica gli altri metodi in sequenza. Manca la possibilità di estendere l'oggetto con nuovi metodi. Quindi se il cliente vuole creare un'altra versione dell'applicazione in una lingua diversa ma con nuovi effetti, occorre aggiungere un altro metodo al nostro oggetto. Ma il metodo andrebbe ad aumentare il peso della libreria, mentre a noi interessa solo avere un effetto diverso su un'altra pagina.

Prima avevamo:


var App = new function() {

	// metodi
	
	
	this.autoload = function() {
	
		for(var i in this) {
		
			if(typeof this[i] === 'function' && this[i] !== arguments.callee) {
			
				this[i]();
			
			}
		
		}
	
	
	};

}();

Dobbiamo dare la possibilità di aggiungere un metodo da eseguire a parte. Dato che il caricamento avverrà all'interno dell'evento ready(), una soluzione quick and dirty sarebbe:


$(function() {

	App.autoload();
	
	App.method = function() {
	
		//...
	
	};
	
	App.method();


});

Questo è contrario al nostro scopo, perchè noi vogliamo semplificare oltre che aggiungere un metodo. Quindi:


var App = new function() {

	// metodi
	
	
	this.autoload = function() {
	
		for(var i in this) {
		
			if(typeof this[i] === 'function' && this[i] !== arguments.callee) {
			
				this[i]();
			
			}
		
		}
		
		if(arguments.length > 0) {
		
			for(var j = 0; j < arguments.length; j++) {
			
				var method = arguments[j];
				
				if(typeof method !== 'undefined' && typeof method === 'function') {
				
					arguments[j]();
				
				}
			
			}
		
		
		}
	
	
	};

}();

In pratica adesso il nostro metodo non solo esegue i metodi dell'oggetto, ma può eseguire un determinato numero di funzioni anonime passate come argomento:


$(function() {

	App.autoload(function() {
	
		console.log('a'); // 'a'
	
	}, function() {
	
		console.log('b'); // 'b'
	
	});


});

Il codice viene eseguito sempre all'interno del nostro namespace, ma opera sulla pagina.

Ridondanza

La causa principale della ridondanza del codice è dovuta alle animazioni jQuery. Abbiamo detto che le animazioni sono simili, quindi invece di creare metodi mastodontici per gestirle, possiamo semplificare il codice con metodi più semplici:


var move = function(params) {

  params.element.animate(params.styles, params.speed);

};

E avremo:


move({
	element: $('#test'),
	styles: {
		bottom: 40,
		left: 20
	},
	speed: 800
});

Usando un oggetto per gestire i parametri del metodo, possiamo astrarre una casistica di animazioni:


var anims = {

	toTop: {
	
		bottom: 40,
		left: 20
	
	},
	
	toBottom: {
	
		top: 40,
		left: 20
	
	}
};

Quindi avremo:


move({
	element: $('#test'),
	styles: anims.toTop,
	speed: 800
});

Se poi al cliente non va bene l'effetto, dobbiamo solo cambiare qualche valore nell'oggetto in cui abbiamo registrato i valori. Volendo possiamo far accettare ai nostri metodi una funzione di callback per le animazioni in sequenza:


var move = function(params) {

	var defaults = {
	
		element: null,
		styles: {},
		speed: 500,
		callback: function() {}
	
	};
	
	params = $.extend(defaults, params);
	
	params.element.animate(params.styles, params.speed, params.callback);  


};

Abbiamo usato la stessa tecnica vista per i plugin di jQuery, solo che stavolta è stata applicata ad un metodo di un oggetto. Un altro problema dell'applicazione è che ad ogni scorrimento dello slideshow va eseguito un ciclo di animazioni.

Potremmo eseguire un costrutto switch su ciascuna slide per verificare l'azione da eseguire e astrarre un metodo di animazione che esegua del codice in base al contesto passato come argomento. Ma anche in quel caso non sappiamo cosa animare.

Quindi la prima cosa da fare è aggiungere una classe CSS agli elementi che vorremmo animare:


<div class="slide">
	<img src="figure1.jpg" alt="" class="animated" />
</div>

Ora sappiamo quali elementi animare, ma non gli effetti da usare. Possiamo aggiungerli tramite i nuovi attributi data di HTML5:


<div class="animated box" data-anim="{top: 100, left: 100}"></div>

Possiamo usare il metodo data() di jQuery per reperire la stringa contenuta nel nostro attributo custom e quindi convertirla in oggetto letterale (a causa della retrocompatibilità siamo costretti ad usare eval()):


var element = $('div.animated').eq(0);
var data = element.data('anim');
var styles = eval('(' + data + ')');

move({
	element: element,
	styles: styles,
	speed: 800
});

In questo modo semplifichiamo di molto le nostre routine di animazione dividendo i compiti tra jQuery e la pagina su cui opera.

Conclusioni

Tramite il refactoring possiamo creare codice jQuery altamente flessibile, scalabile e manutenibile. Il punto sta sempre nel trovare il giusto equilibrio tra le nostre aspettative e i requisiti del progetto.

Torna su