JavaRush /Java-Blog /Random-DE /Kaffeepause Nr. 85. Drei Java-Lektionen, die ich auf die ...

Kaffeepause Nr. 85. Drei Java-Lektionen, die ich auf die harte Tour gelernt habe. So verwenden Sie SOLID-Prinzipien im Code

Veröffentlicht in der Gruppe Random-DE

Drei Java-Lektionen, die ich auf die harte Tour gelernt habe

Quelle: Mittel Java zu lernen ist schwierig. Ich habe aus meinen Fehlern gelernt. Jetzt kannst auch Du aus meinen Fehlern und bitteren Erfahrungen lernen, was Du nicht unbedingt machen musst. Kaffeepause Nr. 85.  Drei Java-Lektionen, die ich auf die harte Tour gelernt habe.  So verwenden Sie SOLID-Prinzipien im Code – 1

1. Lambdas können Probleme verursachen.

Lambdas überschreiten oft 4 Codezeilen und sind größer als erwartet. Dies belastet das Arbeitsgedächtnis. Müssen Sie eine Variable von Lambda ändern? Das kannst du nicht machen. Warum? Wenn das Lambda auf Call-Site-Variablen zugreifen kann, können Threading-Probleme auftreten. Daher können Sie Variablen von Lambda nicht ändern. Aber Happy Path in Lambda funktioniert gut. Nach einem Laufzeitfehler erhalten Sie diese Antwort:
at [CLASS].lambda$null$2([CLASS].java:85)
at [CLASS]$$Lambda$64/730559617.accept(Unknown Source)
Es ist schwierig, dem Lambda-Stack-Trace zu folgen. Die Namen sind verwirrend und schwer aufzuspüren und zu debuggen. Mehr Lambdas = mehr Stack-Traces. Was ist der beste Weg, Lambdas zu debuggen? Verwenden Sie Zwischenergebnisse.
map(elem -> {
 int result = elem.getResult();
 return result;
});
Eine weitere gute Möglichkeit ist die Verwendung fortgeschrittener IntelliJ-Debugging-Techniken. Wählen Sie mit TAB den Code aus, den Sie debuggen möchten, und kombinieren Sie ihn mit den Zwischenergebnissen. „Wenn wir an einer Zeile anhalten, die ein Lambda enthält, und F7 (einsteigen) drücken, hebt IntelliJ das Fragment hervor, das debuggt werden muss. Wir können den Block mit der Tabulatortaste auf Debug umschalten, und wenn wir uns dazu entschieden haben, drücken wir erneut F7.“ Wie greife ich über Lambda auf Call-Site-Variablen zu? Sie können nur auf finale oder tatsächlich finale Variablen zugreifen. Sie müssen einen Wrapper um die Anrufortvariablen erstellen. Entweder mit AtomicType oder mit Ihrem eigenen Typ. Sie können den erstellten Variablen-Wrapper mit Lambda ändern. Wie löst man Stack-Trace-Probleme? Verwenden Sie benannte Funktionen. Auf diese Weise können Sie schnell verantwortlichen Code finden, die Logik überprüfen und Probleme lösen. Verwenden Sie benannte Funktionen, um den kryptischen Stack-Trace zu entfernen. Wird das gleiche Lambda wiederholt? Platzieren Sie es in einer benannten Funktion. Sie haben einen einzigen Bezugspunkt. Jedes Lambda erhält eine generierte Funktion, was es schwierig macht, den Überblick zu behalten.
lambda$yourNamedFunction
lambda$0
Benannte Funktionen lösen ein anderes Problem. Große Lambdas. Benannte Funktionen zerlegen große Lambdas, erstellen kleinere Codeteile und erstellen steckbare Funktionen.
.map(this::namedFunc1).filter(this::namedFilter1).map(this::namedFunc2)

2. Probleme mit Listen

Sie müssen mit Listen ( Listen ) arbeiten. Sie benötigen eine HashMap für die Daten. Für Rollen benötigen Sie eine TreeMap . Die Liste geht weiter. Und an der Arbeit mit Sammlungen führt kein Weg vorbei. Wie erstelle ich eine Liste? Was für eine Liste benötigen Sie? Sollte es unveränderlich oder veränderlich sein? Alle diese Antworten wirken sich auf die Zukunft Ihres Codes aus. Wählen Sie im Voraus die richtige Liste aus, damit Sie es später nicht bereuen. Arrays::asList erstellt eine „End-to-End“-Liste. Was können Sie mit dieser Liste nicht machen? Sie können die Größe nicht ändern. Er ist unveränderlich. Was kann man hier machen? Geben Sie Elemente, Sortierungen oder andere Vorgänge an, die keinen Einfluss auf die Größe haben. Verwenden Sie Arrays::asList sorgfältig, da seine Größe unveränderlich ist, sein Inhalt jedoch nicht. new ArrayList() erstellt eine neue „veränderbare“ Liste. Welche Operationen unterstützt die erstellte Liste? Das ist es, und das ist ein Grund zur Vorsicht. Erstellen Sie mit new ArrayList() veränderliche Listen aus unveränderlichen Listen . List::of erstellt eine „unveränderliche“ Sammlung. Seine Größe und sein Inhalt bleiben unter bestimmten Bedingungen unverändert. Wenn es sich bei dem Inhalt um primitive Daten wie int handelt , ist die Liste unveränderlich. Schauen Sie sich das folgende Beispiel an.
@Test
public void testListOfBuilders() {
  System.out.println("### TESTING listOF with mutable content ###");

  StringBuilder one = new StringBuilder();
  one.append("a");

  StringBuilder two = new StringBuilder();
  two.append("a");

  List<StringBuilder> asList = List.of(one, two);

  asList.get(0).append("123");

  System.out.println(asList.get(0).toString());
}
### TESTING listOF mit veränderbarem Inhalt ### a123
Sie müssen unveränderliche Objekte erstellen und sie in List::of einfügen . List::of bietet jedoch keine Garantie für Unveränderlichkeit. List::of bietet Unveränderlichkeit, Zuverlässigkeit und Lesbarkeit. Wissen Sie, wann Sie veränderliche und wann unveränderliche Strukturen verwenden sollten. Die Liste der Argumente, die sich nicht ändern sollten, sollte in der unveränderlichen Liste enthalten sein. Eine veränderbare Liste kann eine veränderbare Liste sein. Verstehen Sie, welche Sammlung Sie benötigen, um zuverlässigen Code zu erstellen.

3. Anmerkungen verlangsamen Sie

Benutzen Sie Anmerkungen? Verstehst du sie? Wissen Sie, was sie tun? Wenn Sie denken, dass die protokollierte Annotation für jede Methode geeignet ist, dann liegen Sie falsch. Ich habe Logged verwendet , um Methodenargumente zu protokollieren. Zu meiner Überraschung hat es nicht funktioniert.
@Transaction
@Method("GET")
@PathElement("time")
@PathElement("date")
@Autowired
@Secure("ROLE_ADMIN")
public void manage(@Qualifier('time')int time) {
...
}
Was stimmt mit diesem Code nicht? Hier gibt es eine Menge Konfigurationsüberblick. Das wird Ihnen oft passieren. Die Konfiguration wird mit regulärem Code gemischt. An sich nicht schlecht, aber es fällt einem ins Auge. Anmerkungen sind erforderlich, um den Boilerplate-Code zu reduzieren. Sie müssen nicht für jeden Endpunkt eine Protokollierungslogik schreiben. Keine Notwendigkeit, Transaktionen einzurichten, verwenden Sie @Transactional . Anmerkungen reduzieren das Muster, indem sie Code extrahieren. Hier gibt es keinen klaren Gewinner, da beide im Spiel sind. Ich verwende immer noch XML und Anmerkungen. Wenn Sie ein sich wiederholendes Muster finden, verschieben Sie die Logik am besten in die Anmerkung. Beispielsweise ist die Protokollierung eine gute Annotationsoption. Moral: Überbeanspruchen Sie Anmerkungen nicht und vergessen Sie XML nicht.

Bonus: Möglicherweise haben Sie Probleme mit Optional

Sie verwenden orElse von Optional . Es kommt zu unerwünschtem Verhalten, wenn Sie die orElse- Konstante nicht übergeben . Sie sollten sich dessen bewusst sein, um künftigen Problemen vorzubeugen. Schauen wir uns ein paar Beispiele an. Wenn getValue(x) einen Wert zurückgibt, wird getValue(y) ausgeführt . Die Methode in orElse wird ausgeführt, wenn getValue(x) einen nicht leeren optionalen Wert zurückgibt .
getValue(x).orElse(getValue(y)
                  .orElseThrow(() -> new NotFoundException("value not present")));

public Optional<Value> getValue(Source s)
{
  System.out.println("Source: " + s.getName());

  // returns value from s source
}

// when getValue(x) is present system will output
Source: x
Source: y
Verwenden Sie orElseGet . Es wird kein Code für nicht leere Optionals ausgeführt .
getValue(x).orElseGet(() -> getValue(y)
                  .orElseThrow(() -> new NotFoundException("value not present")));

public Optional<Value> getValue(Source s)
{
  System.out.println("Source: " + s.getName());

  // returns value from s source
}

// when getValue(x) is present system will output
Source: x

Abschluss

Java zu lernen ist schwierig. Sie können Java nicht in 24 Stunden lernen. Verbessern Sie Ihre Fähigkeiten. Nehmen Sie sich Zeit, lernen Sie und übertreffen Sie Ihren Job.

So verwenden Sie SOLID-Prinzipien im Code

Quelle: Cleanthecode Das Schreiben von zuverlässigem Code erfordert SOLIDE Prinzipien. Irgendwann mussten wir alle das Programmieren lernen. Und seien wir ehrlich. Wir waren DUMM. Und unser Code war derselbe. Gott sei Dank haben wir SOLID. Kaffeepause Nr. 85.  Drei Java-Lektionen, die ich auf die harte Tour gelernt habe.  So verwenden Sie SOLID-Prinzipien im Code - 2

SOLIDE Prinzipien

Wie schreibt man also SOLID-Code? Es ist eigentlich einfach. Sie müssen nur diese fünf Regeln befolgen:
  • Prinzip der Einzelverantwortung
  • Offen-Geschlossen-Prinzip
  • Liskov-Ersetzungsprinzip
  • Prinzip der Schnittstellentrennung
  • Abhängigkeitsinversionsprinzip
Keine Sorge! Diese Prinzipien sind viel einfacher als sie scheinen!

Prinzip der Einzelverantwortung

In seinem Buch beschreibt Robert C. Martin dieses Prinzip wie folgt: „Eine Klasse sollte nur einen Grund für eine Änderung haben.“ Schauen wir uns gemeinsam zwei Beispiele an.

1. Was Sie nicht tun sollten

Wir haben eine Klasse namens User , die es dem Benutzer ermöglicht, die folgenden Dinge zu tun:
  • Einen Account registrieren
  • Anmeldung
  • Erhalten Sie eine Benachrichtigung, wenn Sie sich zum ersten Mal anmelden
Diese Klasse hat nun mehrere Verantwortlichkeiten. Wenn sich der Registrierungsprozess ändert, ändert sich auch die Benutzerklasse . Das Gleiche passiert, wenn sich der Anmeldevorgang oder der Benachrichtigungsprozess ändert. Dies bedeutet, dass die Klasse überlastet ist. Er hat zu viele Verantwortungen. Der einfachste Weg, dies zu beheben, besteht darin, die Verantwortung auf Ihre Klassen zu verlagern, sodass die Benutzerklasse nur für die Kombination von Klassen verantwortlich ist. Wenn sich der Prozess dann ändert, haben Sie eine klare, separate Klasse, die geändert werden muss.

2. Was zu tun ist

Stellen Sie sich eine Klasse vor, die einem neuen Benutzer eine Benachrichtigung anzeigen soll, FirstUseNotification . Es wird aus drei Funktionen bestehen:
  • Prüfen Sie, ob bereits eine Benachrichtigung angezeigt wurde
  • Benachrichtigung anzeigen
  • Benachrichtigung als bereits angezeigt markieren
Gibt es für diese Klasse mehrere Gründe, sich zu ändern? Nein. Diese Klasse hat eine klare Funktion – das Anzeigen einer Benachrichtigung für einen neuen Benutzer. Dies bedeutet, dass die Klasse einen Grund hat, sich zu ändern. Nämlich dann, wenn sich dieses Ziel ändert. Diese Klasse verstößt also nicht gegen das Prinzip der Einzelverantwortung. Natürlich können sich einige Dinge ändern: Die Art und Weise, wie Benachrichtigungen als gelesen markiert werden, oder die Art und Weise, wie die Benachrichtigung angezeigt wird. Da der Zweck des Kurses jedoch klar und grundlegend ist, ist dies in Ordnung.

Offen-Geschlossen-Prinzip

Das Open-Closed-Prinzip wurde von Bertrand Meyer geprägt: „Softwareobjekte (Klassen, Module, Funktionen usw.) sollten für Erweiterungen offen, für Modifikationen jedoch geschlossen sein.“ Dieses Prinzip ist eigentlich sehr einfach. Sie müssen Ihren Code so schreiben, dass neue Funktionen hinzugefügt werden können, ohne den Quellcode zu ändern. Dadurch wird verhindert, dass Sie Klassen ändern müssen, die von Ihrer geänderten Klasse abhängen. Allerdings ist dieses Prinzip deutlich schwieriger umzusetzen. Meyer schlug vor, die Vererbung zu nutzen. Aber es führt zu einer starken Verbindung. Wir werden dies in den Prinzipien der Schnittstellentrennung und den Prinzipien der Abhängigkeitsumkehr diskutieren. Also entwickelte Martin einen besseren Ansatz: die Verwendung von Polymorphismus. Anstelle der herkömmlichen Vererbung verwendet dieser Ansatz abstrakte Basisklassen. Auf diese Weise können Vererbungsspezifikationen wiederverwendet werden, ohne dass eine Implementierung erforderlich ist. Die Schnittstelle kann einmal beschrieben und dann geschlossen werden, um Änderungen vorzunehmen. Neue Funktionen müssen diese Schnittstelle dann implementieren und erweitern.

Liskov-Ersetzungsprinzip

Dieses Prinzip wurde von Barbara Liskov erfunden, einer Turing-Preisträgerin für ihre Beiträge zu Programmiersprachen und Softwaremethodik. In ihrem Artikel definierte sie ihr Prinzip wie folgt: „Objekte in einem Programm sollten durch Instanzen ihrer Untertypen ersetzbar sein, ohne die korrekte Ausführung des Programms zu beeinträchtigen.“ Schauen wir uns dieses Prinzip als Programmierer an. Stellen Sie sich vor, wir haben ein Quadrat. Es könnte sich um ein Rechteck handeln, was logisch klingt, da ein Quadrat eine Sonderform eines Rechtecks ​​ist. Hier kommt das Liskov-Ersetzungsprinzip zum Einsatz. Wo immer Sie in Ihrem Code ein Rechteck erwarten würden, kann auch ein Quadrat erscheinen. Stellen Sie sich nun vor, Ihr Rechteck verfügt über die Methoden SetWidth und SetHeight . Das bedeutet, dass auch das Quadrat diese Methoden benötigt. Leider ergibt das keinen Sinn. Dies bedeutet, dass hier das Liskov-Ersetzungsprinzip verletzt wird.

Prinzip der Schnittstellentrennung

Wie alle Prinzipien ist auch das Prinzip der Schnittstellentrennung viel einfacher als es scheint: „Viele kundenspezifische Schnittstellen sind besser als eine allgemeine Schnittstelle.“ Wie beim Single-Responsibility-Prinzip besteht das Ziel darin, Nebenwirkungen und die Anzahl der erforderlichen Änderungen zu reduzieren. Natürlich schreibt niemand solchen Code absichtlich. Aber es ist leicht zu begegnen. Erinnern Sie sich an das Quadrat aus dem vorherigen Prinzip? Stellen Sie sich nun vor, wir beschließen, unseren Plan umzusetzen: Wir machen aus einem Rechteck ein Quadrat. Jetzt zwingen wir das Quadrat, setWidth und setHeight zu implementieren , was wahrscheinlich nichts bewirkt. Wenn sie das täten, würden wir wahrscheinlich etwas kaputt machen, weil die Breite und Höhe nicht unseren Erwartungen entspräche. Glücklicherweise bedeutet dies für uns, dass wir nicht mehr gegen das Liskov-Substitutionsprinzip verstoßen, da wir jetzt die Verwendung eines Quadrats überall dort zulassen, wo wir ein Rechteck verwenden. Allerdings entsteht dadurch ein neues Problem: Wir verstoßen nun gegen das Prinzip der Schnittstellentrennung. Wir zwingen die abgeleitete Klasse, Funktionen zu implementieren, die sie nicht verwenden möchte.

Abhängigkeitsinversionsprinzip

Das letzte Prinzip ist einfach: High-Level-Module sollten wiederverwendbar sein und nicht durch Änderungen an Low-Level-Modulen beeinträchtigt werden.
  • A. High-Level-Module sollten nicht von Low-Level-Modulen abhängig sein. Beide müssen von Abstraktionen (z. B. Schnittstellen) abhängen.
  • B. Abstraktionen sollten nicht von Details abhängen. Die Details (konkrete Implementierungen) müssen von den Abstraktionen abhängen.
Dies kann durch die Implementierung einer Abstraktion erreicht werden, die High- und Low-Level-Module trennt. Der Name des Prinzips lässt vermuten, dass sich die Richtung der Abhängigkeit ändert, was jedoch nicht der Fall ist. Es trennt die Abhängigkeit nur, indem eine Abstraktion zwischen ihnen eingeführt wird. Als Ergebnis erhalten Sie zwei Abhängigkeiten:
  • High-Level-Modul, abhängig von der Abstraktion
  • Low-Level-Modul, abhängig von derselben Abstraktion
Dies mag schwierig erscheinen, geschieht aber tatsächlich automatisch, wenn Sie das Offen/Geschlossen-Prinzip und das Liskov-Substitutionsprinzip richtig anwenden. Das ist alles! Sie haben jetzt die fünf Grundprinzipien kennengelernt, die SOLID zugrunde liegen. Mit diesen fünf Prinzipien können Sie Ihren Code großartig machen!
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION