JavaScript-foutafhandeling anti-patroon

Een van de gebieden waar volgens mij onvoldoende discussie over is, is foutafhandeling in JavaScript. Hoewel er doorgaans veel aandacht wordt besteed aan foutafhandeling in serversoftware, compleet met foutenlogboeken en bewakingssystemen, is er weinig nadruk op hetzelfde voor JavaScript. Ik heb geprobeerd het bewustzijn hiervan te vergroten met mijn Ajax Experience-lezing, Enterprise JavaScript Error Handling, waar ik zowel benaderingen voor foutafhandeling als veelvoorkomende foutbronnen besprak.

Een van mijn suggesties in het gesprek was om een ​​foutopsporingsmodus voor uw toepassing te bieden. Het idee is dat de productiemodus JavaScript-fouten voor de gebruiker verbergt en deze op de juiste manier afhandelt, terwijl de foutopsporingsmodus fouten toestaat om naar het browserniveau te borrelen en zoals gewoonlijk te worden gerapporteerd. Dit laatste is natuurlijk belangrijk voor het debuggen. Wanneer de fout in de browser verschijnt, hebt u de mogelijkheid om fouten op te sporen met alle omringende contextinformatie. Het patroon dat ik in mijn talk voorstelde, ziet er als volgt uit:

function doSomething(value){
    try {
        process(value);
    } catch (ex){
        if (debugMode){
            throw ex;
        } else {
            log(1, "doSomething(): " + ex.message);
        }
    }
}

Het idee hier is dat de fout wordt opgevangen en, afhankelijk van de modus, iets passends doet. Zoals vaak gebeurt in ontwikkeling, heb ik nu ontdekt dat dit niet de beste aanpak is en eigenlijk een pijnlijk gevolg met zich meebrengt.

Als er een fout optreedt in process() , die fout wordt opgevangen en gegooid vanaf doSomething() , die de call-stack verstoort. De fout is nu te ver verwijderd van de werkelijke gebeurtenis om bruikbaar te zijn voor foutopsporing. Alle contextinformatie die tot een oplossing zou kunnen leiden, gaat verloren zodra de uitvoering process() . afsluit . Stelt u zich eens voor dat uw debugger is ingesteld om op alle fouten te breken:als u deze code gebruikt, zou de onderbreking plaatsvinden op de regel met throw ex wanneer je echt wilt dat het in process() breekt want daar zit het eigenlijke probleem.

Ik beschouw dit nu als een foutafhandeling anti-patroon, een patroon dat nuttige foutopsporing voorkomt in plaats van het in te schakelen. Het patroon dat ik nu aanbeveel is om de try-catch . volledig te verwijderen statement in debug-modus. Dit zorgt voor normale code-uitvoering en zal resulteren in de juiste plaatsing van de call-stack wanneer er een fout optreedt. Er zijn een aantal manieren om dit patroon te bereiken, de eerste is een nogal lelijk ogende conditionele verklaring:

function doSomething(value){
    if (debugMode){
        process(value);
    } else {
        try {
            process(value);
        } catch (ex){
            log(1, "doSomething(): " + ex.message);
        }
    }
}

De tweede, aantoonbaar elegantere benadering is om simpelweg de hele functie te vervangen op basis van de uitvoeringsmodus:

var doSomething = debugMode ?
    function(value){
        process(value);
    } :
    function(value){
        try {
            process(value);
        } catch (ex){
            log(1, "doSomething(): " + ex.message);
        }
    };

Dit is mijn voorkeursaanpak omdat het het controleren van debugMode . overbodig maakt elke keer dat de functie wordt uitgevoerd. Bovendien is deze aanpak eenvoudig te automatiseren. Stel dat u een of meer objecten heeft en u wilt dat al hun methoden een wrapper in productie hebben om fouten op te vangen. De volgende code doet dit vrij eenvoudig:

//by Nicholas C. Zakas (MIT Licensed)
function productionize(object){

    var name,
        method;

    for (name in object){
        method = object[name];
        if (typeof method == "function"){
            object[name] = function(name, method){
                return function(){
                    try {
                        return method.apply(this, arguments);
                    } catch (ex) {
                        log(1, name + "(): " + ex.message);
                    }
                };

            }(name, method);
        }
    }
}

Deze code herhaalt de eigenschappen van een object en vervangt elke functie door een andere functie die het juiste foutafhandelingsmechanisme bevat. U kunt de functie als volgt gebruiken:

var system = {
    fail: function(){
        throw new Error("Oops!");
    }
};

function log(severity, message){
    alert(severity + ":" + message);
}

if (!debugMode){
    productionize(system);
}

system.fail();   //error is trapped!

Dit patroon van foutopsporing zal u goed van pas komen in complexe omgevingen waar fouten moeilijk op te sporen zijn. Ervoor zorgen dat de fout vanaf de juiste plaats wordt gegenereerd, is de eerste stap bij het debuggen van het probleem.