- Die SOLID-Prinzipien definieren fünf objektorientierte Designrichtlinien, die die Klarheit, Erweiterbarkeit und Testbarkeit des Codes verbessern.
- Wenden Sie SRP, OCP, LSP, ISP und DIP an. Python Es geht darum, Verantwortlichkeiten zu trennen, auf Abstraktionen zurückzugreifen und kohärente Hierarchien zu entwerfen.
- Ein solides Design wird mit DRY, KISS und YAGNI kombiniert, um wartungsfreundliche Systeme ohne Überdimensionierung zu erreichen, die auf den realen Kontext des Projekts zugeschnitten sind.
In den folgenden Zeilen werden wir im Detail sehen, was SOLID ist und woher es kommt. Wozu dient es im alltäglichen Python-Einsatz und wie lässt es sich anhand von Beispielen aus der Praxis anwenden? In dieser Sprache werden wir außerdem weitere wichtige Prinzipien wie DRY, KISS und YAGNI integrieren und erörtern, in welchen Kontexten es sich möglicherweise nicht lohnt, bei SOLID ultrapuristisch vorzugehen.
Was ist SOLID und woher kommt es?
Wenn wir von SOLID sprechen, meinen wir fünf klassische Prinzipien des objektorientierten Designs die sich darauf konzentrieren, wie Klassen, Module und Schnittstellen strukturiert werden sollten, um Software leicht erweiterbar, testbar und wartbar zu machen.
Diese Prinzipien erlangten dank folgender Prinzipien Popularität: Robert C. Martin (Onkel Bob), einer der Väter der agilen Entwicklung und Autor von Büchern wie Code bereinigen o Saubere ArchitekturIn den 90er und frühen 2000er Jahren veröffentlichte er mehrere Artikel über objektorientiertes Design, darunter „Die Prinzipien des OOD“ und „Designprinzipien und Entwurfsmuster“.
Später schlug der Ingenieur Michael Feathers das Akronym vor. SOLIDE um sich auf die fünf wichtigsten Prinzipien zu beziehen, die aus diesen Werken hervorgegangen sind. Die Idee war, eine einfache Merkregel dass sich jeder Entwickler das merken und in seine Klassengestaltung einfließen lassen kann.
Das Akronym steht für:
- S - Prinzip der Einzelverantwortung (SRP, Prinzip der Einzelverantwortung).
- O - Offen-Geschlossen-Prinzip (OCP, Open/Closed Principle).
- L - Liskov-Substitutionsprinzip (LSP, Liskovsches Substitutionsprinzip).
- I - Prinzip der Schnittstellentrennung (ISP, Interface Segregation Principle).
- D - Prinzip der Abhängigkeitsumkehr (DIP, Prinzip der Abhängigkeitsumkehr).
Obwohl sie ursprünglich für stark typisierte Sprachen wie … formuliert wurden Java oder C#Diese Prinzipien sind vollkommen anwendbar auf Python, C++, PHP und im Allgemeinen für jede Sprache, die Objektorientierung unterstützt.

Wozu werden die SOLID-Prinzipien in realen Projekten angewendet?
Über die Theorie hinaus wird SOLID besonders deutlich, wenn das Projekt wächst und Es gibt mehrere Entwickler, die monate- oder jahrelang am selben Code arbeiten.Die Anwendung dieser Prinzipien hat mehrere sehr klare Vorteile:
Zum einen verbessern sie die Lesbarkeit und Klarheit der ArchitekturKlassen haben definierte Verantwortlichkeiten, Abhängigkeiten werden kontrolliert, und es ist einfacher, dem Geschäftsablauf zu folgen, ohne sich in Infrastrukturdetails zu verlieren.
Außerdem bevorzugen sie die Wiederverwendung und Erweiterbarkeit des Codes. Sind die Klassen gut konzipiert, bedeutet das Hinzufügen neuer Funktionen nicht, dass das halbe System auseinandergenommen werden muss. Die Tendenz geht dahin, durch neue Implementierungen zu erweitern, anstatt bereits funktionierenden und getesteten Code neu zu schreiben.
Ein weiterer wichtiger Punkt ist der TestbarkeitDurch die Reduzierung von Kopplungen und die Nutzung von Abstraktionen wird es einfacher. Doppeltest injizieren (Mocks, Stubs) und schreiben Sie schnelle Unit-Tests, die nicht von ... abhängen Datenbanken reale Netzwerkanrufe oder andere externe Systeme.
Schließlich leidet eine Codebasis, die den SOLID-Prinzipien folgt, weniger unter Code-Gerüche, Code-Verfall und „Spaghetti-Code“Anders ausgedrückt: Es zersetzt sich weniger mit die zeitEs häuft weniger technische Schulden an und ist in kritischen Umgebungen sicherer und stabiler.
S – Single Responsibility Principle in Python
Das Prinzip der Einzelverantwortung besagt, dass Eine Klasse sollte nur einen einzigen Grund für eine Änderung haben.Das bedeutet nicht, dass er eine einzige mikroskopische Sache tut, sondern vielmehr, dass er für einen klar definierten Funktionsbereich verantwortlich ist.
Wenn eine Klasse Geschäftslogik, Datenzugriff, Berichtsformatierung und Präsentationslogik vermischt, erfordert jede Änderung in einem dieser Bereiche die Anpassung desselben Codeabschnitts. Dies erhöht das Risiko von Kreuzfehler und Konflikte zwischen den Teams.
Denken Sie beispielsweise an eine Klasse Mitglied In Python repräsentiert diese Klasse nicht nur den Benutzer, sondern ist auch für die Datenbankverbindung, das Speichern von Benutzerdaten und das Generieren von Textberichten zuständig. Diese Klasse muss möglicherweise aus verschiedenen Gründen angepasst werden: beispielsweise bei Änderungen der Benutzerattribute, der Datenbank oder des Berichtsformats.
Die mit dem SRP vereinbare Alternative besteht darin, Verantwortlichkeiten zu trennen: eine Klasse oder Datenklasse für die Herrschaftsmodell des Benutzers, ein weiterer für den Ausdauer (Repository oder Gateway) und ein weiteres für das Berichte oder Ansichten generierenJede Änderung fällt in den entsprechenden Teil, wodurch die Kopplung verringert wird.
Etwas Ähnliches lässt sich an einem klassischen Python-Beispiel beobachten: einer Klasse Duck die die Ente definiert und gleichzeitig die Kommunikation zwischen Enten durch eine Methode steuert greet()Dieser Kommunikationsteil kann in ein Objekt extrahiert werden. CommunicatorDie Ente kann also nur fliegen, schwimmen und Laute von sich geben, der Vermittler hingegen kann einen Dialog zwischen zwei Vögeln artikulieren.
Dieser Ansatz passt sehr gut zu anderen Prinzipien wie zum Beispiel TROCKEN (Vermeidung von Logikwiederholungen in mehreren Klassen) und mit dem Ziel, eine hohe Kohäsion innerhalb jedes Moduls: Alle seine Funktionen und Attribute verweisen auf dieselbe geschäftliche Verantwortung.
O – Offen/Geschlossen-Prinzip angewendet auf Verhaltenserweiterungen
Das Offen/Geschlossen-Prinzip besagt, dass Software-Entitäten sollten erweiterbar, aber nicht veränderbar sein.Im Alltag ausgedrückt: Wir sollten in der Lage sein, neue Verhaltensvarianten hinzuzufügen, ohne bereits funktionierenden Code bearbeiten zu müssen.
Ein typisches Anti-Pattern in Python ist eine Klasse mit einer Methode, die das Verhalten einer Klasse bestimmt. if/elif Riese, je nach Art: zum Beispiel ein Flächenrechner das prüft, ob das Objekt Rectangle, Circle, Triangle Und so weiter. Jede neue geometrische Form erfordert das Hinzufügen eines weiteren Zweigs zur Methode und die Modifizierung einer bereits getesteten Klasse.
Die Achtung des OCP beinhaltet Programmierung gegen AbstraktionenIn diesem Fall definieren wir eine abstrakte Klasse. Shape mit einer Methode area() Wir sorgen dafür, dass jede Form (Rechteck, Kreis, Dreieck usw.) diese Methode implementiert. Der Flächenrechner ruft sie einfach auf. shape.area() ohne zu wissen, mit welchem genauen Typ er es zu tun hat.
Um auf Python zurückzukommen: Ein weiteres Beispiel, das in verschiedenen Quellen diskutiert wird, ist das eines Objekts. Communicator Diese Methode wird für die Kommunikation zwischen Vögeln verwendet. Wenn wir verschiedene Kommunikationsformen unterstützen möchten (einfach, formell, mit Emojis usw.), anstatt die Methode zu modifizieren, ... communicate() jedes Mal definieren wir ein Konversationsabstraktion (zum Beispiel, AbstractConversation) und wir haben Implementierungen wie diese erstellt SimpleConversation die die Phrasen generieren. Der Kommunikator gibt einfach aus, was die Konversation zurückgibt, ohne seinen eigenen Code zu verändern.
In der Geschäftswelt ist OCP entscheidend für die Risikominderung bei der Einführung von Änderungen: Es vermeidet die Wiedereröffnung weit verbreiteter Basisklassen und fördert... Erweiterung durch neue Unterklassen oder Strategien, wobei der stabile Code so weit wie möglich erhalten bleibt.
L – Liskovsches Substitutionsprinzip und korrekte Vererbung
Das Liskovsche Substitutionsprinzip besagt, dass Objekte einer Unterklasse sollten Objekte ihrer Oberklasse ersetzen können, ohne das System zu beschädigen.Mit anderen Worten: Wenn ein Code eine Instanz der Basisklasse erwartet, sollte er genauso funktionieren, wenn wir ihm eine Instanz einer beliebigen Unterklasse geben.
In der Praxis bedeutet dies, dass die Unterklasse nicht Erwartungen oder Vertrag brechen Das definiert die Basisklasse: Rückgabetypen, Ausnahmen, Vorbedingungen und Nachbedingungen müssen konsistent bleiben.
Ein bekanntes (und trügerisches) Beispiel ist die Hierarchie. Rechteck / QuadratMathematisch gesehen ist ein Quadrat ein Rechteck, aber im Code ist das nicht immer praktisch. Square direkt geerbt von RectangleBesitzt das Rechteck separate Methoden zum Festlegen von Höhe und Breite, kann eine Testmethode davon ausgehen, dass eine Änderung der Höhe die Breite unverändert lässt. Erzwingt die Klasse „Quadrat“ jedoch eine gleichzeitige Änderung beider Werte, bricht der Vertrag der Oberklasse plötzlich zusammen.
Etwas Ähnliches geschieht in Hierarchien wie Vogel In Python. Wenn die Basisklasse eine Methode enthält fly()Aber dann gibt es Unterklassen wie Ostrich oder Pinguine, die nicht fliegen können, erzwingen Sie diese Implementierung und starten Sie ein NotImplementedError verstößt gegen das LSP. Jede Funktion, die einen Bird und Ruf an fly() Ich sollte das ohne unerwartete Ausnahmen schaffen.
Die Lösung besteht in einer Neugestaltung der Hierarchie: Es wird eine generische Basisklasse beibehalten. Vogel ohne die Methode fly()und es werden Zwischenklassen wie beispielsweise erstellt FlyingBird y Schwimmender VogelVögel, die fliegen, implementieren das erste Prinzip; schwimmende Vögel das zweite; und solche, die beides können, erben von beiden. Auf diese Weise wird keine Unterklasse gezwungen, ein Verhalten zu bieten, das für sie keinen Sinn ergibt.
Die Einhaltung des LSP erfordert eine sorgfältige Betrachtung der Vererbungsbeziehungen in Python und führt in vielen Fällen zur Bevorzugung von einfache Komposition und Schnittstellen Angesichts tiefer Vererbungsbäume, bei denen es leicht ist, den Vertrag der Oberklasse zu verletzen.
I – Interface-Segregationsprinzip und spezifischere Klassen
Das Interface-Segregationsprinzip besagt, dass Wir sollten einen Kunden nicht dazu zwingen, auf Methoden zurückzugreifen, die er nicht anwendet.In der Praxis bedeutet dies, dass man kleine, spezifische Schnittstellen (oder abstrakte Klassen in Python) entwirft, anstatt einer einzigen riesigen Schnittstelle für alles.
Stellen Sie sich eine Schnittstelle vor Arbeitnehmer in Python, das die Methoden definiert work() y eat()Diese Schnittstelle ist sinnvoll für einen Menschen, der arbeitet und isst, aber nicht für einen Roboter, der nur arbeitet. Wenn wir die Klasse erzwingen Robot implementieren eat()wird mit einer leeren Methode oder einer Ausnahme enden, was darauf hinweist, dass „die Roboter „Sie essen nicht“, was ein klares Zeichen für einen Verstoß gegen die Internetdienstvereinbarung ist.
Die Lösung besteht darin, diese Schnittstelle aufzuteilen in verarbeitbar (etwas, das funktionieren kann) und Essbar (etwas, das man essen kann). Daher die Klasse Human implementiert beides, während Robot Es implementiert lediglich den Arbeitsteil. Jeder Kunde nutzt nur die Methoden, die er tatsächlich benötigt.
Um auf Vögel zurückzukommen: Etwas Ähnliches geschieht mit einer Schnittstelle, die Methoden wie die folgenden kombiniert: fly() y swim()Nicht alle Vögel tun beides, daher ist es sinnvoller, sie zu trennen in FlyingBird y Schwimmender VogelDas resultierende Design ist flexibler und vermeidet das Auffüllen irrelevanter Methoden mit Dummy-Code.
In Python gibt es zwar keine formalen Schnittstellen wie in Java, aber wir können denselben Effekt erzielen, indem wir abstrakte Klassen des Moduls abc oder Typisierungsprotokolle. Die Trennung von Schnittstellen macht jede Klasse besser definiert und verringert die Wahrscheinlichkeit, unnötige Abhängigkeiten einzuführen.
D – Prinzip der Abhängigkeitsumkehr und abstraktionsbasiertes Design
Das Prinzip der Abhängigkeitsumkehr hebt zwei Kernideen hervor: Module höherer Ebene sollten nicht von Modulen niedrigerer Ebene abhängen.und beide müssen auf Abstraktionen beruhenDarüber hinaus sollten Abstraktionen nicht von Details abhängen; Details hängen von Abstraktionen ab.
Ein sehr häufiger Fehler besteht darin, dass eine Domänen- oder Geschäftslogikklasse in Python direkt eine konkrete Instanz einer Datenbank, eines HTTP-Dienstes oder eines Messaging-Clients erzeugt: zum Beispiel eine Benutzer-Repository das intern tut self.database = MySQLDatabase()Wenn wir morgen für Testzwecke auf PostgreSQL oder eine In-Memory-Engine umsteigen wollen, müssen wir diese Klasse bearbeiten.
Die Anwendung von DIP beinhaltet die Einführung eines Datenbankabstraktion (zum Beispiel eine abstrakte Klasse) Database mit Methoden connect() y query()und konkrete Umsetzungen erstellen, wie zum Beispiel MySQLDatabase o PostgreSQLDatabaseDas Repository stellt die Instanziierung der Datenbank selbst ein und empfängt sie stattdessen von außen, üblicherweise über den Konstruktor.
Dieses Muster ist bekannt als Abhängigkeitsinjektion Und es ist eine der Säulen für die Erreichung von modularem und testbarem Code: In der Produktion injizieren wir eine reale Implementierung; beim Testen injizieren wir ein Test-Double, das das Verhalten simuliert, ohne externe Systeme zu berühren.
In einem komplexeren Beispiel kann man eine Abstraktion eines Kommunikationskanals definieren, wie etwa AbstractChannelund eine Abstraktion des Kommunikators, AbstractCommunicator, wer arbeitet mit AbstractConversationStattdessen SMSCommunicator erzeugt intern ein SMSChannelDie DIP-konforme Version stellt sicher, dass der Kommunikator im Konstruktor einen abstrakten Kanal empfängt. Das bedeutet, dass er nicht von einer konkreten Klasse, sondern von einer Schnittstelle abhängt und mit jedem Kanaltyp arbeiten kann, der den Vertrag erfüllt.
Zusammenfassend lässt sich sagen, dass DIP die Entwicklung von Schichten fördert, in denen die Geschäftslogik auf generischen Schnittstellen basiert und Frameworks, Konfigurationen oder Fabriken für die Verknüpfung dieser Schnittstellen mit spezifischen Implementierungen je nach Umgebung verantwortlich sind.
Praktisches Beispiel in Python: SOLID-Benachrichtigungsdienst
Ein sehr anschauliches Beispiel, um zu sehen, wie mehrere Gestaltungsprinzipien kombiniert werden, ist das eines Benachrichtigungssystem Das System kann Nachrichten über verschiedene Kanäle wie E-Mail oder SMS versenden und lässt sich zudem problemlos um neue Mittel (Push-Benachrichtigungen, Webhooks usw.) erweitern.
Ziel ist es, die Hauptlogik zu haben, die entscheidet: „Ich muss Sache X benachrichtigen“. muss nicht neu geschrieben werden jedes Mal, wenn wir einen Kanal hinzufügen, und dass wir ihn testen können, ohne während der Tests echte E-Mails oder SMS auszulösen.
Dazu definieren wir eine Abstraktion. Notifier mit einer Methode send(to, subject, body) die einen booleschen Wert zurückgibt, der Erfolg oder Misserfolg anzeigt. Darauf aufbauend erstellten wir Implementierungen wie diese: E-Mail-Benachrichtigung y SMS-Benachrichtigungjeweils einen externen Client in seinem Konstruktor empfangen (zum Beispiel smtp_client o sms_client) zuständig für die Kommunikation mit dem königlichen Dienst.
Zu all dem kommt noch hinzu, Benachrichtigungsdienst die eine Liste von Benachrichtigungseinheiten (alles, was den Vertrag implementiert) empfängt und eine Methode anbietet, um Senden Sie eine Benachrichtigung über alle KanäleDieser Dienst weiß nichts über SMTP, SMS-Anbieter oder HTTP-APIs: Er durchläuft lediglich Iterationen und ruft diese auf. send() über jeden Benachrichtigungsdienst.
Dieses Design veranschaulicht deutlich mehrere Prinzipien in der Praxis:
- SRPJede Klasse hat eine sehr begrenzte Verantwortung: a
EmailNotifierEs verarbeitet ausschließlich E-Mails.SMSNotifierNur SMS, der Orchesterdienst und externe Kunden wissen nur über den Transport Bescheid. - DIP:
NotificationServiceEs hängt von der Abstraktion ab.Notifiernicht von bestimmten Klassen, und die Benachrichtigungsfunktionen wiederum hängen von injizierten Clients ab. - OCP: um ein hinzuzufügen
PushNotifieroWebhookNotifierEs besteht keine Notwendigkeit, es zu berühren.NotificationServiceErstellen Sie einfach eine neue Implementierung und registrieren Sie diese. - LSP und ISP: alle Implementierungen von
NotifierSie respektieren denselben Vertrag.send(), ohne obligatorische Methoden, die für einen bestimmten Kanal keinen Sinn ergeben.
Aus Testperspektive genügt es, ein oder mehrere zu erstellen. DummyNotifier die die Anrufe aufzeichnen send() Ohne weitere Maßnahmen. Durch die Einbindung in den Dienst können wir überprüfen, ob alle Benachrichtigungen ausgelöst wurden, ohne dass Netzwerke, Warteschlangen oder externe Systeme erforderlich sind.
Dieses Beispiel kann auch durch Prinzipien wie die folgenden ergänzt werden: TROCKEN (Vermeidung der Duplizierung gemeinsamer Formatierungslogik), KISS (Führen Sie Wiederholungsversuche, Warteschlangen oder Priorisierung erst dann ein, wenn dies wirklich notwendig ist) und YAGNI (Erwarten Sie keine Funktionen, die nicht in unserer unmittelbaren Roadmap enthalten sind).
SOLIDs Verhältnis zu anderen Gestaltungsprinzipien
Obwohl SOLID den Fokus auf objektorientiertes Design legt, wird es in der Praxis mit anderen allgemeinen Software-Designprinzipien kombiniert, wie zum Beispiel TROCKNEN, KÜSSEN und YAGNIdie auch in der Literatur zu sauberem Code immer wieder auftauchen.
DRY (Wiederhole dich nicht) Denken Sie daran, dass wichtige Geschäftslogik nur einmal im System repräsentiert sein sollte. Dies entspricht perfekt dem Single-Responsibility-Prinzip (SRP) und dem Objective-Case-Prinzip (OCP): Wenn wir Verhalten in verschiedenen Klassen wiederholen, erfordert die Erweiterung oder Korrektur eines Anwendungsfalls Änderungen an vielen Stellen und erhöht das Risiko von Inkonsistenzen.
KISS (Keep It Simple, Stupid) Es ermutigt uns, unnötig komplexe Architekturen und Abstraktionen zu vermeiden. Auch wenn es widersprüchlich erscheinen mag: Die Anwendung von SOLID bedeutet nicht, das Projekt um seiner selbst willen mit Schnittstellen zu überfrachten, sondern nur so viele einzuführen, wie nötig sind, um die Funktionalität zu erhalten. verständliches Design für das Team.
YAGNI (Du wirst es nicht brauchen) Es ist ein Gegenmittel gegen Überdesign. In Projekten mit stark schwankenden Anforderungen kann der Versuch, alle möglichen zukünftigen Änderungen durch das Hinzufügen immer neuer Schichten vorherzusehen, kontraproduktiv sein. Der ideale Ansatz besteht darin, ein Gleichgewicht zu finden: Das SOLID-Prinzip sollte auf die Teile angewendet werden, von denen wir wissen, dass sie wachsen oder sich verändern werden, und die Teile sollten so einfach wie möglich gehalten werden, von denen wir wissen, dass sie stabil sind.
Zusammengenommen führen all diese Prinzipien zu einem Entwicklungsstil, bei dem die Priorität darin besteht, klarer, modularer und kollaborationsorientierter Codeaber ohne in den Fetischismus der Architektur um der Architektur willen zu verfallen.
Vorteile, Grenzen und Fälle, in denen sich Purismus nicht auszahlt
Die SOLID-Prinzipien gelten nahezu als Goldene Regel bei mittleren und großen ProjektenDas heißt aber nicht, dass sie in jedem Kontext wörtlich angewendet werden sollten. Es ist auch wichtig, ihre Grenzen zu kennen.
Bei sehr kleinen Anwendungen, einmaligen Skripten oder Projekten mit geringem Budget, die nicht langfristig gewartet werden sollen, kann die Einführung zu vieler Abstraktionsebenen problematisch sein. teurer als nützlichDer mentale und programmiertechnische Aufwand, der mit der Anwendung von DIP oder ISP auf alles verbunden ist, lohnt sich möglicherweise nicht.
Es gibt auch Situationen mit sehr enge Zeitvorgaben oder die Entwicklung eines MVP, bei dem die schnelle Bereitstellung Priorität hat. Es ist sinnvoll, einige der SOLID-Optimierungen auf einen späteren Zeitpunkt zu verschieben, wenn das Produkt stabil ist und wir wissen, welche Teile sich bewähren werden.
In Altsystemen mit viel Legacy-CodeDer Versuch, alles auf einmal zu „festigen“, ist in der Regel unrealistisch. In solchen Fällen ist es besser, die Prinzipien schrittweise auf häufig genutzte Bereiche des Codes oder auf Bereiche, die neue Funktionen blockieren, anzuwenden.
Schließlich gibt es Domänen, die sehr empfindlich reagieren auf extreme Leistung Beispielsweise bei bestimmten häufig ausgeführten Routinen, bei denen Abstraktionsschichten unerwünschten Mehraufwand verursachen können. In solchen Fällen ist es mitunter gerechtfertigt, in bestimmten Bereichen Abstriche bei der Reinheit des SOLID-Konzepts zu machen, sofern das restliche System weiterhin gut konzipiert ist.
Zusammenfassend lässt sich sagen, dass das Verständnis und die Anwendung der SOLID-Prinzipien in Python es Ihnen ermöglichen, von Code, der „einfach funktioniert“, zu Code zu gelangen, der Es hat den Test der Zeit gut bestandenEs passt sich besser an Veränderungen im Geschäftsbetrieb an, erleichtert die Zusammenarbeit im Team und reduziert Überraschungen in der Produktion; entscheidend ist, sie mit Bedacht einzusetzen und zu wissen, wann es sich lohnt, eine weitere Abstraktion einzuführen und wann es besser ist, die Dinge einfach und unkompliziert zu halten.
Leidenschaftlicher Autor über die Welt der Bytes und der Technologie im Allgemeinen. Ich liebe es, mein Wissen durch Schreiben zu teilen, und genau das werde ich in diesem Blog tun und Ihnen die interessantesten Dinge über Gadgets, Software, Hardware, technologische Trends und mehr zeigen. Mein Ziel ist es, Ihnen dabei zu helfen, sich auf einfache und unterhaltsame Weise in der digitalen Welt zurechtzufinden.
