Abschnitt 20 · Lektion 2
Code aufteilen
Die letzte Lektion hat eine Funktion in vier zerlegt. Diese Lektion stellt die nächste Frage: nach welchem Schnittmuster?
Es gibt eines, das fast immer passt, und du kennst es aus diesem Kurs schon, ohne dass es je einen Namen bekommen hätte.
Die drei Schichten
Fast jede Oberfläche besteht aus denselben drei Teilen.
Daten. Woher die Sachen kommen, und in welcher Form sie im Programm liegen. Eine Liste im Code, eine Antwort aus dem Netz, ein Eintrag aus dem Speicher.
Logik. Was daraus gerechnet wird. Rabatte, Summen, Filter, Sortierung, Prüfregeln.
Darstellung. Was davon auf dem Bildschirm landet. Elemente bauen, Texte setzen, Klassen tauschen.
<h1>Angebote</h1>
<ul id="liste"></ul> body {
font-family: system-ui, sans-serif;
padding: 1rem;
}
#liste {
list-style: none;
padding: 0;
}
.zeile {
padding: 6px 0;
border-bottom: 1px solid #d6d3d1;
} const angebote = [
{ titel: "Grundkurs", preisInCent: 4900, minuten: 180 },
{ titel: "Aufbaukurs", preisInCent: 6900, minuten: 240 },
{ titel: "Werkstatt", preisInCent: 12900, minuten: 420 },
];
function zeigeAngebote(istMitglied) {
const liste = document.getElementById("liste");
liste.textContent = "";
for (const angebot of angebote) {
let rabatt = 0;
if (istMitglied) {
rabatt = 10;
}
if (angebot.minuten >= 300) {
rabatt = rabatt + 5;
}
const endpreis = Math.round(
angebot.preisInCent * (1 - rabatt / 100)
);
const zeile = document.createElement("li");
zeile.className = "zeile";
zeile.textContent =
angebot.titel +
": " +
(endpreis / 100).toFixed(2).replace(".", ",") +
" € (" +
rabatt +
" % Rabatt)";
liste.append(zeile);
}
}
zeigeAngebote(true); Im ersten Beispiel liegen alle drei in einer Schleife übereinander. Die Funktion holt sich das Element, rechnet den Rabatt aus, formatiert einen Betrag und baut nebenbei ein li. Sie ist knapp dreißig Zeilen lang und tut vier Dinge.
Das ist der Zustand, in dem der meiste Code der Welt ist, und er funktioniert. Er wird erst beim ersten Wunsch unangenehm: „Zeig die Rabatte auch in der Rechnungs-Mail.” Jetzt brauchst du die Rechnung ohne die Seite, und sie steht mitten in einer Funktion, die ein li baut.
Die Probe dafür ist eine Zeile. Öffne das erste Beispiel und frag in der Eingabezeile der Console nach typeof rabattInProzent. Die Antwort ist undefined. Die Rabattregel steht in dieser Datei, sie rechnet richtig, und trotzdem gibt es sie nicht: Sie hat keinen Namen, unter dem man sie rufen könnte.
Dieselbe Aufgabe, getrennt
<h1>Angebote</h1>
<ul id="liste"></ul> body {
font-family: system-ui, sans-serif;
padding: 1rem;
}
#liste {
list-style: none;
padding: 0;
}
.zeile {
padding: 6px 0;
border-bottom: 1px solid #d6d3d1;
} // 1. Daten
const ANGEBOTE = [
{ titel: "Grundkurs", preisInCent: 4900, minuten: 180 },
{ titel: "Aufbaukurs", preisInCent: 6900, minuten: 240 },
{ titel: "Werkstatt", preisInCent: 12900, minuten: 420 },
];
// 2. Logik. Kennt keine Elemente, kein document, keine Klassen.
function rabattInProzent(minuten, istMitglied) {
let rabatt = 0;
if (istMitglied) {
rabatt = rabatt + 10;
}
if (minuten >= 300) {
rabatt = rabatt + 5;
}
return rabatt;
}
function endpreisInCent(preisInCent, rabatt) {
return Math.round(preisInCent * (1 - rabatt / 100));
}
function euroText(cent) {
return (cent / 100).toFixed(2).replace(".", ",") + " €";
}
function zeileText(angebot, istMitglied) {
const rabatt = rabattInProzent(angebot.minuten, istMitglied);
const preis = endpreisInCent(angebot.preisInCent, rabatt);
return `${angebot.titel}: ${euroText(preis)} (${rabatt} % Rabatt)`;
}
// 3. Darstellung. Kennt die Regeln nicht, nur die Texte.
function zeichnen(angebote, istMitglied) {
const liste = document.getElementById("liste");
liste.textContent = "";
for (const angebot of angebote) {
const zeile = document.createElement("li");
zeile.className = "zeile";
zeile.textContent = zeileText(angebot, istMitglied);
liste.append(zeile);
}
}
zeichnen(ANGEBOTE, true); Derselbe Bildschirminhalt, nur in Schichten. Oben die Daten, dann vier Funktionen, die rechnen, und ganz unten eine, die zeichnet.
Sieh dir an, was die einzelnen Teile nicht wissen. In rabattInProzent kommt kein document vor, keine Klasse, kein Element. In zeichnen kommt keine Zahl und keine Regel vor, nur zeileText. Die Rabattregel lässt sich ändern, ohne die Darstellung anzufassen, und umgekehrt.
Der Code ist dabei länger geworden. Das ist der Preis, und er ist niedriger, als er aussieht: die zusätzlichen Zeilen sind Funktionsköpfe, keine zusätzliche Arbeit.
Warum die Logik nichts vom DOM wissen darf
<p>Diese Seite ist leer. Sieh in die Console.</p> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
p {
margin: 0 0 0.75rem;
} function rabattInProzent(minuten, istMitglied) {
let rabatt = 0;
if (istMitglied) {
rabatt = rabatt + 10;
}
if (minuten >= 300) {
rabatt = rabatt + 5;
}
return rabatt;
}
// Kein Element, kein Klick, kein Warten. Nur Eingabe und
// Ergebnis, und genau so nimmt dieser Kurs seit Abschnitt 5
// deine Funktionen ab.
const faelle = [
{ minuten: 120, mitglied: false, erwartet: 0 },
{ minuten: 120, mitglied: true, erwartet: 10 },
{ minuten: 300, mitglied: false, erwartet: 5 },
{ minuten: 420, mitglied: true, erwartet: 15 },
{ minuten: 299, mitglied: true, erwartet: 10 },
];
for (const fall of faelle) {
const ergebnis = rabattInProzent(fall.minuten, fall.mitglied);
const stimmt = ergebnis === fall.erwartet;
console.log(
stimmt ? "ok" : "FALSCH",
`${fall.minuten} min, Mitglied: ${fall.mitglied}`,
"ergibt",
ergebnis
);
} Hier kommt der eigentliche Gewinn. Eine Funktion, die nur Werte hinein und Werte heraus reicht, kannst du mit Beispielen abklopfen. Du gibst ihr fünf Eingaben, vergleichst fünf Ergebnisse und siehst in drei Sekunden, ob die Regel stimmt.
Genau das macht dieser Kurs seit Abschnitt 5 mit deinen Aufgaben. Die Prüfung ruft deine Funktion mit festen Argumenten auf und sieht sich an, was zurückkommt. Sie kann das nur, weil deine Funktion nichts von der Seite verlangt.
Bei einer Funktion, die mittendrin ein li baut, geht das nicht mehr. Du müsstest ein Dokument bereitstellen, das Element suchen und den Text wieder herauslesen, nur um zu erfahren, ob 15 Prozent richtig gerechnet wurden.
Der vorletzte Fall im Beispiel ist übrigens der interessanteste: 299 Minuten. Grenzfälle sind die Stellen, an denen sich > und >= unterscheiden, und sie sind der Grund, warum man solche Listen überhaupt schreibt.
Frag den Grenzfall selbst. Im zweiten oder dritten Beispiel antwortet die Eingabezeile der Console auf rabattInProzent(299, false) mit 0 und auf rabattInProzent(300, false) mit 5. Eine Minute Unterschied, und die Regel kippt. rabattInProzent(300, true) antwortet 15, weil sich beide Gründe addieren.
Dieselbe Frage im ersten Beispiel zu stellen ist unmöglich, und das ist der ganze Unterschied zwischen den zwei Fassungen. Drei getippte Zeilen gegen: Seite aufbauen, auf den richtigen Stand klicken, Text aus einem li herauslesen und hoffen, dass man die richtige Zeile erwischt hat.
Mit Modulen und ohne
Wenn Module zur Verfügung stehen, ist der Schnitt sichtbar: daten.js, rabatte.js, ansicht.js, und jede Datei gibt nach außen, was die anderen brauchen. Was ein Modul ist, stand in Lektion 18.4.
Ohne Module ändert sich nichts am Prinzip, nur an der Sichtbarkeit. Die Schichten stehen dann in derselben Datei untereinander, oben die Daten, in der Mitte die Logik, unten die Darstellung. Das ist die Reihenfolge aus der letzten Lektion, und es ist dieselbe Idee eine Etage tiefer.
Fang immer so an. Eine Datei mit klaren Blöcken lässt sich in fünf Minuten in Module zerlegen, sobald du sie brauchst. Ein Durcheinander in drei Dateien ist ein Durcheinander in drei Dateien.
Woran du merkst, dass eine Datei zu groß ist
An dir selbst. Wenn du beim Suchen scrollst, ist sie zu groß. Wenn du eine Funktion zweimal öffnest, weil du beim zweiten Mal nicht mehr weißt, wo sie steht, ist sie zu groß.
Es gibt Faustzahlen dafür, und keine davon hilft. Zweihundert übersichtliche Zeilen sind besser als achtzig verschachtelte.
Der Punkt, an dem alle stolpern
Wer die Sache einmal verstanden hat, teilt zu früh.
Zwanzig Dateien mit je zwölf Zeilen sind nicht übersichtlicher als eine mit zweihundert. Sie sind nur anders unübersichtlich: Du siehst nichts mehr im Zusammenhang und springst beim Lesen zwischen den Dateien hin und her.
Die ehrliche Antwort ist, dass die Grenze Erfahrung ist und keine Zahl. Bis die da ist, gibt es eine brauchbare Ersatzregel: Teile erst, wenn es weh tut. Wenn du zum dritten Mal an derselben Stelle suchst, ist der Moment gekommen, und dann weißt du auch, wo der Schnitt hingehört.
Zum Mitnehmen
Rechnen und Zeichnen im selben Block ist bequem und rächt sich beim ersten Umbau. Getrennt lässt sich die Rechnung prüfen, ohne dass es eine Seite gibt.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
Was in dieser Lektion steckt
-
Artikel mit 3 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe im Editor, direkt im Browser geprüft
Öffnet sich mit dem Basis Konto.