Qualche settimana fa stavo osservando il progetto di laurea di un mio amico. Si trattava di un'implementazione in Java di un semplice gioco multiplayer client-server a turni. Dato che sarebbe stato valutato di lì a poco, era necessario correggere i bugs e gli errori di stile e di ingegnerizzazione in poco tempo: per far questo, in moltissimi metodi sono stati aggiunti dei blocchi try per loggare tutte le eccezioni verificate. Dalla parte del server, sarebbe comunque stato vantaggioso lasciare questa parte dedicata al logging anche dopo il termine del progetto, poiché l'amministratore avrebbe potuto gestire e manutenere il programma grazie a tali reports.

Mi ha colpito il fatto che non sia possibile, in Java, ma soprattutto in C#, implementare un oggetto logger universale. Tuttavia mi è subito venuto in mente che in un linguaggio un po' più permissivo si potrebbe fare. Ho pensato al JavaScript... No! Non è uno scherzo! La flessibilità delle funzioni JavaScript permette di fare questo ed altro. L'idea di base è la seguente.

Vogliamo loggare ogni eccezione che si verifichi nel dominio di un certo oggetto. Per far questo si potrebbe impacchettare ogni funzione (metodo) dell'oggetto in un'ulteriore funzione con l'unico scopo di catturare le eccezioni e notificare i dettagli sulla console. Ma questo è fattibilissimo in JavaScript. Infatti ogni oggetto può essere visto come un hash, ossia un dizionario. Perciò possiamo enumerare tutti i suoi membri con un comunissimo for. Tra questi è possibile vedere quali sono funzioni usando typeof, che restituisce "function" in quel caso. Dopodiché possiamo sfruttare la (eccessiva, dico io) permissività del linguaggio nel passaggio di parametri: anche se una funzione viene definita senza parametri, il chiamante gliene può passare quanti ne vuole e tutti saranno registrati nella variabile arguments, che fa parte del contesto di ogni funzione. Ecco, quindi, che cosa ho prodotto:

// Oggetto di prova
var obj = 
    {
        firstName : "Aldo",
        lastName : "Baglio",
        sayHello : function() { alert("Hello! I'm " + this.firstName + " " + this.lastName + "!"); throw "Error";}
    };

function logExceptions(obj)
{
    for(var key in obj)
    {
        if (typeof(obj[key]) == "function")
        {
            var oldFunc = obj[key];
            var newFunc = function() 
            {
                try
                {
                    oldFunc.apply(obj, arguments);
                }
                catch(e)
                {
                    if (e.message == null && e.toString().length <= 0)
                        return;
                        
                    var funcName = obj[key].name;
                    funcName = (funcName != null && funcName.length > 0) ? "'" + funcName + "'" : "anonymous";
                    var message = (typeof(e) == "object" && e.message != null) ? e.message : e.toString();
                    var errorName = (e.name != null ? e.name : "Generic Error");
                    
                    console.log(errorName + " occurred in " + funcName + " function:");
                    console.log("Error message: " + message);
                    console.log("Arguments: ");
                    console.log(arguments);
                }
            };
            obj[key] = newFunc;
        }
    }
}

logExceptions(obj);
// Notate che invoco sayHello con un parametro nonostante la sua dichiarazione non ne richieda
obj.sayHello("hello");

Potete verificare che il codice passa effettivamente tutti i parametri passati alla funzione wrapper anche alla funzione wrappata. Infatti apply, a differenza di call, permette di usare un array come specifica per la lista di parametri.

 

Et voilà! Basta chiamare logExceptions su un oggetto per loggarne tutti i metodi automaticamente, ed è possibile personalizzare il report modificandone il codice una sola volta. Quando non avete più bisogno del logger, basta cancellare la chiamata a logExceptions. Semplice, no?