Back

TD3: Clean coding

Objectif

Le principe de ce TP sera de réfléchir à l'application des concepts du clean code sur un petit programme en JavaScript. En outre, il donnera l'occasion d'effectuer une première pull-request et de réaliser une première code review.

Le principe du programme est de réaliser une petite calculatrice qui permet de donner le résultat d'une formule mathématique écrite en utilisant la notation polonaise inversée.

Pour cela vous allez partir du squelette HTML suivant:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Calculator</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link rel="stylesheet" href="">
    <script type="module" defer>
        // écriture du programme
    </script>
</head>

<body>
    <input type="text" size="50" id="formula" name="formula">
    <button id="calc">Calc</button>
    <div id="result"></div>
</body>

</html>

Complétez le squelette pour qu'en cas de click sur le bouton calc, le résultat de la formule saisie dans le champ texte soit affiché dans le div prévu. Attention veillez à travailler sur un fork du votre dépôt ou sur une branche. Il faut un fork ou une branche par membre du groupe. Le programme doit être réalisé dans un répertoire nommé clean-code.

Une fois le squelette complété, veuillez ouvrir une pull-request par membre du groupe. En utilisant l'interface de GitHub procédez alors à la code review de la pull-request d'un autre membre du groupe, en utilisant les commentaires des pull-requests et les annotations directes dans le code. Lors de la code review, concentre vous sur les aspects de qualité structurelle, notamment en relation avec le clean-code vu en cours. Vous pouvez aussi remonter des problèmes de qualité fonctionnelles

class Tokenizer {
    static numberRegexp = /[0-9](\.[0-9])?/;
    static operatorRegexp = /[+\/\-*]/;

    constructor(formula) {
        if (!formula)
            throw new Error("Empty formula");
            
        this.tokens = formula.split(" ");
    }

    /**
        * @return the next token in the formula, either a Number or an Operator, or undefined if 
        * it has reached the end of the formula.
        */
    nextToken() {
        const token = this.tokens.shift();
        if (token == undefined)
            return token;
        else if (token.match(Tokenizer.numberRegexp))
            return Number(token);
        else if (token.match(Tokenizer.operatorRegexp))
            return Tokenizer.operatorFromToken(token);
        else
            throw new Error("Invalid token: " + token);
    }

    static operatorFromToken(token) {
        switch(token) {
            case "+":
                return new Plus();
            case "-":
                return new Minus();
            case "*":
                return new Times();
            case "/":
                return new Divided();
            default:
                throw new Error("Invalid operator: " + token);
        }
    }
}

class Operator {
    apply(lhs, rhs) {
        throw new Error("You must implement this function");
    }
}

class Plus extends Operator {
    apply(lhs, rhs) {
        return lhs + rhs;
    }
}

class Minus extends Operator {
    apply(lhs, rhs) {
        return lhs - rhs;
    }
}

class Times extends Operator {
    apply(lhs, rhs) {
        return lhs * rhs;
    }
}

class Divided extends Operator {
    apply(lhs, rhs) {
        if (rhs === 0)
            throw new Error("Division by zero");
        
        return lhs / rhs;
    }
}

function calculate(formula) {
    const tokenizer = new Tokenizer(formula);
    const stack = [];
    let token = tokenizer.nextToken();
    while (token != undefined) {
        if (typeof token === 'number')
            stack.push(token);
        else if (token instanceof Operator) {
            const lhs = stack.pop();
            const rhs = stack.pop();
            const result = token.apply(lhs, rhs);
            stack.push(result);
        }
        else 
            throw new Error("Unrecognized token " + token);
        
        token = tokenizer.nextToken();
    }

    if (stack.length == 1)
        return stack.pop();
    else
        throw new Error("Invalid formula " + formula);
}

document.getElementById("calc").addEventListener("click", function() {
    const output = document.getElementById("result")
    try {
        const formula = document.getElementById("formula").value
        const result = calculate(formula);
        output.innerText = result;
    }
    catch (error) {
        output.innerText = error;
    }
});