❤️
DRY, YAGNI, KISS und SINE - Die 4 wichtigsten Prinzipien der Softwareentwicklung

4 grundlegende Entwicklungsprinzipien: DRY, YAGNI, KISS, SINE
Einleitung
Mehr als fünf Jahre lang hing über meinem Schreibtisch ein großes Poster mit den Worten: DRY, YAGNI, KISS und SINE. Diese Akronyme repräsentieren vier wesentliche Prinzipien in der Softwareentwicklung, die Ihnen helfen, besseren Code zu erstellen und die Zeitachse Ihrer Projekte zu verkürzen.
Im Bild oben finden Sie eine Zusammenfassung der einzelnen Prinzipien. In diesem Artikel schauen wir uns jeden von ihnen genauer an und erfahren, wie Sie sie beim Programmieren verwenden können.
1. TROCKEN - Wiederhole dich nicht!
Das DRY-Prinzip ist eine Art heiliger Gral der leicht zu wartenden Codebasis. Die Idee ist einfach: Schreiben Sie denselben Code nur einmal. Und nein, Sie dürfen auch keinen Code kopieren, einfügen.
Wenn Sie denselben Code duplizieren, tun Sie in 99% der Fälle etwas, das Sie in Zukunft bereuen werden. Dadurch wird es schwierig, Ihre Codebasis zu warten. Wenn Sie beispielsweise auf einen Fehler stoßen, müssen Sie daran denken, die Korrektur an allen Stellen durchzuführen, an denen derselbe Code kopiert und eingefügt wird. Und wenn Sie dies nicht tun, werden Sie denselben Fehler erneut auftreten und müssen ihn erneut beheben.
"Jedes Stück Wissen muss eine einzige, eindeutige, maßgebliche Repräsentation innerhalb eines Systems haben" -Andy Hunt & Dave Thomas
Wenn Sie ähnliche Funktionen an mehreren Speicherorten benötigen und am Ende Code duplizieren, ist ein Refactoring erforderlich. Unter Refactoring versteht man die Verbesserung des vorhandenen Codes, ohne dessen Funktionalität zu verändern. Ziel ist es, ein besseres Design oder eine bessere Struktur für den Code zu erstellen, um ihn später leichter ändern zu können.
Betrachten wir den folgenden Code eines imaginären Fantasy-Spiels:
const MANA_POTION_EFFECT = 10;
const ELVEN_BREAD_EFFECT = 5;
function eatElvenBread() {
character.mana += ELVEN_BREAD_EFFECT;
}
function useManaPotion() {
character.mana += MANA_POTION_EFFECT;
}
Beide Funktionen haben im Grunde den gleichen Code. Trotzdem sieht es nicht so schlecht aus, oder?
Denken wir nun an ein Szenario, in dem sich unsere Spielregeln ändern. Wir erhalten folgende Anforderung:
Requirement:
The effects of the mana potion and elven bread must be relative
to the maximum mana of the hero so that they have meaningful
impact even on higher levels.
For example: If the hero's current maximum mana is 50,
the mana potion restores 10% of it (instead of 10 units),
which is 5 units.
Nun, wie können wir unseren Code dafür ändern?
Ja, wir müssen beide Funktionen ändern. Das ist ein Code-Geruch und wir haben eigentlich zwei Risiken:
- In der realen Welt können die Funktionen weit voneinander entfernt sein, und es ist leicht, eine zu übersehen und nur Änderungen an der anderen vorzunehmen.
- Wenn wir beide Funktionen bemerken, ist es verlockend, die Änderungen einfach für beide zu implementieren, da es ganz einfach ist und der Zeitdruck hart sein kann.
Dies ist oft der Punkt, an dem die Dinge schief gehen.
Ein bester Ansatz wäre gewesen, die allgemeine Logik ursprünglich in eine separate Funktion zu extrahieren, aber lassen Sie uns jetzt das Refactoring durchführen:
const MANA_POTION_EFFECT = 10;
const ELVEN_BREAD_EFFECT = 5;
function applyManaEffect(effect) {
character.mana += effect;
}
function eatElvenBread() {
applyManaEffect(ELVEN_BREAD_EFFECT);
}
function useManaPotion() {
applyManaEffect(MANA_POTION_EFFECT);
}
Jetzt ist es einfach, die Spielregeln zu ändern, da wir nur eine Funktion ändern müssen:
const MANA_POTION_EFFECT = 10;
const ELVEN_BREAD_EFFECT = 5;
function calculateManaEffect(relativeEffect) {
return (relativeEffect / 100) * character.maximumMana;
}
function applyManaEffect(relativeEffect) {
const effect = calculateManaEffect(relativeEffect);
applyManaEffect(effect);
}
function eatElvenBread() {
applyManaEffect(ELVEN_BREAD_EFFECT);
}
function useManaPotion() {
applyManaEffect(MANA_POTION_EFFECT);
}
Diese Art des Refactorings ist unerlässlich, da die Duplizierung von Code zu einem Spaghetti-Chaos führt, das schwer zu warten ist und die technischen Schulden erhöht. Es bremst dich auf Dauer enorm aus.
DRY ist vielleicht das wichtigste Programmierprinzip.
Wenn Sie also das nächste Mal Strg+C und Strg+V drücken, halten Sie für eine Sekunde inne und überlegen Sie, wie Sie dies vermeiden können.
Die Idee des DRY-Prinzips ist einfach, aber schwer anzuwenden. Sie müssen es nur tun! Setzen Sie keinen TODO-Kommentar; Du weißt bereits, dass es immer etwas Wichtigeres geben wird. Führen Sie das Refactoring sofort durch, und Sie werden es sich später danken.
2. YAGNI - Du wirst es nicht brauchen!
Das zweite Prinzip lautet: Implementieren Sie keine Features oder schreiben Sie Funktionen, die Sie gerade nicht benötigen.
Kommen wir zurück zu unserem Fantasy-Spiel. Unser Held wird in Eldoria herausfordernde Abenteuer erleben, also haben wir vielleicht die Idee, dass unser Held in Zukunft die Fähigkeit haben wird, sich in einen Werwolf zu verwandeln. Also fügen wir diese Funktion hinzu:
WEREWOLF_STRENGTH_MODIFIER = 1.5;
function transformIntoWerefolf() {
character.strength *= WEREWOLF_STRENGTH_MODIFIER;
// ...
// NOTE: This is for future needs. Not all parts of
// transforming into a werefolf are implemented.
}
Dies ist ein Anti-Muster, das Sie vermeiden sollten! Wenn Sie sich eine solche Gewohnheit aneignen, wird Ihre Codebasis voll von ungenutztem Code und TODOs, die Sie noch pflegen müssen.
Als Entwickler denken wir oft, wir wüssten, welche Features oder Funktionen wir in Zukunft brauchen werden. Wir programmieren sie im Voraus, weil wir denken, dass wir Zeit sparen, während wir sie in Wirklichkeit nur verschwenden. Diese Art des Denkens war tatsächlich einer der größten Fehler, die ich in den ersten Jahren meiner Karriere gemacht habe.
Erstens: Man kann die Zukunft nicht vorhersagen. So einfach ist das. Was heute noch vernünftig erscheint, kann morgen völlig anders sein. Alles ändert sich, und zwar sehr schnell.
Zweitens: Wenn Sie versuchen, die Zukunft vorherzusagen, schreiben Sie am Ende Code, der nicht verwendet wird. Es gibt zwei Möglichkeiten: Entweder Sie behalten den ungenutzten Code bei, was Zeit und Mühe erfordert und sich dumm anfühlt, oder Sie lassen den Code nicht gepflegt. Meiner Erfahrung nach bedeutet das Nicht-Pflegen des Codes, dass er nicht verwendbar ist, selbst wenn Ihre Vorhersage wahr wird und der Tag kommt, an dem Sie ihn brauchen.
Schreiben Sie also einfach den Code, wenn Sie ihn brauchen.
Und damit meine ich nicht, dass man die Zukunft überhaupt nicht berücksichtigen muss. Ich meine, Sie sollten für Veränderungen entwerfen, aber keinen Code schreiben, den Sie noch nicht benötigen.
Erfahren Sie, wie Sie Code schreiben, den Sie leicht bearbeiten können. Vermeiden Sie z. B. harte Codierungswerte, haben Sie eine gute Architektur, verwenden Sie Entwurfsmuster, halten Sie Ihren Code sauber und halten Sie die Funktionen kurz. Versuchen Sie nicht, die Zukunft vorherzusagen, sondern schaffen Sie ein System, das Sie leicht ändern können.
3. KISS - Keep it stupid simple!
Das KISS-Prinzip besagt: Halten Sie die Dinge super einfach und vermeiden Sie unnötige Komplexität.
Die Softwareentwicklung ist eine große Herausforderung, daher ist es wichtig, einfache Lösungen zu haben. Einfache Lösungen könnten funktionieren. Komplexe werden es nicht tun.
Denken Sie an die Herstellung von Booten (von der ich nichts weiß, aber ich denke, es ist auch sehr schwierig). Bei Booten spielt es keine Rolle, wenn das Farbpigment des Ruders leicht falsch liegt. Wenn Sie jedoch einen ähnlichen Fehler im Code machen, kann das gesamte System ohne Vorwarnung abstürzen.
Selbst der kleinste Fehler im Code kann kritische Probleme verursachen. In meiner Karriere habe ich viele einzeilige Änderungen vorgenommen, die zu großen Fehlern geführt haben. Daher ist es wichtig, die Bausteine des Systems so einfach wie möglich zu halten. Überlegen Sie nach der Implementierung der ersten Version des Codes, wie Sie ihn vereinfachen und umgestalten können.
Erstellen Sie immer die einfachste Lösung, die Sie sich vorstellen können.
Haben:
- Einfache Klassen, die dem Single-Responsibility-Prinzip (SRP) folgen
- Kurze Methoden
- Leicht verständliche Algorithmen
- Übersichtliche Verzeichnisstruktur
- Einfache Architektur, die leicht zu verstehen ist.
Wenn Sie einem Kollegen nicht in 30 Sekunden erklären können, wie Ihr Code funktioniert, ist er wahrscheinlich zu kompliziert oder Sie verstehen ihn selbst nicht.
Beachten Sie, dass eine einfache Lösung nicht unbedingt eine einfache Lösung bedeutet. Ich werde dieses Konzept im nächsten Abschnitt anhand eines Beispiels erläutern, das veranschaulicht, wie man die Dinge einfach hält.
4. SINE - Einfach ist nicht einfach!
SINE ist meine Erweiterung des KISS-Prinzips, weil das KISS-Prinzip nicht immer richtig verstanden wird: Einfach wird oft fälschlicherweise mit leicht verwechselt, und in der Softwareentwicklung ist es das Gegenteil.
- Einfach bedeutet etwas, das schnell erledigt werden kann, ohne viel Lernen, Recherchieren, Nachdenken oder Arbeit.
- Einfach ist das Gegenteil von komplex. Es ist etwas, das nur eine kleine Anzahl beweglicher Teile hat.
Betrachten wir zum Beispiel die berühmte Formel von Albert Einstein:
E = mc²
Es ist einfach: sehr allgemein und nicht zu viele "bewegliche Teile". Aber es war definitiv nicht einfach, es zu erfinden, und es ist auch nicht allzu leicht zu verstehen.
Auf ähnliche Weise ist das Schreiben von Code, das Implementieren von Funktionen und das Lösen von Problemen relativ einfach. Die meisten Entwickler können die erforderlichen Funktionen implementieren. Allerdings sind nur wenige kompetent genug, um diese auf einfache Weise umzusetzen, denn Einfachheit ist schwer zu erreichen.
"Jeder kann das Einfache kompliziert machen. Kreativität bedeutet, das Komplizierte einfach zu machen." -Charles Mingus
Es ist viel einfacher, ein Feature auf die erste denkbare Weise zu implementieren, indem man eine lange Funktion mit 500 Zeilen schreibt, als kleine, wiederverwendbare Funktionen oder Methoden zu entwerfen oder eine gute Klassenhierarchie zu erstellen.
Ich meine nicht, dass man jedes Mal, wenn man einen Code schreibt, eine neue Relativitätstheorie erfinden muss. Sie müssen jedoch die Dinge durchdenken und die Refactoring-Arbeit leisten, um die Dinge einfach zu machen.
Kommen wir zurück in die Welt von Eldoria. Schauen Sie sich das folgende Beispiel für einfach und einfach an. Dieser Code ist relativ einfach zu schreiben:
const HEALTH_POTION_NAME = 'Health Potion';
const MANA_POTION_NAME = 'Mana Potion';
const STRENGTH_POTION_NAME = 'Strength Potion';
const HEALTH_POTION_DESCRIPTION = 'Restores a portion of health';
const MANA_POTION_DESCRIPTION = 'Restores a portion of mana';
const STRENGTH_POTION_DESCRIPTION = 'Increases physical strength';
function getHealthPotionDetails() {
console.log(`Name: ${HEALTH_POTION_NAME}`);
console.log(`Description: ${HEALTH_POTION_DESCRIPTION}`);
}
function getManaPotionDetails() {
console.log(`Name: ${MANA_POTION_NAME}`);
console.log(`Description: ${MANA_POTION_DESCRIPTION}`);
}
function getStrengthPotionDetails() {
console.log(`Name: ${STRENGTH_POTION_NAME}`);
console.log(`Description: ${STRENGTH_POTION_DESCRIPTION}`);
}
Es ist jedoch nicht sehr wartungsfreundlich und folgt eindeutig auch nicht dem DRY-Prinzip. Stellen Sie sich vor, was passieren würde, wenn wir unserem Spiel 10 weitere Gegenstände hinzufügen würden.
Lassen Sie uns nun dem KISS-Prinzip folgen und eine einfachere Version davon schreiben, indem wir JavaScript-Objekte als Wörterbücher verwenden:
const POTION_DETAILS = {
HEALTH: {
name: 'Health Potion',
description: 'Restores a portion of health',
},
MANA: {
name: 'Mana Potion',
description: 'Restores a portion of mana',
},
STRENGTH: {
name: 'Strength Potion',
description: 'Increases physical strength',
},
};
function getPotionDetails(potionType) {
console.log(`Name: ${POTION_DETAILS[potionType].name}`);
console.log(
`Description: ${POTION_DETAILS[potionType].description}`
);
}
Ich halte diesen Code für einfacher, aber auch schwieriger zu schreiben. Es ist einfacher, weil es nur eine statt drei Funktionen hat. Es ist schwieriger zu schreiben, weil Sie das Konzept von Wörterbüchern verstehen müssen und wie man JS-Objekte als solche verwendet. Das Beispiel ist ziemlich vereinfacht, aber ich glaube, Sie haben den Punkt von einfach vs. leicht verstanden.
Das Erstellen einfacher Lösungen kann manchmal eine Herausforderung sein, aber es lohnt sich, den Code nach der ersten funktionierenden Version auf einfacher umzugestalten. Einfachheit erfordert zwar Arbeit, zahlt sich aber auf lange Sicht aus, wenn Sie eine leicht verständliche Codebasis haben und schnell Änderungen daran vornehmen können. Davon profitieren neben Ihnen auch andere Entwickler in Ihrem Team.
Schlussfolgerung
Die vier wesentlichen Prinzipien der Softwareentwicklung sind:
- TROCKEN: Wiederholen Sie sich nicht ("Nicht kopieren-einfügen")
- YAGNI: Du wirst es nicht brauchen ("Schreib den Code nicht, wenn du ihn jetzt nicht brauchst")
- KISS: Keep it simple, stupid ("Schreibe den Code auf die einfachste Art und Weise, die du denken kannst. Umgestalten.")
- SINE: Einfach ist nicht einfach ("Einfachheit erfordert Arbeit. Mach es.")
Wenn Sie diese vier Grundprinzipien befolgen, können Sie besseren Code erstellen und bessere Entscheidungen treffen. Einfachheit erfordert Arbeit, aber sie zahlt sich auf lange Sicht mit einer Codebasis aus, die leicht zu verstehen und schnell zu ändern ist. Nutzen Sie also diese Richtlinien, um besseren Code zu schreiben und ein besserer Entwickler zu werden.