Refactoring
Unser Rechner funktioniert, aber der Code ist noch etwas sperrig. Schauen wir, was sich vereinfachen lässt.
Zuerst suchen wir Codestellen, die wir mehrmals verwenden. Diese können wir in eine Funktion auslagern.
A calculate function
Section titled “A calculate function”Wir haben diesen Codeblock zwei Mal verwendet:
let newResult;if (operator === 'plus') newResult = firstValue + secondValue;if (operator === 'minus') newResult = firstValue - secondValue;if (operator === 'times') newResult = firstValue * secondValue;if (operator === 'divide') newResult = firstValue / secondValue;Wir können diesen Teil in eine Funktion calculate auslagern. Zuerst kopieren wir einfach den Teil in die Funktion hinein:
function calculate() { let newResult; if (operator === 'plus') newResult = firstValue + secondValue; if (operator === 'minus') newResult = firstValue - secondValue; if (operator === 'times') newResult = firstValue * secondValue; if (operator === 'divide') newResult = firstValue / secondValue;}Die Funktion calculate muss ein Ergebnis liefern, daher ergänzen wir ein return:
function calculate() { let newResult; if (operator === 'plus') newResult = firstValue + secondValue; if (operator === 'minus') newResult = firstValue - secondValue; if (operator === 'times') newResult = firstValue * secondValue; if (operator === 'divide') newResult = firstValue / secondValue; return newResult;}Nächste Vereinfachung: wir können uns die Variable newResult sparen, indem wir das early return pattern verwenden:
function calculate() { if (operator === 'plus') return firstValue + secondValue; if (operator === 'minus') return firstValue - secondValue; if (operator === 'times') return firstValue * secondValue; if (operator === 'divide') return firstValue / secondValue;}Nun zu den Argumenten. calculate benötigt drei Argumente: operator, firstValue und secondValue. Diese fügen wir als Parameter hinzu:
function calculate(firstValue, operator, secondValue) { if (operator === 'plus') return firstValue + secondValue; if (operator === 'minus') return firstValue - secondValue; if (operator === 'times') return firstValue * secondValue; if (operator === 'divide') return firstValue / secondValue;}Und so wenden wir die Funktion calculate() an:
// in the operator sectionif ( previousButtonType !== 'operator' && previousButtonType !== 'equal' && typeof firstValue === 'number' && operator) { const newResult = calculate(firstValue, operator, secondValue); display.textContent = newResult; calculator.dataset.firstValue = newResult;} else { // ...}
// n the equal sectionif (typeof firstValue === 'number' && operator) { const newResult = calculate(firstValue, operator, secondValue); display.textContent = newResult; calculator.dataset.firstValue = newResult; calculator.dataset.modifierValue = secondValue;} else { // ...}Improving calculate
Section titled “Improving calculate”Bevor wir eine Berechnung machen, müssen wir prüfen, ob firstValue und secondValue eine Zahl sind. Im Moment machen wir das, bevor wir calculate benutzen, und zwar sowohl im Abschnitt Operator als auch Equal:
if (buttonType === 'operator') { // ...
const firstValue = parseFloat(calculator.dataset.firstValue); const operator = calculator.dataset.operator; const secondValue = parseFloat(result);
if (/* ...*/) { // calculate goes here }}if (buttonType === 'equal') { const firstValue = parseFloat(calculator.dataset.firstValue); const operator = calculator.dataset.operator; const modifierValue = parseFloat(calculator.dataset.modifierValue); const secondValue = modifierValue || parseFloat(result);
if (/*...*/) { // calculate goes here }}Wenn wir parseFloat in calculate auslagern, brauchen wir es nicht mehr zwei Mal prüfen:
function calculate(firstValue, operator, secondValue) { firstValue = parseFloat(firstValue); secondValue = parseFloat(secondValue);
if (operator == 'plus') ... // ...}Anwendung (wir müssen auch nicht mehr auf den Typ number prüfen!):
if (buttonType === 'operator') { // ... // Removed need to parseFloat const firstValue = calculator.dataset.firstValue; const operator = calculator.dataset.operator; const secondValue = result;
if ( previousButtonType !== 'operator' && previousButtonType !== 'equal' && // Removed need to check for numbers with 'typeof' firstValue && operator ) { const newResult = calculate(firstValue, operator, secondValue); display.textContent = newResult; calculator.dataset.firstValue = newResult; } else { calculator.dataset.firstValue = result; } // ...}if (buttonType === 'equal') { // Removed need to parseFloat const firstValue = calculator.dataset.firstValue; const operator = calculator.dataset.operator; const modifierValue = calculator.dataset.modifierValue; const secondValue = modifierValue || result;
// Removed need to check for numbers with 'typeof' if (firstValue && operator) { const newResult = calculate(firstValue, operator, secondValue) { display.textContent = newResult; calculator.dataset.firstValue = newResult; calculator.dataset.modifierValue = secondValue; } else { display.textContent = parseFloat(result) * 1; } }}Reordering key handling
Section titled “Reordering key handling”Wir haben die Tasten in folgender Reihenfolge abgehandelt:
calculatorButtonsDiv.addEventListener('click', e => { // Number keys // Decimal key // Operator keys // Equal key // Clear key});Wenn man den Code von oben bis unten liest, ist man schon maximal verwirrt, wenn man den Clear-Abschnitt erreicht. Operator und Istgleich sind ja einigermaßen komplex. Da wir die Tasten in beliebiger Reihenfolge abhandeln können, können wir die Löschen-Taste nach oben schieben. Der Code wird dann insgesamt einfacher zu lesen, weil die schwierigen Teile am Ende kommen.
calculatorButtonsDiv.addEventListener('click', e => { // Clear key // Number keys // Decimal key // Operator keys // Equal key});Refactoring Clear
Section titled “Refactoring Clear”So sieht der Abschnitt zum Löschen im Moment aus:
if (buttonType === 'clear') { if (button.textContent === 'AC') { delete calculator.dataset.firstValue; delete calculator.dataset.operator; delete calculator.dataset.modifierValue; }
display.textContent = '0'; button.textContent = 'AC';}Der Code ist nicht wirklich einfach zu lesen, weil die Reihenfolge der Operationen keinen Sinn ergibt. Es liest sich so:
if (buttonType === 'clear') { // if clear key pressed twice, do this // if clear key pressed at least once, to that}Umgekehrt würde es mehr Sinn machen:
if (buttonType === 'clear') { // if clear key pressed once, to this // if clear key pressed twice, to that}Bringen wir also display.textContent und button.textContent nach oben:
if (buttonType === 'clear') { display.textContent = '0'; button.textContent = 'AC'; if (button.textContent === 'AC') { delete calculator.dataset.firstValue; delete calculator.dataset.operator; delete calculator.dataset.modifierValue; }}Wir können jetzt zwar den textContent nicht mehr prüfen, aber wir können prüfen, ob zuvor die Löschtaste gedrückt wurde:
if (buttonType === 'clear') { display.textContent = '0'; button.textContent = 'AC'; if (previousButtonType === 'clear') { delete calculator.dataset.firstValue; delete calculator.dataset.operator; delete calculator.dataset.modifierValue; }}Das ist jetzt viel einfacher als vorher!
Changing variable names
Section titled “Changing variable names”Bis jetzt haben wir die Variable result für das im Display angezeigte “Ergebnis” verwendet:
const result = display.textContent;Das kann verwirrend sein, weil wir unter result eigentlich das Ergebnis einer Berechnung verstehen. Im Display steht aber mitunter einfach nur eine Zahl, die wir eingegeben haben. Ändern wir also den Namen dieser Variablen zu displayValue. Und schon wird wieder vieles klarer.
Wenn wir jetzt eine Berechnung machen, können wir tatsächlich result statt newResult verwenden. Das heißt, wir ändern alle newResult zu result.