Beyond SOLID: KISS-, DRY- und LOD-Prinzipien in Go
Daniel Hayes
Full-Stack Engineer · Leapcell

Neben den bekannten SOLID-Prinzipien gibt es tatsächlich noch weitere nützliche und weit verbreitete Designprinzipien. Dieser Artikel stellt diese Designprinzipien vor, insbesondere die folgenden drei:
- KISS-Prinzip;
- DRY-Prinzip;
- LOD-Prinzip.
KISS-Prinzip
Das KISS-Prinzip (Keep It Simple, Stupid) ist ein wichtiges Prinzip in der Softwareentwicklung. Es betont, Dinge im Design und in der Implementierung von Softwaresystemen einfach und intuitiv zu halten und übermäßige Komplexität und unnötiges Design zu vermeiden.
Es gibt verschiedene Versionen der Beschreibung des KISS-Prinzips, wie zum Beispiel die folgenden:
- Keep It Simple and Stupid;
- Keep It Short and Simple;
- Keep It Simple and Straightforward.
Bei näherer Betrachtung werden Sie jedoch feststellen, dass sie im Wesentlichen dasselbe bedeuten, was ins Chinesische übersetzt werden kann als: „Halten Sie es so einfach wie möglich“.
Das KISS-Prinzip ist ein wichtiges Mittel, um die Lesbarkeit und Wartbarkeit von Code zu gewährleisten. Die „Einfachheit“ in KISS wird nicht an der Anzahl der Codezeilen gemessen. Weniger Codezeilen bedeuten nicht unbedingt einfacheren Code. Wir müssen auch die logische Komplexität, die Schwierigkeit der Implementierung, die Lesbarkeit des Codes usw. berücksichtigen. Wenn ein Problem selbst komplex ist, verstößt das Lösen mit komplexen Methoden nicht gegen das KISS-Prinzip. Darüber hinaus kann derselbe Code in einem Geschäftsszenario das KISS-Prinzip erfüllen, in einem anderen jedoch nicht.
Richtlinien zum Schreiben von Code, der das KISS-Prinzip erfüllt:
- Verwenden Sie bei der Implementierung von Code keine Technologien, die Ihre Kollegen möglicherweise nicht verstehen.
- Erfinden Sie das Rad nicht neu; Nutzen Sie vorhandene Bibliotheken.
- Überoptimieren Sie nicht.
Hier ist ein Beispiel für ein einfaches Taschenrechnerprogramm, das nach dem KISS-Prinzip entworfen wurde:
package main import "fmt" // Calculator definiert eine einfache Taschenrechnerstruktur type Calculator struct{} // Add-Methode addiert zwei Zahlen func (c Calculator) Add(a, b int) int { return a + b } // Subtract-Methode subtrahiert zwei Zahlen func (c Calculator) Subtract(a, b int) int { return a - b } func main() { calculator := Calculator{} // Berechne 5 + 3 result1 := calculator.Add(5, 3) fmt.Println("5 + 3 =", result1) // Berechne 8 - 2 result2 := calculator.Subtract(8, 2) fmt.Println("8 - 2 =", result2) }
Im obigen Beispiel haben wir eine einfache Taschenrechnerstruktur „Calculator“ definiert, die die Methoden „Add“ und „Subtract“ zur Durchführung von Addition und Subtraktion enthält. Mit einfachem Design und Implementierung ist dieses Taschenrechnerprogramm klar, leicht verständlich und erfüllt die Anforderungen des KISS-Prinzips.
DRY-Prinzip
Das DRY-Prinzip, kurz für Don’t Repeat Yourself (Wiederhole dich nicht), ist eines der wichtigsten Prinzipien in der Softwareentwicklung. Es betont, doppelten Code und Funktionen zu vermeiden und Redundanz im System zu minimieren. Die Kernidee des DRY-Prinzips ist, dass jede Informationseinheit im System eine und nur eine eindeutige Darstellung haben sollte. Es vermeidet, dieselben Informationen oder dieselbe Logik wiederholt an mehreren Stellen zu definieren.
Sie denken vielleicht, dass das DRY-Prinzip sehr einfach und leicht anzuwenden ist: Solange zwei Codeabschnitte gleich aussehen, verstößt es gegen DRY. Aber ist das wirklich der Fall? Die Antwort ist nein. Dies ist ein häufiges Missverständnis des Prinzips. In Wirklichkeit verstößt doppelter Code nicht unbedingt gegen DRY, und Code, der nicht repetitiv aussieht, kann in der Tat gegen DRY verstoßen.
Typischerweise gibt es drei Arten von Codewiederholungen: Wiederholung der Implementierungslogik, Wiederholung der funktionalen Semantik und Wiederholung der Ausführung. Einige davon scheinen möglicherweise gegen DRY zu verstoßen, tun es aber nicht, während andere möglicherweise gut aussehen, aber tatsächlich dagegen verstoßen.
Wiederholung der Implementierungslogik
type UserAuthenticator struct{} func (ua *UserAuthenticator) authenticate(username, password string) { if !ua.isValidUsername(username) { // ... Codeblock 1 } if !ua.isValidPassword(username) { // ... Codeblock 1 } // ... anderer Code ausgelassen ... } func (ua *UserAuthenticator) isValidUsername(username string) bool {} func (ua *UserAuthenticator) isValidPassword(password string) bool {}
Angenommen, die Funktionen isValidUserName()
und isValidPassword()
enthalten doppelten Code. Auf den ersten Blick scheint dies ein klarer Verstoß gegen DRY zu sein. Um die Duplizierung zu beseitigen, könnten wir den Code umgestalten und ihn in eine allgemeinere Funktion isValidUserNameOrPassword()
zusammenführen.
Nach der Umgestaltung sinkt die Anzahl der Zeilen und es gibt keinen wiederholten Code mehr. Ist das besser? Die Antwort ist nein. Schon am Funktionsnamen erkennen wir, dass die zusammengeführte Funktion isValidUserNameOrPassword()
zwei Aufgaben übernimmt: Benutzernamen und Passwörter validieren. Dies verstößt gegen das Single Responsibility Principle und das Interface Segregation Principle.
Selbst wenn wir die beiden Funktionen zusammenführen, bleiben Probleme bestehen. Obwohl isValidUserName()
und isValidPassword()
logisch repetitiv erscheinen, sind sie semantisch nicht repetitiv. Semantische Nicht-Repetition bedeutet, dass diese beiden Methoden funktional völlig unterschiedliche Dinge tun: Die eine validiert Benutzernamen, die andere validiert Passwörter. Obwohl die Validierungslogik im aktuellen Design identisch ist, führen wir bei der Zusammenführung potenzielle Probleme ein. Wenn wir beispielsweise eines Tages die Passwortvalidierungslogik ändern, unterscheiden sich die beiden Funktionen wieder in ihrer Implementierung. Wir müssten sie dann wieder in die ursprünglichen beiden Funktionen aufteilen.
Für Fälle von doppeltem Code können wir das Problem häufig lösen, indem wir sie in kleinere, differenziertere Funktionen abstrahieren.
Wiederholung der funktionalen Semantik
Betrachten Sie im selben Projekt die beiden Funktionen: isValidIp()
und checkIfIpValid()
. Obwohl sich ihre Namen unterscheiden und sie unterschiedliche Implementierungen verwenden, ist ihre Funktionalität identisch – sie prüfen beide, ob eine IP-Adresse gültig ist.
func isValidIp(ipAddress string) bool { // ... Validierung mit Regex } func checkIfIpValid(ipAddress string) bool { // ... Validierung mit String-Operationen }
In diesem Beispiel sind zwar die Implementierungen unterschiedlich, aber die Funktionalität ist wiederholt, d. h. semantische Wiederholung. Dies verstößt gegen das DRY-Prinzip. In solchen Fällen sollten wir die Implementierung in einem einzigen Ansatz vereinheitlichen, und wo immer wir prüfen müssen, ob eine IP-Adresse gültig ist, sollten wir konsequent dieselbe Funktion aufrufen.
Wiederholung der Ausführung
type UserService struct { userRepo UserRepo } func (us *UserService) login(email, password string) { existed := us.userRepo.checkIfUserExexisted(email, password) if !existed { // ... } user := us.userRepo.getUserByEmail(email) } type UserRepo struct{} func (ur *UserRepo) checkIfUserExisted(email, password string) bool { if !ur.isValidEmail(email) { // ... } } func (ur *UserRepo) getUserByEmail(email string) User { if !ur.isValidEmail(email) { // ... } }
Im obigen Code gibt es keine logische Duplizierung und keine semantische Duplizierung, aber er verstößt dennoch gegen DRY. Dies liegt daran, dass der Code Wiederholung der Ausführung enthält.
Die Behebung ist relativ einfach: Wir müssen lediglich die Validierungslogik aus UserRepo
entfernen und sie in UserService
zentralisieren.
Wie kann man die Wiederverwendbarkeit von Code verbessern?
- Reduzieren Sie die Code-Kopplung.
- Befolgen Sie das Single Responsibility Principle.
- Trennen Sie Geschäftslogik von nicht-geschäftlicher Logik.
- Verschieben Sie gemeinsamen Code in freigegebene Module.
- Wenden Sie Vererbung, Polymorphismus, Abstraktion und Kapselung an.
- Verwenden Sie Entwurfsmuster wie Vorlagen.
Hier ist ein einfaches Personalverwaltungssystem-Beispiel, das das DRY-Prinzip anwendet, um Klarheit und Wiederverwendbarkeit des Codes zu gewährleisten:
package main import "fmt" // Person struct stellt persönliche Informationen dar type Person struct { Name string Age int } // PrintPersonInfo gibt persönliche Informationen aus func PrintPersonInfo(p Person) { fmt.Printf("Name: %s, Age: %d\n", p.Name, p.Age) } func main() { // Erstelle zwei Personen person1 := Person{Name: "Alice", Age: 30} person2 := Person{Name: "Bob", Age: 25} // Gib persönliche Informationen aus PrintPersonInfo(person1) PrintPersonInfo(person2) }
Im obigen Beispiel haben wir eine Person
-Struktur definiert, um persönliche Informationen darzustellen, zusammen mit einer PrintPersonInfo
-Funktion, um sie auszugeben. Durch die Kapselung der Ausgabelogik in PrintPersonInfo
halten wir uns an das DRY-Prinzip, vermeiden wiederholte Ausgabelogik und verbessern die Wiederverwendbarkeit und Wartbarkeit des Codes.
LOD-Prinzip
Das LOD-Prinzip (Law of Demeter), auch bekannt als Principle of Least Knowledge, zielt darauf ab, die Kopplung zwischen Objekten zu reduzieren und die Abhängigkeiten zwischen verschiedenen Teilen des Systems zu minimieren. Das LOD-Prinzip betont, dass ein Objekt so wenig wie möglich über andere Objekte wissen sollte und nicht direkt mit Fremden kommunizieren sollte, sondern stattdessen über seine eigenen Mitglieder operieren sollte.
Das Gesetz von Demeter betont, dass Klassen ohne direkte Abhängigkeit keine haben sollten und dass Klassen mit Abhängigkeiten sich nur auf die erforderlichen Schnittstellen verlassen sollten. Die Idee ist, die Kopplung zwischen Klassen zu reduzieren und sie so unabhängig wie möglich zu machen. Jede Klasse sollte so wenig wie möglich über den Rest des Systems wissen. Wenn Änderungen auftreten, müssen sich weniger Klassen dieser Änderungen bewusst sein und sich daran anpassen.
Hier ist ein Beispiel für ein einfaches Benutzerverwaltungssystem, das nach dem LOD-Prinzip entworfen wurde:
package main import "fmt" // UserService: verantwortlich für die Benutzerverwaltung type UserService struct{} // GetUserByID ruft Benutzerinformationen anhand der Benutzer-ID ab func (us UserService) GetUserByID(id int) User { userRepo := UserRepository{} return userRepo.FindByID(id) } // UserRepository: verantwortlich für die Benutzerdatenpflege type UserRepository struct{} // FindByID ruft Benutzerinformationen aus der Datenbank anhand der ID ab func (ur UserRepository) FindByID(id int) User { // Simuliere das Abrufen von Benutzern aus der Datenbank return User{id, "Alice"} } // User struct type User struct { ID int Name string } func main() { userService := UserService{} user := userService.GetUserByID(1) fmt.Printf("Benutzer-ID: %d, Name: %s\n", user.ID, user.Name) }
Im obigen Beispiel haben wir ein einfaches Benutzerverwaltungssystem entworfen, das aus zwei Teilen besteht: UserService
(Benutzerdienst) und UserRepository
(Benutzerrepository). UserService
fragt Benutzerinformationen ab, indem es UserRepository
aufruft. Dies hält sich an das LOD-Prinzip, indem sichergestellt wird, dass die Kommunikation nur mit „direkten Freunden“ stattfindet.
Wir sind Leapcell, Ihre erste Wahl für das Hosten von Go-Projekten.
Leapcell ist die Serverless-Plattform der nächsten Generation für Webhosting, asynchrone Aufgaben und Redis:
Mehrsprachige Unterstützung
- Entwickeln Sie mit Node.js, Python, Go oder Rust.
Stellen Sie unbegrenzt viele Projekte kostenlos bereit
- Zahlen Sie nur für die Nutzung – keine Anfragen, keine Gebühren.
Unschlagbare Kosteneffizienz
- Pay-as-you-go ohne Leerlaufgebühren.
- Beispiel: 25 $ unterstützen 6,94 Millionen Anfragen bei einer durchschnittlichen Antwortzeit von 60 ms.
Optimierte Entwicklererfahrung
- Intuitive Benutzeroberfläche für mühelose Einrichtung.
- Vollautomatische CI/CD-Pipelines und GitOps-Integration.
- Echtzeitmetriken und -protokollierung für umsetzbare Erkenntnisse.
Mühelose Skalierbarkeit und hohe Leistung
- Automatische Skalierung zur einfachen Bewältigung hoher Parallelität.
- Kein operativer Overhead – konzentrieren Sie sich einfach auf das Erstellen.
Weitere Informationen finden Sie in der Dokumentation!
Folgen Sie uns auf X: @LeapcellHQ