Das Erstellen eines neuen Systems ist ein mehrstufiger Prozess: Ausarbeitung des Konzepts und des Designs, Architekturdesign, Implementierung, Testen, Freigabe. Architekturdesign und -implementierung sind die Phasen, mit denen sich Entwickler hauptsächlich befassen.
Die meisten Entwickler beschäftigen sich gerne mit Architektur und überlegen, wie das System oder ein Teil davon von Grund auf neu angeordnet wird. Wenn jemand die Architektur des Systems durchdacht und implementiert hat, gibt es keine Probleme mit der Motivation: Der Programmierer wird durch die Umsetzung seiner Ideen zufrieden sein. Aber wenn ein Gedanke an Architektur und ein anderer an der Umsetzung beteiligt sein wird, dann kann letzterer eine natürliche Empörung haben: Sie haben alles für mich überlegt, aber kann ich einfach das tun, was geschrieben steht?

In diesem Artikel wird erläutert, wie solche Situationen vermieden werden können, warum die Implementierung nicht weniger interessant sein kann als die Ausarbeitung der Architektur und manchmal auch mehr.
Einführung
Eine durchdachte Architektur kann als Grundlage für Ausfallaufgaben verwendet werden : Die Implementierung jeder ausreichend separaten Komponente wird zu einer separaten Unteraufgabe.
Wenn es beispielsweise eine Abfrageverarbeitungspipeline gibt, die im Stil von Pipes & Filtern entworfen wurde , sind die Unteraufgaben die Implementierung einzelner Verarbeitungsschritte (jeder Schritt hat seine eigene Unteraufgabe) und einer anderen Unteraufgabe, um alle Schritte miteinander zu verbinden.
Eine durchdachte Architektur und die Aufteilung in Unteraufgaben geben zwar eine allgemeine Vorstellung davon, wie ein System erstellt und die Bewertung der Arbeitskosten ermöglicht wird, reichen jedoch nicht aus, um den Plan umzusetzen. In der Beschreibung der Unteraufgabe wird angegeben, was die Komponente tun soll. Sie enthält möglicherweise Anforderungen an Geschwindigkeit und Speicherverbrauch, enthält jedoch keine umfassenden Anweisungen dazu.
Tatsache ist, dass es viele Optionen gibt, um eine Komponente herzustellen, die die angegebenen Anforderungen erfüllt. Viel hängt davon ab, wie es implementiert wird: Codeflexibilität, Erweiterbarkeit, einfache Unterstützung usw. Wir kommen dem Konzept des Code-Designs nahe .
Code-Design-Konzept
Manchmal wird Code-Design als Architektur oder Code-Organisation bezeichnet, manchmal sogar nur als Architektur. Ich halte mich an den Begriff Code-Design, weil er sich von der Systemarchitektur abhebt und eine klare Linie zwischen ihnen zieht. Um genauer zu sein, betrachten Sie ein Beispiel.
Nehmen wir an, wir entwickeln das Backend einer Website, die immer beliebter wird. Die Anzahl der Server hat bereits mehrere zehn überschritten, das Publikum wächst und wir beschließen, Analysen zum Benutzerverhalten auf der Website zu sammeln: Beliebtheit von Besuchsseiten, Häufigkeit der Nutzung von Funktionen abhängig vom Benutzerprofil usw.
Es treten eine Reihe von architektonischen und technologischen Problemen auf: Wo werden Metriken gespeichert, wie werden sie über das Netzwerk übertragen, was ist zu tun, wenn der Metrikspeicher nicht verfügbar ist, wie der Backend-Dienst Metriken aufzeichnet usw. Die Architektur muss nur diese Fragen beantworten, die Komponenten der Lösung bestimmen und Anforderungen für sie festlegen.
Angenommen, wir haben eine Architektur entwickelt: Wir werden InfluxDB als Speicher verwenden, Metriken mithilfe von UDP für das Telegraf über das Netzwerk übertragen und die Nichtverfügbarkeit des Speichers umgehen, um die Metriken in Kafka zu speichern, die auf mehreren Servern repliziert wurden. Alle Metriken gehen den Weg Backend-Service -> Telegraf -> Kafka -> InfluxDB. Um die Metriken zu schreiben, hat das Backend beschlossen, ein Modul zu schreiben, das die Metrikübertragungsfunktion in Telegraf mithilfe von UDP implementiert.
Das Modul zum Aufzeichnen von Metriken ist eine separate Komponente des Systems. Das Schreiben ist eine separate Unteraufgabe, die dem Entwickler anvertraut werden kann. Diese Unteraufgabe enthält viele Lösungen und Fragen, die beantwortet werden müssen: Metriken werden synchron oder asynchron gesendet. Wie wird der gleichzeitige Zugriff auf mehrere Backend-Threads synchronisiert, welche Hauptklassen / Funktionen werden verwendet?
Diese Fragen gehen über die Beschreibung der Lösungsarchitektur hinaus, aber die Antworten darauf haben weitreichende Konsequenzen. Wenn beispielsweise während des Betriebs einer Lösung klar wird, dass der Technologie-Stack nicht optimal ist und Sie Telegraf durch eine alternative Lösung ersetzen müssen, kann dies durch eine falsche Unterteilung des Moduls in Klassen nicht ohne Umschreiben des gesamten Moduls erfolgen. Die Antworten auf diese Fragen liegen im Bereich des Code-Designs .
Die Entwicklung des Code-Designs ist eine separate Entwurfsphase , die zwischen der Entwicklung der Systemarchitektur und der Codierung liegt. Wenn Sie eine Grenze zwischen Architektur und Code-Design ziehen, können Sie ein System entwerfen, ohne alle Details zu berücksichtigen, und die Arbeitskosten in einer begrenzten Zeit bewerten. Wenn Sie andererseits die Entwicklung des Code-Designs als separate Implementierungsphase hervorheben, können Sie die Qualität der Systemimplementierung verbessern, die Kosten für weitere Verbesserungen senken und den Support vereinfachen.
Die Notwendigkeit, das Code-Design in der Implementierungsphase vor dem Codieren zu überdenken, macht die Implementierung interessant : Die Aufgaben beim Entwerfen eines Code-Designs können nicht weniger interessant sein als das Entwerfen eines gesamten Systems auf Architekturebene. Diese Idee wurde von Brooks im mythischen Mannmonat zum Ausdruck gebracht.
Natürlich ist es möglicherweise nicht so einfach, eine Grenze zwischen Architektur und Code-Design zu ziehen. Schauen wir uns dieses Problem genauer an.
Die Grenze zwischen Architektur und Code-Design
Ideologisch gesehen befinden sich Architektur und Code-Design auf verschiedenen Designebenen: Architektur wird in der Anfangsphase durchdacht, wenn wenig Sicherheit besteht, und das Nachdenken über das Code-Design fügt Details hinzu. Dementsprechend werden sie zu unterschiedlichen Zeitpunkten ausgeführt: Die Architektur ist näher am Anfang und das Code-Design während der Implementierung von Unteraufgaben.
Das Zeichnen einer Grenze zwischen diesen beiden Entwurfsstufen hängt von einer Reihe von Faktoren ab. Hier sind die wichtigsten:
- Das Ausmaß, in dem die Komponente das System beeinflusst. Manchmal kann das Gerät des gesamten Systems erheblich vom Gerät seiner einzelnen Komponente abhängen. In diesem Fall müssen Sie die Komponente in der Architekturentwicklungsphase und nicht in der Implementierungsphase entwerfen.
- Das Vorhandensein einer klaren Schnittstelle für die Komponente. Es ist nur möglich, das Design einer Komponente als Unteraufgabe zu isolieren, wenn klar definiert ist, was diese Komponente tun soll und wie sie mit dem Rest des Systems interagiert.
- Realistische Schätzungen des Aufwands zur Vervollständigung der Teilaufgabe. Die Aufgabe ist möglicherweise zu groß, um die Arbeitskosten mit ausreichender Genauigkeit bewerten zu können. In diesem Fall ist es besser, die Aufgabe detaillierter zu gestalten und in eigene Unteraufgaben zu unterteilen, um eine angemessenere Bewertung der Arbeitskosten zu erhalten.
Es gibt mehrere Sonderfälle, in denen Sie eine gute Grenze zwischen Architekturdesign und Code-Design ziehen können.
Die Komponente verfügt über eine strikte API.
In meiner Praxis gab es beispielsweise eine Aufgabe: Auf einer UNIX-Socket-API zu implementieren, um Betriebssystemressourcen zu erfassen / freizugeben, die von einem vorhandenen Dämon verwendet werden. Diese Aufgabe entstand im Rahmen der gewählten Architektur für das neue epische Feature . Im Rahmen der Architektur war es ziemlich allgemein, die API zu beschreiben, und das detaillierte Design wurde später während der Implementierung durchgeführt.
Modul / Klasse mit einer bestimmten Schnittstelle
Der einfachste Weg, den Entwurf eines Teils eines monolithischen Systems zu delegieren, besteht darin, ein Modul oder eine Klasse hervorzuheben, ihre Schnittstelle und Aufgaben zu beschreiben. Ein Modul, das als separate Unteraufgabe zugewiesen ist, sollte nicht zu groß sein. Beispielsweise ist die Clientbibliothek für den Zugriff auf die Shard-Datenbank zweifellos ein separates Modul, aber die Aufgabe der Implementierung dieser Bibliothek wird ohne ein detaillierteres Design nur schwer anhand der Arbeitskosten bewertet werden können. Andererseits ist die Aufgabe, eine zu kleine Klasse zu implementieren, trivial. Wenn beispielsweise die Unteraufgabe "Implementieren einer Funktion, die die Existenz eines bestimmten Ordners anhand eines bestimmten Pfads überprüft" auftritt, wird die Architektur eindeutig zu detailliert durchdacht.
Kleine Komponente mit festen Anforderungen
Wenn die Komponente klein genug ist und das Problem, das sie löst, genau definiert ist, können die Arbeitskosten für die Implementierung mit ausreichender Genauigkeit geschätzt werden, und die Implementierung der Komponente selbst lässt Raum für Design. Beispiel: Ein Prozess, der auf einer Krone ausgeführt wird und alte Dateien und Verzeichnisse unter einem bestimmten Pfad rekursiv löscht.
Antipatterns
Es gibt Szenarien, in denen die Verteilung zwischen dem Nachdenken über die Architektur und der Implementierung nicht richtig ist. Einige davon werden im Folgenden erläutert.
Alles ist bis ins kleinste Detail gestaltet.
Detaillierte UML-Diagramme wurden erstellt, die Signatur jeder Methode jeder Klasse wurde angegeben, die Algorithmen zur Implementierung einzelner Methoden wurden beschrieben ... Nach einer so detaillierten Beschreibung können Sie das System wirklich am schnellsten implementieren, da alles so detailliert geplant ist, dass überhaupt kein Raum für Kreativität vorhanden ist. Nehmen Sie es und tun Sie es geschrieben. Wenn der Entwickler das Ziel hat, so schnell wie möglich zu codieren, was er zu ihm sagt, können Sie dies.
Wenn Sie jedoch etwas tiefer graben, wird eine Reihe von Mängeln bei der Organisation der Arbeit in diesem Sinne deutlich. Erstens müssen Sie viel Zeit mit dem Design selbst verbringen, um alles in solchen Details zu entwerfen. Was der Entwickler normalerweise vor der Implementierung denkt, wird der Architekt in diesem Schema durchdenken: Der gesamte Entwurf verschiebt sich näher an den Beginn des Projekts, was seine Dauer verlängern kann. Wenn Sie die Konstruktionsarbeit nicht in Teile zerlegen, können Sie sie schließlich nicht parallelisieren. Zweitens wird der Mangel an Designarbeit während der Implementierung die Motivation der Entwickler erheblich verringern: Genau das zu tun, was sie sagen, kann für Anfänger nützlich sein, aber erfahrene Entwickler werden sich langweilen. Drittens kann dieser Ansatz im Allgemeinen die Qualität der Ausgabe verringern: Das System, das nicht in ausreichend unabhängige Komponenten unterteilt ist, ist schwieriger zu warten und zu erweitern.
Architektur wird immer von einem Entwickler entworfen, der Rest Rauch beiseite nur realisieren
Zunächst sollten mehrere Fälle erwähnt werden, in denen dies nützlich sein kann. Erstens ist es ein Team, in dem es viele Anfänger und nur einen erfahrenen Programmierer gibt. In diesem Fall verfügen Anfänger nicht über genügend Erfahrung, um die Architektur so zu gestalten, dass sie die Arbeit mit ausreichender Qualität erledigt. Gleichzeitig hilft ihnen die Implementierung einer durchdachten Architektur, ihr Niveau zu verbessern. Zweitens handelt es sich um große Projekte, an denen mehrere Teams beteiligt sind. Dann wird das Design der Architektur des Projekts in zwei Ebenen unterteilt: Der Architekt denkt es als Ganzes durch und jedes Team - die Architektur der Komponenten in seinem Verantwortungsbereich.
Betrachten Sie jedoch ein Team, das aus ausreichend erfahrenen Spezialisten besteht. Wenn Architekturaufgaben immer nur einem, beispielsweise dem erfahrensten Entwickler, zugewiesen werden, können andere Entwickler ihre Fähigkeiten nicht vollständig offenlegen. Die Systemarchitektur wird einseitig sein, da jeder eine Reihe von Techniken hat, die er anwendet. Wenn verschiedene Entwickler an die Architektur verschiedener Komponenten / Subsysteme denken würden, würde dies den Erfahrungsaustausch und die Entwicklung von Teammitgliedern erleichtern. Selbst nicht allzu erfahrene Teammitglieder sollten manchmal architektonische Aufgaben erhalten: Dies erhöht ihr Niveau und erhöht ihre Beteiligung am Projekt.
Fazit
Das Vorhandensein der Entwurfsphase in der Implementierung ist der Hauptfaktor, der Implementierungsaufgaben interessant macht. Natürlich gibt es noch andere: den Einsatz neuer Technologien, Forschungsaufgaben, aber sie sind in der Regel viel seltener. Wenn die Implementierungsaufgaben kein Design erfordern und aus einer einfachen Codierung bestehen, wirkt sich dies stark auf die Motivation der Entwickler aus und ermöglicht nicht den Einsatz ihrer Fähigkeiten.
Durch das Entwerfen eines Code-Designs in der Implementierungsphase können Sie schnell angemessene Schätzungen der Arbeitskosten vornehmen, die Arbeit effizienter parallelisieren und im Allgemeinen die Qualität des Systems verbessern.
Die Notwendigkeit, während der Implementierung ein Code-Design zu entwerfen, macht die Implementierungsarbeit in den Augen der Entwickler interessant.
Es lohnt sich nicht, Fehler zu machen, indem Sie Entwurfsarbeiten von der Implementierung von Unteraufgaben ausschließen, so wie Sie Architekturaufgaben nicht immer nur dem erfahrensten Entwickler anvertrauen sollten.