Die 10 hÀufigsten Fehler bei der Arbeit mit der Spring-Plattform. Teil 1

Hallo an alle. Heute teilen wir den ersten Teil des Artikels, dessen Übersetzung speziell fĂŒr Studenten des Kurses "Developer on the Spring Framework" erstellt wurde . Fangen wir an!





Spring ist vielleicht eine der beliebtesten Java-Entwicklungsplattformen. Dies ist ein mĂ€chtiges, aber ziemlich schwer zu erlernendes Werkzeug. Die grundlegenden Konzepte sind relativ einfach zu verstehen und zu erlernen, aber es erfordert Zeit und MĂŒhe, ein erfahrener Spring-Entwickler zu werden.

In diesem Artikel werden einige der hÀufigsten Fehler bei der Arbeit in Spring behandelt, die sich insbesondere auf die Entwicklung von Webanwendungen und die Verwendung der Spring Boot-Plattform beziehen. Wie auf der Spring Boot-Website erwÀhnt , verfolgt Spring Boot einen standardisierten Ansatz zum Erstellen gebrauchsfertiger Anwendungen. Dieser Artikel folgt diesem Ansatz. Es enthÀlt eine Reihe von Empfehlungen, die bei der Entwicklung von Standard-Webanwendungen auf Basis von Spring Boot effektiv verwendet werden können.

Falls Sie mit der Spring Boot-Plattform nicht sehr vertraut sind, aber mit den Beispielen in diesem Artikel experimentieren möchten, habe ich ein GitHub-Repository mit zusĂ€tzlichen Materialien fĂŒr diesen Artikel erstellt . Wenn Sie diesen Artikel irgendwann etwas verwirrt lesen, wĂŒrde ich Ihnen raten, einen Klon dieses Repositorys zu erstellen und mit dem Code auf Ihrem Computer zu experimentieren.

HĂ€ufiger Fehler Nr. 1: Zu einfache Programmierung


Wir können diesem weit verbreiteten Fehler leicht erliegen, da das "Syndrom der Ablehnung der Entwicklung eines anderen" fĂŒr die Programmierumgebung recht typisch ist. Eines seiner Symptome ist das stĂ€ndige Umschreiben von hĂ€ufig verwendeten Codeteilen, und dies ist ein Symptom, das von vielen Programmierern gesehen wird.
Das Studium der internen Struktur und der Implementierungsfunktionen einer bestimmten Bibliothek ist hĂ€ufig ein nĂŒtzlicher, notwendiger und interessanter Prozess. Wenn Sie jedoch wĂ€hrend der Implementierung von Funktionen auf niedriger Ebene stĂ€ndig denselben Codetyp schreiben, kann sich dies als schĂ€dlich fĂŒr Sie als Softwareentwickler herausstellen. Aus diesem Grund existieren und werden Abstraktionen und Plattformen wie Spring verwendet. Sie ersparen Ihnen wiederholte manuelle Arbeit und ermöglichen es Ihnen, sich auf Objekte Ihres Themenbereichs und der Programmcodelogik auf einer höheren Ebene zu konzentrieren.

Abstraktionen sollten daher nicht vermieden werden. Wenn Sie das nĂ€chste Mal mit der Notwendigkeit konfrontiert sind, ein bestimmtes Problem zu lösen, fĂŒhren Sie zunĂ€chst eine schnelle Suche durch und finden Sie heraus, ob die Bibliothek, die dieses Problem löst, bereits in Spring integriert ist. Sie werden wahrscheinlich eine geeignete schlĂŒsselfertige Lösung finden. Eine solche nĂŒtzliche Bibliothek ist Project Lombok , die Anmerkungen, aus denen ich in den Beispielen in diesem Artikel verwenden werde. Lombok wird als Vorlagencode-Generator verwendet, daher wird der faule Entwickler, der in jedem von uns lebt, sehr glĂŒcklich sein, diese Bibliothek kennenzulernen. Sehen Sie sich beispielsweise an, wie die Standard-JavaBean-Komponente in Lombok aussieht:

@Getter @Setter @NoArgsConstructor public class Bean implements Serializable { int firstBeanProperty; String secondBeanProperty; } 

Wie Sie vielleicht bereits verstanden haben, wird der obige Code in die folgende Form konvertiert:

 public class Bean implements Serializable { private int firstBeanProperty; private String secondBeanProperty; public int getFirstBeanProperty() { return this.firstBeanProperty; } public String getSecondBeanProperty() { return this.secondBeanProperty; } public void setFirstBeanProperty(int firstBeanProperty) { this.firstBeanProperty = firstBeanProperty; } public void setSecondBeanProperty(String secondBeanProperty) { this.secondBeanProperty = secondBeanProperty; } public Bean() { } } 

Beachten Sie, dass Sie höchstwahrscheinlich das entsprechende Plug-In installieren mĂŒssen, wenn Sie Lombok in Ihrer integrierten Entwicklungsumgebung verwenden möchten. Die Version dieses Plugins fĂŒr die IntelliJ IDEA finden Sie hier .

HĂ€ufiger Fehler Nummer 2. „Leck“ interner Strukturen


Der Zugang zu internen Strukturen war nie eine gute Idee, da dies die FlexibilitĂ€t des Servicemodells beeintrĂ€chtigt und infolgedessen zur Bildung eines schlechten Programmierstils beitrĂ€gt. Das „Leck“ interner Strukturen manifestiert sich in der Tatsache, dass auf die Datenbankstruktur von bestimmten API-Endpunkten aus zugegriffen werden kann. Angenommen, das folgende „gute alte Java-Objekt“ (POJO) reprĂ€sentiert eine Tabelle in Ihrer Datenbank:

 @Entity @NoArgsConstructor @Getter public class TopTalentEntity { @Id @GeneratedValue private Integer id; @Column private String name; public TopTalentEntity(String name) { this.name = name; } } 

Angenommen, es gibt einen Endpunkt, der auf die Daten eines TopTalentEntity Objekts TopTalentEntity . Das ZurĂŒckgeben von Instanzen von TopTalentEntity scheint eine verlockende Idee zu sein. Eine flexiblere Lösung wĂ€re jedoch, eine neue Klasse zu erstellen, die TopTalentEntity-Daten fĂŒr den API-Endpunkt darstellt:

 @AllArgsConstructor @NoArgsConstructor @Getter public class TopTalentData { private String name; } 

Das Vornehmen von Änderungen an den internen Komponenten der Datenbank erfordert daher keine zusĂ€tzlichen Änderungen auf Serviceebene. Mal sehen, was passiert, wenn das Kennwortfeld zur TopTalentEntity Klasse hinzugefĂŒgt wird, um Benutzerkennwort-Hashes in der Datenbank zu speichern: Wenn kein Connector wie TopTalentData ist und der Entwickler vergisst, den Schnittstellenteil des Dienstes zu Ă€ndern, kann dies zu einer sehr unerwĂŒnschten Offenlegung geheimer Informationen fĂŒhren!

HÀufiger Fehler Nummer 3. Kombinieren von Funktionen, die besser im Code verteilt werden können


Das Organisieren von Anwendungscode wÀhrend des Wachstums wird zu einer immer wichtigeren Aufgabe. Seltsamerweise funktionieren die meisten Prinzipien einer effektiven Programmierung nicht mehr, wenn ein bestimmter Entwicklungsgrad erreicht ist, insbesondere wenn die Anwendungsarchitektur nicht gut durchdacht ist. Und einer der am hÀufigsten gemachten Fehler ist die Kombination von Funktionen im Code, deren separate Implementierung sinnvoller ist.

Der Grund fĂŒr die Verletzung des Grundsatzes der Aufgabentrennung ist in der Regel die HinzufĂŒgung neuer Funktionen zu bestehenden Klassen. Vielleicht ist dies eine gute momentane Lösung (insbesondere erfordert es das Schreiben einer geringeren Menge an Code), aber in Zukunft wird es unweigerlich zu einem Problem, auch in den Phasen des Testens und Verwaltens des Codes und zwischen diesen. Betrachten Sie den folgenden Controller, der TopTalentData aus dem Repository zurĂŒckgibt:

 @RestController public class TopTalentController { private final TopTalentRepository topTalentRepository; @RequestMapping("/toptal/get") public List<TopTalentData> getTopTalent() { return topTalentRepository.findAll() .stream() .map(this::entityToData) .collect(Collectors.toList()); } private TopTalentData entityToData(TopTalentEntity topTalentEntity) { return new TopTalentData(topTalentEntity.getName()); } } 

Auf den ersten Blick scheint es keine offensichtlichen Fehler in diesem Codefragment zu geben. Es enthĂ€lt eine Liste der TopTalentData Objekte, die aus Instanzen der TopTalentEntity Klasse stammt. Wenn Sie sich den Code genauer ansehen, werden Sie TopTalentController dass TopTalentController in Wirklichkeit mehrere Aktionen ausfĂŒhrt, nĂ€mlich Anforderungen mit einem bestimmten Endpunkt korreliert, Daten aus dem Repository TopTalentRepository und von TopTalentRepository empfangene TopTalentRepository in ein anderes Format konvertiert. Eine sauberere Lösung sollte diese Funktionen in separate Klassen unterteilen. Zum Beispiel könnte es so aussehen:

 @RestController @RequestMapping("/toptal") @AllArgsConstructor public class TopTalentController { private final TopTalentService topTalentService; @RequestMapping("/get") public List<TopTalentData> getTopTalent() { return topTalentService.getTopTalent(); } } @AllArgsConstructor @Service public class TopTalentService { private final TopTalentRepository topTalentRepository; private final TopTalentEntityConverter topTalentEntityConverter; public List<TopTalentData> getTopTalent() { return topTalentRepository.findAll() .stream() .map(topTalentEntityConverter::toResponse) .collect(Collectors.toList()); } } @Component public class TopTalentEntityConverter { public TopTalentData toResponse(TopTalentEntity topTalentEntity) { return new TopTalentData(topTalentEntity.getName()); } } 

Ein zusĂ€tzlicher Vorteil dieser Hierarchie besteht darin, dass wir anhand des Klassennamens verstehen können, wo sich eine Funktion befindet. Anschließend können wir wĂ€hrend des Testens den Code einer dieser Klassen bei Bedarf problemlos durch Ersatzcode ersetzen.

HĂ€ufige Fehlernummer 4. Einheitlicher Code und schlechte Fehlerbehandlung


Das Thema der Code-Einheitlichkeit gilt nicht nur fĂŒr Spring (oder allgemein fĂŒr Java), ist jedoch ein wichtiger Aspekt, der bei der Arbeit mit Projekten in Spring berĂŒcksichtigt werden muss. Die Auswahl eines bestimmten Programmierstils kann ein Diskussionsthema sein (und ist normalerweise innerhalb des Entwicklungsteams oder im gesamten Unternehmen konsistent), aber in jedem Fall trĂ€gt das Vorhandensein eines gemeinsamen Standards zum Schreiben von Code zu einer Steigerung der Arbeitseffizienz bei. Dies gilt insbesondere dann, wenn mehrere Personen am Code arbeiten. Einheitlicher Code kann von Entwickler zu Entwickler weitergegeben werden, ohne dass viel Aufwand fĂŒr die Wartung oder ausfĂŒhrliche ErklĂ€rungen erforderlich ist, warum diese oder jene Klassen benötigt werden.

Stellen Sie sich vor, es gibt ein Spring-Projekt, in dem verschiedene Konfigurationsdateien, Dienste und Controller vorhanden sind. Nach der semantischen Einheitlichkeit bei der Benennung erstellen wir eine Struktur, anhand derer Sie leicht suchen können und in der jeder Entwickler den Code leicht verstehen kann. Sie können beispielsweise das Konfigurationssuffix zu den Namen der Konfigurationsklassen, das Dienstsuffix zu den Dienstnamen und das Controller-Suffix zu den Controllernamen hinzufĂŒgen.

Die serverseitige Fehlerbehandlung hÀngt eng mit der Code-Einheitlichkeit zusammen und verdient besondere Aufmerksamkeit. Wenn Sie jemals Ausnahmen behandelt haben, die von einer schlecht geschriebenen API stammen, wissen Sie wahrscheinlich, wie schwierig es ist, die Bedeutung dieser Ausnahmen richtig zu verstehen und noch schwieriger festzustellen, warum sie tatsÀchlich aufgetreten sind.
Als API-Entwickler möchten Sie im Idealfall alle Endpunkte abdecken, mit denen der Benutzer arbeitet, und sie dazu bringen, ein einziges Fehlermeldungsformat zu verwenden. In der Regel bedeutet dies, dass Sie Standardfehlercodes und deren Beschreibung verwenden und zweifelhafte Entscheidungen wie das Versenden einer Meldung „500 Internal Server Error“ oder Stack-Trace-Ergebnisse an den Benutzer aufgeben mĂŒssen (letztere Option sollte ĂŒbrigens auf jeden Fall vermieden werden, da Sie interne Daten preisgeben Außerdem sind solche Ergebnisse auf Kundenseite schwer zu verarbeiten.
Hier zum Beispiel, wie das allgemeine Format der Fehlermeldung aussehen könnte:

 @Value public class ErrorResponse { private Integer errorCode; private String errorMessage; } 

Ein Ă€hnliches Format findet sich hĂ€ufig in den gĂ€ngigsten APIs und funktioniert in der Regel hervorragend, da es einfach und systematisch dokumentiert werden kann. Sie können eine Ausnahme in dieses Format konvertieren, indem Sie der @ExceptionHandler Annotation @ExceptionHandler hinzufĂŒgen (ein Beispiel fĂŒr die Annotation finden Sie im Abschnitt "HĂ€ufiger Fehler Nr. 6").

HĂ€ufiger Fehler Nummer 5. Falsche Arbeit mit Multithreading


Das Implementieren von Multithreading kann eine schwierige Aufgabe sein, unabhĂ€ngig davon, ob es in Desktopanwendungen oder Webanwendungen, in Spring-Projekten oder in Projekten fĂŒr andere Plattformen verwendet wird. Probleme, die durch die parallele AusfĂŒhrung von Programmen verursacht werden, sind schwer zu verfolgen, und die Behandlung mit einem Debugger ist oft sehr schwierig. Wenn Sie verstehen, dass es sich um einen parallelen AusfĂŒhrungsfehler handelt, mĂŒssen Sie höchstwahrscheinlich den Debugger verlassen und den Code manuell untersuchen, bis die Hauptursache des Fehlers gefunden ist. Leider gibt es keinen Standardweg, um solche Probleme zu lösen. In jedem Fall ist es notwendig, die Situation zu bewerten und das Problem mit der Methode „anzugreifen“, die unter den gegebenen Bedingungen am besten erscheint.

Im Idealfall möchten Sie natĂŒrlich die mit Multithreading verbundenen Fehler vollstĂ€ndig vermeiden. Und obwohl es hier kein universelles Rezept gibt, kann ich dennoch einige praktische Empfehlungen geben.

Vermeiden Sie die Verwendung globaler ZustÀnde


Denken Sie zunĂ€chst immer an das Problem der „globalen Staaten“. Wenn Sie eine Multithread-Anwendung entwickeln, mĂŒssen Sie absolut alle global verĂ€nderbaren Variablen sorgfĂ€ltig ĂŒberwachen und sie nach Möglichkeit vollstĂ€ndig entfernen. Wenn Sie immer noch einen Grund haben, warum die globale Variable geĂ€ndert werden sollte, implementieren Sie die Synchronisierung ordnungsgemĂ€ĂŸ und ĂŒberwachen Sie die Leistung Ihrer Anwendung. Sie sollten sicherstellen, dass sie nicht durch zusĂ€tzliche Wartezeiten verlangsamt wird.

Vermeiden Sie verÀnderbare Objekte


Diese Idee stammt direkt aus den Prinzipien der funktionalen Programmierung und besagt, dass verĂ€nderbare Klassen und verĂ€nderliche ZustĂ€nde vermieden werden sollten, da sie an die Prinzipien der OOP angepasst sind. Kurz gesagt bedeutet dies, dass Sie keine Einstellungsmethoden (Setter) festlegen und private Felder mit dem endgĂŒltigen Modifikator in allen Modellklassen haben sollten. Ihre Werte Ă€ndern sich nur, wenn das Objekt erstellt wird. In diesem Fall können Sie sicher sein, dass mit der Konkurrenz um Ressourcen keine Probleme verbunden sind. Wenn Sie auf die Eigenschaften des Objekts zugreifen, werden immer die richtigen Werte erhalten.

Wichtige Daten protokollieren


Bewerten Sie, wo Probleme in der Anwendung auftreten können, und richten Sie im Voraus eine Protokollierung aller wichtigen Daten ein. Wenn ein Fehler auftritt, freuen Sie sich ĂŒber Informationen darĂŒber, welche Anfragen eingegangen sind, und können die GrĂŒnde fĂŒr die Fehlfunktion Ihrer Anwendung besser verstehen. Beachten Sie jedoch, dass die Protokollierung zusĂ€tzliche Datei-E / A umfasst und nicht missbraucht werden sollte, da dies die Anwendungsleistung beeintrĂ€chtigen kann.

Verwenden Sie vorgefertigte Thread-Implementierungen


Wenn Sie Ihre Threads erzeugen mĂŒssen (z. B. um asynchrone Anforderungen an verschiedene Dienste zu erstellen), verwenden Sie vorgefertigte sichere Thread-Implementierungen, anstatt Ihre eigenen zu erstellen. In den meisten FĂ€llen können Sie die ExecutorService- Abstraktionen und die spektakulĂ€ren funktionalen Abstraktionen CompletableFuture fĂŒr Java 8 verwenden, um Threads zu erstellen. Mit der Spring-Plattform können Sie auch asynchrone Anforderungen mithilfe der DeferredResult- Klasse verarbeiten.

Das Ende des ersten Teils.
Lesen Sie den zweiten Teil.

Source: https://habr.com/ru/post/de464429/


All Articles