Trucchi e suggerimenti per il debugging moderno

Con l’aumentare dei dispositivi mobili, lo sviluppo ed il debugging web è divenuto più complesso che mai: dobbiamo supportare più browser e più piattaforme, ci sono molte più dimensioni di schermo e risoluzioni e stiamo creando applicazioni all’interno del browser invece dei noiosi siti vetrina di un tempo.

L’articolo prosegue sotto

Fortunatamente, abbiamo anche a disposizione degli strumenti migliori. La console JavaScript è una caratteristica standard per la maggior parte dei browser. Sia JavaScript che il DOM HTLM offrono nativamente l’error handling. Ci sono anche servizi ed applicazioni che ci aiutano a fare il debug da remoto dei nostri siti.

In questo articolo tratterò l’error thowing and handling, il code injection ed il debugging per mobile. Per maggiori informazioni sul debugging, si veda l’articolo del 2009 di Hallvord R.M. Steen e Chris Mills, Advanced Debugging With JavaScript.

Error throwing and catching#section1

JavaScript vi permette di tenere traccia e di gestire gli errori attraverso una combinazione di istruzioni throw e trycatch e tramite l’oggetto error.

L’error throwing è utile per cogliere gli errori a runtime, come ad esempio, una funzione che ha gli argomenti sbagliati. Nell’esempio sottostante, add() accetta due parametri. Farà il throw di un errore se gli argomenti che le vengono passati sono nulli o se non sono né un numero né una stringa numerica. [Gli “a capo” sono segnati con », —Ed.]

 
function add(x,y){
     if( isNaN(x) || isNaN(y) ){
          throw new Error("Hey, I need two numbers to add!");
     } else {
	      // ensure we're adding numbers not concatenating »
numeric strings.
	     return (x * 1) + (y * 1);
     }
}

Proviamo a richiamare add() usando degli argomenti non validi. Faremo il catch dell’errore “lanciato” utilizzando un blocco trycatch e lo manderemo in output sulla console:

 
var a;
 
try{
 a = add(9);
} catch(e) {
 console.error( e.message );
}
La error console di Dragonfly

Fig 1: La error console di Dragonfly

In Dragonfly di Opera (illustrato qui sopra), possiamo vedere il messaggio di errore ed il corrispondente numero di riga, relativo allo script. Tenete sempre a mente che in questi esempi stiamo inserendo il JavaScript nella nostra pagina HTML.

La error console di Firebug.

Fig. 2: La error console di Firebug

Firebug include anche il messaggio dell’errore lanciato ed il numero di riga, ma relativamente al documento.

Tutti gli oggetti error hanno tre proprietà standard:

  • constructor: ritorna un riferimento alla funzione Error che ha creato il prototipo dell’istanza,
  • message: il messaggio lanciato, il messaggio che passate come argomento,
  • name: il tipo di errore, solitamente error, a meno di usare un tipo più specifico.

Al momento in cui scrivo, gli oggetti error in Firefox includono anche due proprietà non standard: fileName e lineNumber. Internet Explorer include due proprietà non standard proprietarie: description (che funziona in maniera simile a message) e number (che dà in output il numero di linea).

Anche la proprietà stack non è standard, ma è più o meno supportata dalle versioni più recenti di Chrome, Firefox e Opera. Traccia l’ordine delle chiamate di funzione, con i corrispondenti numeri di linea ed argomenti. Modifichiamo il nostro esempio per allertare stack invece:

 
var a;
 
try{
 a = add(9);
} catch(e) {
 alert( e.stack );
}

Fig 3: La proprietà stack rivela il “throw error” nel codice.

La proprietà stack rivela il punto in cui è presente il codice throw Error (in questo caso, alla riga 7) e su quale riga l’errore è stato fatto scattare (in questo caso, la riga 15).

Non dovete per forza lanciare (throw) un oggetto error. Potreste, ad esempio, lanciare un messaggio: throw "The value of x or y is NaN.". Lanciare un errore, tuttavia, dà maggiori informazioni nella maggior parte dei browser.

Utilizzare trycatch può comunque avere un effetto negativo sulla riduzione della dimensione del codice e sulla performance. Sebbene sia comodo per il debugging, il vostro codice pronto per andare in produzione dovrebbe utilizzare trycatch in maniera molto limitata, se non addirittura evitarlo.

Gestione degli errori con l’evento window.onerror#section2

Anche il Document Object Model offre un meccanismo per catturare gli errori: l’evento window.onerror. A differenza di trycatch, potete impostare un gestore di eventi per window.onerror che catturi gli errori che voi non lanciate. Questo accade se cercate di invocare una funzione non definita o se provate ad accedere ad una variabile indefinita.

Quando viene attivato l’evento window.onerror, il browser farà un controllo per vedere se c’è disponibile una funzione handler. Se non ve ne sono, il browser mostrerà l’errore all’utente. Se invece ce n’è una disponibile, la funzione handler riceve tre argomenti:

  • il messaggio di errore,
  • l’URL nel quale è stato sollevato l’errore e
  • il numero di riga dove si è verificato l’errore.

Si può accedere a questi argomenti in uno dei due modi seguenti:

  1. utilizzando l’oggetto arguments che è nativo e localmente disponibile per tutte le funzioni JavaScript, oppure
  2. utilizzando dei named parameters.

Nell’esempio seguente, useremo arguments. Tuttavia, per favorire la leggibilità, dovreste utilizzare i named parameters:

 
 
window.onerror = function(){
 alert(arguments[0] +'\n'+arguments[1]+'\n'+arguments[2]);
}
 
init(); // undefined and triggers error event.
Il nostro errore appare come un alert in Internet Explorer 9

Fig 4: Il nostro errore appare come un alert in Internet Explorer 9

Qui init() non è stato ancora definito. come risultato, l’evento onerror verrà attivato nei browser che lo supportano.

Avvertimento: il supporto per window.onerror è limitato. Chrome 10+ e Firefox (inclusa la versione mobile) lo supportano. Internet Explorer lo supporta, ma i messaggi d’errore veramente utili si trovano solo dalla versione 9+. Sebbene gli ultimi build di WebKit supportino window.onerror, le versioni recenti di Safari e le versioni leggermente più vecchie di Android WebKit non lo supportano. Anche Opera non lo supporta perfettamente. Aspettatevi che le cose cambino man mano che la specifica di HTML5 si evolve ed i produttori di browser adeguano di conseguenza le loro implementazioni.

Modificare JavaScript al volo usando l’interfaccia a riga di comando#section3

Una delle caratteristiche più potenti disponibile ad oggi nei tool per il debug è la console JavaScript. E’ praticamente una riga di comando (CLI) per JavaScript. Con essa potete fare il dump dei dati o inserire del JavaScript per esaminare il motivo per cui il codice si comporta in maniera inaspettata.

Lanciare la Console JavaScript#section4

  • In Chrome: View > Developer > JavaScript console
  • In Safari: Develop > Show Web Inspector
  • In Internet Explorer 8 & 9: Tools > Developer Tools (oppure utilizzare il tasto F12)
  • In Opera: Cercate Dragonfly in Tools > Advanced (Mac OS X) o Menu > Page > Developer Tools (Windows, Linux)

Firefox è un caso speciale. Per anni, gli sviluppatori hanno utilizzato l’estensione Firebug. Tuttavia, Firefox 4 ha aggiunto una console nativa (Tools > Web Console oppure Menu > Web Developer > Web Console).

Firebug supporta completamente le Console API e ha delle caratteristiche di debugging CSS più robuste. Suggerisco di installarlo, sebbene la Web Console sia un tool adatto alle necessità di base.

Negli esempi qui sotto utilizzo il tool di debug di Opera, Dragonfly (ebbene sì, lavoro per Opera). Questi esempi, comunque, funzionano in maniera simile in Chrome, Safari, Firefox, Firebug e Internet Explorer.

Diamo un’altra occhiata al codice dei nostri esempi precedenti. Aggiungiamo una nuova riga —var a = document.querySelector('#result');—, che assume un elemento con il valore dell’id impostato in “result.”

Rapida nota riguardante il metodo querySelector(): questo e querySelectorAll() fanno parte delle selectors API del DOM. querySelector() ritorna il primo elemento uguale al selettore CSS. Entrambe i metodi sono supportati dalle ultime versioni della maggior parte dei browser. Potreste anche usare document.getElementById('result'), ma document.querySelector() è più efficiente:

 
function add(x,y){
 if( isNaN(x) || isNaN(y) ){
     throw new Error("Hey, I need two numbers to add!");
 } else {
     // ensure we're adding numbers not concatenating numeric strings.
     return (x * 1) + (y * 1);
 }
}
 
var a = document.getElementById('result');
 
try{
     a.innerHTML = add(9);
} catch(e) {
     console.error(e.message);
}
La console Dragonfly

Fig 5: La console Dragonfly

L’errore che abbiamo lanciato è scritto ancora nella console. Ma introduciamo del JavaScript che funziona correttamente. Inseriremo a.innerHTML = add(21.2, 40); nella nostra console:

La console Dragonfly con il codice introdotto

Fig 6: La console Dragonfly con il codice introdotto

Come potete vedere, abbiamo sovrascritto il valore innerHTML di a:

Una pagina con il codice introdotto

Fig 7: Una pagina con il codice introdotto

Adesso cambiamo completamente il valore di a. Inseriamo a = document.querySelector('h1'); a.innerHTML = add(45,2); nella console:

Cambiamo il codice nella console

Fig 8: Cambiamo il codice nella console

Vedete che 47 viene scritto nella console ed è anche il nuovo innerHTML del nostro elemento h1:

Modificare il DOM

Fig 9: Modificare il DOM

Ora, possiamo perfino ridefinire la nostra funzione add(). Facciamo in modo che add() ritorni il prodotto di due argomenti e poi aggiorni l’elemento h1. Inseriamo function add(){ return arguments[0] * arguments[1]; } nella console, seguito da a.innerHTML = add(9,9);:

Sovrascrivere una funzione con la console JavaScript

Fig 10: Sovrascrivere una funzione con la console JavaScript

Il nuovo innerHTML del nostro elemento h1 è adesso 81, ossia il risultato della nostra funzione add ridefinita:

I risultati della sovrascrittura di una funzione

Fig 11: I risultati della sovrascrittura di una funzione

La console JavaScript offre un potente tool per la comprensione del funzionamento del nostro codice. E’ ancora più potente quando la si usa con i dispositivi mobili.

Debugging da remoto per mobile#section5

Il debug del codice su un dispositivo mobile è ancora uno dei punti più dolenti. Ma, ancora una volta, adesso abbiamo dei tool. Dragonfly di Opera e la sua feature per il debug remoto fornisce agli sviluppatori un modo per debuggare i siti mobile dalla propria postazione desktop. Recentemente, WebKit ha aggiunto il remote debugging al suo core e Google Chrome l’ha già inserito nei suoi developer tool.

Gli sviluppatori indipendenti offrono prodotti simili per altri browser. Tra questi citiamo Bugaboo, una app iOS per il debug basata su Safari, JS Console che è disponibile sul web o come app iOS e Weinre per i browser basati su WebKit.

Esaminiamone due: Dragonfly remote debug e JSConsole.

Remote debugging con Opera Dragonfly#section6

Il punto di forza di Dragonfly è che vi si può fare il debug dei CSS o degli header (si veda il Network tab) oltre che di JavaScript. Però richiede l’installazione di Opera sul proprio desktop e di Opera Mobile sul proprio device.

Entrambe i device devono poi essere connessi alla stessa rete locale. Vi servirà anche l’indirizzo IP della macchina su cui è in esecuzione Dragonfly. Poi completate i seguenti passi:

  1. aprite Dragonfly dal menu Tools > Advanced (Mac OS X) o Page > Developer Tools (Windows, Linux),
  2. cliccate il pulsante Remote Debug The Remote Debugger button,
  3. se volete potete modificare il numero della porta o utilizzare quella di default e cliccare su “Apply, ”
    Il pannello di remote debugging in Dragonfly

    Fig 12: Il pannello di remote debugging in Dragonfly

  4. aprite Opera Mobile sul device su cui volete fare il debug e scrivete opera:debug nella barra degli indirizzi
    la debug console di Opera Mobile

    Fig 13: La debug console di Opera Mobile

  5. inserite l’indirizzo IP ed il numero di porta della macchina host e cliccate su “Connect”
    I campi IP e Port della console opera:debug

    Fig 14: I campi IP e Port della console opera:debug

  6. puntate all’URL della pagina HTML che volete debuggare sul vostro device.
Un'alert su Opera Mobile

Fig 15: Un’alert su Opera Mobile

Dragonfly sulla macchina host caricherà la pagina remota. Potrete quindi interagire con la pagina come se fosse sul vostro desktop. Vedrete i risultati sul device. Ad esempio, se inserite alert( add(8,923) ) sulla console host, l’alert appare sullo schermo del device mobile.

Remote debugging con JSConsole#section7

JSConsole è un servizio web-based e browser-independent. A differenza di Bugaboo, Weinre e Dragonfly, il vostro computer ed il vostro device non devono essere connessi alla stessa rete locale.

Per usare JSConsole:

  • visitate il sito ed inserite :listen nel prompt,
  • aggiungete al documento che volete debuggare il tag che ritorna lo script,
  • aprite il documento sul vostro device mobile.

Le istruzioni sulla console remota appariranno nella finestra di JSConsole (dovete usare console.log() piuttosto che console.error() o console.warn()). Potete anche inviare il codice dalla finestra JSConsole al vostro device. In questo caso, alert( add(6,3) );.

Invio di un comando utilizzando JSConsole.com

Fig 16: Invio di un comando utilizzando JSConsole.com

Remote error logging#section8

Nell’esempio di cui sopra, abbiamo fatto il tracciamento sulla console o abbiamo lanciato un’alert box. Cosa succederebbe se invece avessimo tracciato gli errori su uno script server-side?

Considerate il seguente codice che utilizza XMLHttpRequest().

 
function sendError(){
     var o, xhr, data, msg = {}, argtype = typeof( arguments[0] );
 
     // if it is an error object, just use it.
     if( argtype === 'object' ){
     	  msg = arguments[0];
     }
 
     // if it is a string, check whether we have 3 arguments...
     else if( argtype === 'string') {
     // if we have 3 arguments, assume this is an onerror event.
          if( arguments.length == 3 ){
              msg.message    = arguments[0];
              msg.fileName   = arguments[1];
              msg.lineNumber = arguments[2];
          }
        // otherwise, post the first argument
          else {
              msg.message    = arguments[0];
          }
      }
 
     // include the user agent
     msg.userAgent = navigator.userAgent;
 
	 // convert to JSON string
	 data = 'error='+JSON.stringify(msg);
 
     // build the XHR request
     xhr = new XMLHttpRequest();
     xhr.open("POST",'./logger/');
     xhr.setRequestHeader("Content-type", "application/x-www- »
     form-urlencoded");
     xhr.send( data );
 
 
     // hide error message from user in supporting browsers
     return true;
}

Qui stiamo postando i nostri messaggi di errore su uno script che li logga in un file utilizzando PHP:

 
<?php
 
// decode the JSON object.
$error = json_decode( $_POST['error'], true );
$file = fopen('log.txt','a');
fwrite($file, print_r( $error, true) );
fclose($file);
 
?>

E adesso il disclaimer: per favore, per l’amore della tequila, non permettete allo script di scrivere su una directory che sia leggibile da tutti. Non vale la pena correre il rischio di un potenziale attacco tramite code injection dovuto agli spoofed headers o alle variabili. Il tracciamento di script come questo dovrebbe essere usato soltanto durante lo sviluppo e mai sui server di produzione.

Conclusione#section9

I tool a nostra disposizione si sono evoluti di pari passo con il web. Il code injection, l’error throwing and catching ed i servizi di remote debugging hanno tutti contribuito a farci distribuire app migliori e con meno errori.

Illustrazioni: {carlok}

Sull’autore

Tiffany B. Brown

Tiffany B. Brown è membro del team Opera Software Developer Relations, dove si batte per un web aperto ed interoperabile. Vive a Los Angeles, ama New Orleans e prepara dei discreti Manhattan.

Nessun commento

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Altro da ALA

Webwaste

In questo estratto da World Wide Waste, Gerry McGovern esamina l'impatto ambientale di siti web pieni zeppi di asset inutili. Digital is physical. Sembra economico e gratis ma non lo è: ci costa la Terra.
Industry