Heim > Java > javaLernprogramm > Die faszinierendsten Java-Fehler in 4

Die faszinierendsten Java-Fehler in 4

Mary-Kate Olsen
Freigeben: 2025-01-01 09:19:09
Original
604 Leute haben es durchsucht

Im Jahr 2024 haben wir eine Fülle von Projekten analysiert und unsere Erkenntnisse auf unserem Blog geteilt. Jetzt ist Silvester – es ist Zeit, festliche Geschichten zu erzählen! Wir haben die faszinierendsten Java-Fehler gesammelt, die in Open-Source-Projekten entdeckt wurden, und stellen sie Ihnen jetzt vor!

Top most intriguing Java errors in 4

Vorwort

Wir pflegen seit langem die Tradition, die interessantesten mit PVS-Studio entdeckten Fehler zu veröffentlichen, aber seit 2020 gab es keinen Java-bezogenen Top! Dieses Mal habe ich versucht, die Vintage-Rubrik wiederzubeleben. Ich hoffe, Sie haben eine kuschelige Decke und eine warme Tasse Tee zur Hand, denn ich habe über 10 unterhaltsame Käfer speziell für Sie ausgewählt. So wurden sie eingestuft:

  • meine persönliche Meinung;
  • ein interessanter Hintergrund des Fehlers;
  • Vielfalt, Glaubwürdigkeit und Nicht-Trivialität.

Machen Sie sich bereit für 10 unterhaltsame Geschichten mit ihren eigenen Programmierweisheiten – schließlich steht Silvester bald vor der Tür :)

10. Platz. Zurück in die Zukunft

An zehnter Stelle empfängt uns der erste Codeausschnitt mit offenen Armen.

public Builder setPersonalisation(Date date, ....) {
  ....
  final OutputStreamWriter
    out = new OutputStreamWriter(bout, "UTF-8");
  final DateFormat
    format = new SimpleDateFormat("YYYYMMdd");
  out.write(format.format(date));
    ....
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Ich konnte nicht anders, als es in das Silvester-Top aufzunehmen, weil ein Fehler in diesem Code es uns ermöglicht, schneller zum nächsten Jahr zu reisen :) Ratet mal, woher der Fehler kommt?

Sehen wir uns das Argument an, das an den SimpleDateFormat-Konstruktor übergeben wird. Scheint es gültig zu sein? Wenn wir dort fast ein beliebiges Datum übergeben, beispielsweise das Datum, an dem dieser Artikel geschrieben wurde (12.10.2024), gibt der Code den korrekten Wert zurück: 20241210.

Wenn wir jedoch den 29.12.2024 überschreiten, wird stattdessen 20251229 zurückgegeben und so auf geniale Weise früher ins neue Jahr verschoben. Eine Zeitreise zurück in die Vergangenheit ist übrigens auch machbar.

Dies geschieht, weil das Y-Zeichen im Argument von SimpleDateFormat das Jahr basierend auf der Wochennummer darstellt. Kurz gesagt gilt eine Woche als erste Woche, wenn sie mindestens vier Tage des neuen Jahres umfasst. Wenn unsere Woche also am Sonntag beginnt, können wir drei Tage früher ins neue Jahr starten.

Um das Problem zu beheben, ersetzen Sie einfach ein großes Y durch ein kleines y. Möchten Sie mehr erfahren? Wir haben diesem Thema einen ganzen Beitrag gewidmet.

Hier ist eine PVS-Studio-Warnung für diesen Fehler:

V6122 Verwendung des Musters „Y“ (Woche, Jahr) wurde erkannt: Es war wahrscheinlich beabsichtigt, „y“ (Jahr) zu verwenden. SkeinParameters.java 246

Aufgrund der Besonderheiten der Wochennummer sind Tests nicht der beste Weg, um diesen Fehler zu finden. Warum kommt so ein aktueller Fehler überhaupt erst an letzter Stelle? Der Grund dafür ist, dass die Warnung nicht von der tatsächlichen Version von Bouncy Castle stammt, sondern von unserer Testbasis. Die alten Quellen sind immer noch vorhanden und dieser Fehler wurde schon vor langer Zeit behoben. Das ist so ein Gruß an die Vergangenheit, einfach mal wieder eine Zeitreise :)

9. Platz. „Scheint nicht zu funktionieren“

An neunter Stelle haben wir die Warnung aus der GeoServer-Analyse:

@Test
public void testStore() {
  Properties newProps = dao.toProperties();

  // ....
  Assert.assertEquals(newProps.size(), props.size());
  for (Object key : newProps.keySet()) {
    Object newValue = newProps.get(key);
    Object oldValue = newProps.get(key);              // <=
    Assert.assertEquals(newValue, oldValue);
  }
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Hier ist die PVS-Studio-Warnung:

V6027 Die Variablen „newValue“, „oldValue“ werden durch den Aufruf derselben Funktion initialisiert. Es handelt sich wahrscheinlich um einen Fehler oder um nicht optimierten Code. DataAccessRuleDAOTest.java 110, DataAccessRuleDAOTest.java 111

Was ist an einem solchen Fehler interessant? Lassen Sie mich verraten, was sich hinter den vier Punkten verbirgt:

public Builder setPersonalisation(Date date, ....) {
  ....
  final OutputStreamWriter
    out = new OutputStreamWriter(bout, "UTF-8");
  final DateFormat
    format = new SimpleDateFormat("YYYYMMdd");
  out.write(format.format(date));
    ....
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Es gibt einen Kommentar, dass der Code aus irgendeinem Grund nicht funktioniert. Ehrlich gesagt, ich musste schmunzeln, als ich es zum ersten Mal sah.

Der Kommentar ist allerdings eher zweideutig. Es ist wahrscheinlich, dass der Test absichtlich so geschrieben wurde, um einen Fehler zu verhindern, während der Vergleich nicht funktioniert. Der Code befindet sich jedoch seit über einem Jahrzehnt in diesem Zustand, was einige Fragen aufwirft. Diese Mehrdeutigkeit ist der Grund, warum ich es nicht höher einstufe.

8. Platz. Schuss in den Fuß

Wenn wir Bugs von JBullet nicht als Schusswaffen bezeichnen können, weiß ich nicht, welche wir so nennen können. Hier ist ein Fehler aus dem Artikel:

@Test
public void testStore() {
  Properties newProps = dao.toProperties();

  // ....
  Assert.assertEquals(newProps.size(), props.size());
  for (Object key : newProps.keySet()) {
    Object newValue = newProps.get(key);
    Object oldValue = newProps.get(key);              // <=
    Assert.assertEquals(newValue, oldValue);
  }
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Ich denke, wir brauchen nicht einmal die PVS-Studio-Warnung, um zu erkennen, wo der Fehler liegt. Wie auch immer, nur für den Fall, hier ist es:

V6026 Dieser Wert ist bereits der Variablen „proxy1“ zugewiesen. HashedOverlappingPairCache.java 233

Ja, es ist ein peinlich einfacher Fehler. Diese Einfachheit macht es jedoch noch urkomischer. Dennoch hat es seine eigene Geschichte.

Die JBullet-Bibliothek ist eine Portierung der C/C-Bullet-Bibliothek, und dort gibt es eine ähnliche Funktion:

@Test
public void testStore() {
  Properties newProps = dao.toProperties();

  // properties equality does not seem to work...
  Assert.assertEquals(newProps.size(), props.size());
  for (Object key : newProps.keySet()) {
    Object newValue = newProps.get(key);
    Object oldValue = newProps.get(key);
    Assert.assertEquals(newValue, oldValue);
  }
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Es ist leicht zu erkennen, dass dieser Code korrekt geschrieben ist. Der Git-Schuld nach zu urteilen, ist es ursprünglich korrekt geschrieben. Es stellte sich heraus, dass der Fehler auftrat, als der Code von einer Sprache in eine andere portiert wurde.

Für seine auffällige Schlichtheit gepaart mit einer reichen Geschichte vergebe ich dieser Warnung den achten Platz. Ich hoffe, es hat Ihnen gefallen, dass sich herausstellte, dass der Schuss-in-den-Fuß-Bug mit C zusammenhängt.

7. Platz. Selbst große Mathematiker machen Fehler

Zugegebenermaßen wärmt mir die nächste Warnung aus vielen Gründen das Herz. Hier ist ein Codeausschnitt aus dem GeoGebra-Check:

@Override
public BroadphasePair findPair(BroadphaseProxy proxy0, BroadphaseProxy proxy1) {
  BulletStats.gFindPairs++;
  if (proxy0.getUid() > proxy1.getUid()) {
    BroadphaseProxy tmp = proxy0;
    proxy0 = proxy1;
    proxy1 = proxy0;
  }
  ....
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Versuchen Sie, den Fehler selbst zu finden! Um Sie davon abzuhalten, einen Blick darauf zu werfen, habe ich die Warnung und Erklärung in einem Spoiler versteckt.

Die Antwort
Schauen wir uns zunächst die PVS-Studio-Warnung an:

V6107 Die Konstante 0,7071067811865 wird verwendet. Der resultierende Wert könnte ungenau sein. Erwägen Sie die Verwendung von Math.sqrt(0.5). DrawAngle.java 303

In Wirklichkeit ist 0,7071067811865 keine magische Zahl – es ist einfach das gerundete Ergebnis der Quadratwurzelbildung aus 0,5. Doch wie kritisch ist dieser Präzisionsverlust? GeoGebra ist eine auf Mathematiker zugeschnittene Software, und zusätzliche Präzision scheint dort nicht zu schaden.

Warum gefällt mir dieser Käfer so gut?

Erstens bitte ich die Teilnehmer auf Konferenzen oft, einen ähnlichen Fehler in anderen Codefragmenten zu finden. Es war immer unterhaltsam, ihnen dabei zuzusehen, wie sie den Code sorgfältig analysieren, wenn der Fehler in einer Konstante verborgen ist.

Zweitens ist dies meine erste Diagnoseregel, die für den Java-Analysator implementiert wurde. Deshalb konnte ich es mir nicht verkneifen, es ganz oben zu platzieren – trotz allem Bewusstsein der Voreingenommenheit – ich habe es auf den siebten Platz gesetzt :)

6. Platz. Dieses Muster funktioniert nicht

Die folgende Warnung, die ich dem ersten Artikel basierend auf dem DBeaver-Check entnommen habe, erregt möglicherweise nicht sofort unsere Aufmerksamkeit. Hier ist ein Codefragment:

public Builder setPersonalisation(Date date, ....) {
  ....
  final OutputStreamWriter
    out = new OutputStreamWriter(bout, "UTF-8");
  final DateFormat
    format = new SimpleDateFormat("YYYYMMdd");
  out.write(format.format(date));
    ....
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Das hat der PVS-Studio-Analysator erkannt:

V6082 Unsichere, doppelt überprüfte Verriegelung. Das Feld sollte als flüchtig deklariert werden. TaskImpl.java 59, TaskImpl.java317

Obwohl diese spezielle Warnung nichts Besonderes ist, finde ich sie dennoch sehr faszinierend. Der Punkt ist, dass das angewendete Double-Checked Locking-Muster nicht funktioniert. Was ist der Trick? Das war vor 20 Jahren relevant :)

Wenn Sie mehr über das Thema lesen möchten, empfehle ich Ihnen, den vollständigen Artikel zu lesen. Aber lassen Sie mich zunächst eine kurze Zusammenfassung geben.

Das Double-Checked Locking-Muster wird verwendet, um eine verzögerte Initialisierung in Multithread-Umgebungen zu implementieren. Vor der „Heavyweight“-Prüfung wird die „Lightweight“-Prüfung ohne Synchronisationsblock ausgeführt. Die Ressource wird nur erstellt, wenn beide Prüfungen erfolgreich sind.

Bei diesem Ansatz ist die Objekterstellung jedoch nicht atomar und der Prozessor und Compiler können die Operationsreihenfolge ändern. Daher kann es passieren, dass ein anderer Thread versehentlich ein teilweise erstelltes Objekt empfängt und damit beginnt, damit zu arbeiten, was zu fehlerhaftem Verhalten führen kann. Dieser Fehler kann selten auftreten, daher wird das Debuggen eine große Herausforderung für Entwickler sein.

Hier ist eine Wendung: Dieses Muster funktionierte erst mit JDK 5. Ab JDK 5 wurde das Schlüsselwort volatile eingeführt, um das potenzielle Problem der Neuordnung von Vorgängen dank des passiert-bevor-Prinzips zu lösen. Der Analysator warnt uns, dass wir dieses Schlüsselwort hinzufügen sollten.

Es wäre jedoch besser, dieses Muster trotzdem zu vermeiden. Die Hardware- und JVM-Leistung hat sich seitdem erheblich verbessert und die synchronisierten Vorgänge sind nicht mehr so ​​langsam. Allerdings bleibt die falsche Implementierung des DCL-Musters eine häufige Gefahr mit den oben beschriebenen potenziell schwerwiegenden Folgen. Es bestätigt die Tatsache, dass unser Analysator immer noch solche fahrlässigen Fehler in alten Projekten findet.

5. Platz. Mikrooptimierung

Der fünfte Platz belegt eine weitere DBeaver-Warnung, der wir einen Artikel gewidmet haben. Werfen wir einen Blick darauf:

@Test
public void testStore() {
  Properties newProps = dao.toProperties();

  // ....
  Assert.assertEquals(newProps.size(), props.size());
  for (Object key : newProps.keySet()) {
    Object newValue = newProps.get(key);
    Object oldValue = newProps.get(key);              // <=
    Assert.assertEquals(newValue, oldValue);
  }
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Hier ist eine Erklärung:

V6030 Die Methode rechts vom Operator „&“ wird unabhängig vom Wert des linken Operanden aufgerufen. Vielleicht ist es besser, „&&“ zu verwenden. ExasolTableColumnManager.java 79, DB2TableColumnManager.java 77

Der Entwickler hat das logische && mit dem bitweisen & verwechselt. Sie haben ein unterschiedliches Verhalten: Die Bedingungen im Ausdruck werden nach dem bitweisen UND nicht beendet. Die Kurzschlussauswertung funktioniert nicht für bitweises UND. Auch wenn exasolTableBase != null false zurückgibt, erreicht der Ausführungsthread exasolTableBase.getClass() und führt zu einem NPE.

Okay, es ist nur ein Tippfehler, lass uns weitermachen, ja? DBeaver hat viele solcher Warnungen. Eine Menge. Viele sind relativ harmlos, aber für neugierige Leser habe ich unten ein paar Beispiele hinterlassen:

Verwendung bitweiser Operationen, die nicht zu Fehlern führen
ExasolSecurityPolicy.java:
public Builder setPersonalisation(Date date, ....) {
  ....
  final OutputStreamWriter
    out = new OutputStreamWriter(bout, "UTF-8");
  final DateFormat
    format = new SimpleDateFormat("YYYYMMdd");
  out.write(format.format(date));
    ....
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

ExasolConnectionManager.java:

@Test
public void testStore() {
  Properties newProps = dao.toProperties();

  // ....
  Assert.assertEquals(newProps.size(), props.size());
  for (Object key : newProps.keySet()) {
    Object newValue = newProps.get(key);
    Object oldValue = newProps.get(key);              // <=
    Assert.assertEquals(newValue, oldValue);
  }
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

ExasolDataSource.java:

@Test
public void testStore() {
  Properties newProps = dao.toProperties();

  // properties equality does not seem to work...
  Assert.assertEquals(newProps.size(), props.size());
  for (Object key : newProps.keySet()) {
    Object newValue = newProps.get(key);
    Object oldValue = newProps.get(key);
    Assert.assertEquals(newValue, oldValue);
  }
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Nachdem ich tiefer gegraben habe, ist mein Team davon ausgegangen, dass die Entwickler möglicherweise versucht haben, die Leistung mikrozu optimieren. Für ein vollständiges Bild können Sie sich unseren Artikel ansehen – hier stelle ich eine Zusammenfassung vor.

Der entscheidende Punkt ist, dass bitweise Operationen nicht auf Verzweigungsvorhersagen basieren, was möglicherweise eine schnellere Ausführung im Vergleich zu logischen Operationen ermöglicht.

Zu unserer Überraschung unterstützt ein selbst entwickelter Benchmark diese Behauptung:

Top most intriguing Java errors in 4

Das Diagramm zeigt die benötigte Zeit für jeden Vorgangstyp. Wenn wir ihm vertrauen, scheinen die bitweisen Operationen 40 % schneller zu sein als logische Operationen.

Warum spreche ich dieses Thema an? Um die Kosten potenzieller Mikrooptimierungen hervorzuheben.

Erstens haben Entwickler die Verzweigungsvorhersage aus einem bestimmten Grund erfunden – es wäre zu teuer, sie aufzugeben. Daher arbeitet der Benchmark wahrscheinlich schneller, da die Werte eine Normalverteilung aufweisen, die in realen Fällen wahrscheinlich nicht zu beobachten ist.

Zweitens kann der Verzicht auf den Kurzschlussbewertungsmechanismus zu deutlich höheren Kosten führen. Wenn wir uns das dritte Beispiel aus dem Spoiler oben ansehen, können wir sehen, dass nicht der schnellste Vorgang immer ausgeführt wird, anstatt sofort anzuhalten.

Drittens gewähren wir von Beginn des Kapitels an einen Freibrief für solche Fehler.

Insgesamt finde ich die warnende Geschichte des Mikrooptimierungspreises überzeugend genug, um unsere Top 5 zu eröffnen.

4. Platz. Der Test wird nicht scheitern, wenn er nicht funktioniert

Automatisierte Tests gelten oft als ultimativer Schutz vor verschiedenen Fehlern. Hin und wieder bin ich jedoch versucht zu fragen: „Wer führt die Tests selbst durch?“ Eine weitere Warnung des GeoServer-Checks verdeutlicht dies noch einmal. Hier ist ein Codefragment:

@Override
public BroadphasePair findPair(BroadphaseProxy proxy0, BroadphaseProxy proxy1) {
  BulletStats.gFindPairs++;
  if (proxy0.getUid() > proxy1.getUid()) {
    BroadphaseProxy tmp = proxy0;
    proxy0 = proxy1;
    proxy1 = proxy0;
  }
  ....
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Die PVS-Studio-Warnung:

V6060 Die „e“-Referenz wurde verwendet, bevor sie mit Null verglichen wurde. ResourceAccessManagerWCSTest.java 186, ResourceAccessManagerWCSTest.java 193

Auf den ersten Blick scheint diese Warnung nicht die aufregendste Warnung des Analysators zu sein, da V6060 häufig für redundanten Code ausgegeben wird. Dennoch habe ich versprochen, dass ich die Nominierungen nach ihrer Attraktivität auswählen werde. Dieser Fall ist also weitaus interessanter, als er scheint.

Anfangs mag die Testlogik falsch erscheinen, da die e-Variable vom Catch-Operator erhalten wird und weiterhin unverändert bleibt, sodass sie niemals null sein wird. Wir können einfach eine verirrte Bearbeitung vornehmen und den then-Zweig der if(e == nul)-Bedingung als nicht erreichbar entfernen. Doch das wäre radikal falsch. Hast du den Haken schon herausgefunden?

Der Schlüssel liegt in einer weiteren Variablen im Code, die das Ausnahmeobjekt enthält, es ist ein se. Es hat einen Wert, der sich innerhalb des Schleifenkörpers ändert. Wir können also leicht erraten, dass in der Bedingung die se-Variable anstelle von e vorhanden sein sollte.

Dieser Fehler führt dazu, dass der then-Zweig nie ausgeführt wird, sodass wir nicht wissen, dass es keine Ausnahme gibt. Was noch schlimmer ist, es ist ziemlich schwierig, einen solchen Fehler bei einer Codeüberprüfung zu bemerken, weil die Variablennamen sehr ähnlich sind.

Aus dieser Geschichte lassen sich zwei Weisheiten ableiten:

  1. Benennen Sie Variablen klar, auch in Tests. Andernfalls ist es einfacher, solche Fehler zu machen;
  2. Tests reichen nicht aus, um die Projektqualität zu gewährleisten, da sie auch Fehler enthalten können. Dadurch entstehen Lücken für Fehler in der App.

Für die Vermittlung solch wertvoller Lektionen vergebe ich dieser Warnung den vierten Platz.

3. Platz. Viel Spaß beim Debuggen, Leute

Die ersten drei Gewinner gehören zur Warnung aus dem NetBeans-Check. Frühere Codeausschnitte waren relativ kompakt, schauen wir uns also einen längeren an:

public Builder setPersonalisation(Date date, ....) {
  ....
  final OutputStreamWriter
    out = new OutputStreamWriter(bout, "UTF-8");
  final DateFormat
    format = new SimpleDateFormat("YYYYMMdd");
  out.write(format.format(date));
    ....
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Versuchen Sie zum letzten Mal, den Fehler selbst zu finden – ich werde warten...

Top most intriguing Java errors in 4

Suchen?

Schön! Diejenigen, die den Fehler nur im Ausdruck iDesc.neighbor != null || gefunden haben iDesc.index == iDesc.index, es tut mir leid, das sagen zu müssen – du hast verloren :)

Sicher, es gibt ein Problem, aber es ist bei weitem nicht interessant genug für das oberste. Ja, hier liegen zwei Fehler vor, ich habe dich ein wenig getäuscht. Aber was wären Feiertage ohne ein bisschen Unfug? :)

Der Analysator hat hier einen Fehler im i^i-Ausdruck erkannt und die folgende Warnung ausgegeben:

V6001 Links und rechts vom Operator „^“ gibt es identische Unterausdrücke „i“. LayoutFeeder.java 3897

Die XOR-Operation macht keinen Sinn, da das Exklusiv-ODER mit zwei identischen Werten immer Null ist. Zur schnellen Auffrischung finden Sie hier die Wahrheitstabelle für XOR:

a b a^b
0 0 0
0 1 1
1 0 1
1 1 0

Mit anderen Worten, die Operation ist nur dann wahr, wenn die Operanden unterschiedlich sind. Wir werden alle Bits gleich haben, da der Wert derselbe ist.

Warum hat mir dieser Käfer so gut gefallen? Es gibt die i^1-Operation, die fast identisch mit i^i aussieht. Daher ist es unglaublich leicht, diesen Fehler bei einer Codeüberprüfung zu übersehen, da wir oben bereits das richtige i^1 gesehen haben.

Ich weiß nicht, wie es dir geht, aber es erinnert mich an das berühmte:

public Builder setPersonalisation(Date date, ....) {
  ....
  final OutputStreamWriter
    out = new OutputStreamWriter(bout, "UTF-8");
  final DateFormat
    format = new SimpleDateFormat("YYYYMMdd");
  out.write(format.format(date));
    ....
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Ansonsten ist es schwer zu erklären, wie es in den Code gelangt ist – es sei denn, wir verwerfen die langweilige Version mit einem einfachen Tippfehler. Wenn Sie diesen Fehler entdeckt haben, klopfen Sie sich selbst auf die Schulter – oder teilen Sie Ihre detektivischen Fähigkeiten in den Kommentaren :)

2. Platz. Als Muster versagten

Ich habe die Fehler aus dem ersten und dritten DBeaver-Artikel bereits gezeigt und den zweiten übersprungen. Ich stehe korrigiert da – das Folgende stammt nur aus dem zweiten Artikel.

PVS-Studio-Analysator mag es nicht, dass isBinaryContents vom Konstruktor der TextWithOpen-Klasse aufgerufen wird, der in den Unterklassen überschrieben wird:

@Test
public void testStore() {
  Properties newProps = dao.toProperties();

  // ....
  Assert.assertEquals(newProps.size(), props.size());
  for (Object key : newProps.keySet()) {
    Object newValue = newProps.get(key);
    Object oldValue = newProps.get(key);              // <=
    Assert.assertEquals(newValue, oldValue);
  }
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Na und? Es wird überschrieben – aber keine große Sache. Es scheint ein Codegeruch zu sein, nichts Kritisches. Zumindest habe ich das früher gedacht. Ich habe meinem Kampf mit diesem Fehler einen Artikel gewidmet.

TextWithOpen hat viele Unterklassen und eine davon ist TextWithOpenFile. Dort wird die Methode tatsächlich überschrieben und statt false gibt sie ein Feld zurück, das die Superklasse nicht hat:

@Test
public void testStore() {
  Properties newProps = dao.toProperties();

  // properties equality does not seem to work...
  Assert.assertEquals(newProps.size(), props.size());
  for (Object key : newProps.keySet()) {
    Object newValue = newProps.get(key);
    Object oldValue = newProps.get(key);
    Assert.assertEquals(newValue, oldValue);
  }
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Schon misstrauisch? Wie sieht der Konstruktor dieser Klasse aus?

@Override
public BroadphasePair findPair(BroadphaseProxy proxy0, BroadphaseProxy proxy1) {
  BulletStats.gFindPairs++;
  if (proxy0.getUid() > proxy1.getUid()) {
    BroadphaseProxy tmp = proxy0;
    proxy0 = proxy1;
    proxy1 = proxy0;
  }
  ....
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Merken Sie das? Das Binärfeld wird initialisiert, nachdem der Superklassenkonstruktor aufgerufen wurde. Es gibt jedoch einen Aufruf der Methode isBinaryContents, die auf das Unterklassenfeld verweist!

Top most intriguing Java errors in 4

Hier ist die PVS-Studio-Warnung:

V6052 Der Aufruf der überschriebenen Methode „isBinaryContents“ im Konstruktor der übergeordneten Klasse „TextWithOpen“ kann zur Verwendung nicht initialisierter Daten führen. Feld prüfen: binär. TextWithOpenFile.java(77), TextWithOpen.java 59

Es ist ein ziemlich unterhaltsames Bild. Auf den ersten Blick schien der Entwickler die Best Practices zu befolgen: Vermeiden Sie den Code-Spaghetti, der unmöglich zu warten ist, und versuchen Sie, kanonisches OOP durch ein Vorlagenmethodenmuster zu implementieren. Aber selbst bei der Implementierung eines so einfachen Musters können wir einen Fehler machen, und genau das ist passiert. Meiner Meinung nach ist solch eine (trügerische) Schönheit in der Einfachheit ein solider zweiter Platz.

1. Platz. Ein Fehler glich den anderen aus

Freut euch! Der erste Platz auf der Bühne! Der Wettbewerb war hart, aber die Wahl musste getroffen werden. Nach langem Überlegen habe ich mich entschieden, die Warnung aus dem NetBeans-Check zu übernehmen. Lassen Sie mich den letzten Codeausschnitt vorstellen:

public Builder setPersonalisation(Date date, ....) {
  ....
  final OutputStreamWriter
    out = new OutputStreamWriter(bout, "UTF-8");
  final DateFormat
    format = new SimpleDateFormat("YYYYMMdd");
  out.write(format.format(date));
    ....
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Ich bin zuversichtlich, dass es unmöglich ist, einen solchen Fehler auf den ersten Blick zu erkennen – es sei denn natürlich, Sie haben diesen Fehler selbst gemacht. Ich werde Sie nicht warten lassen – hier ist die PVS-Studio-Warnung:

V6009 Die Pufferkapazität wird mithilfe eines char-Werts auf „47“ festgelegt. Höchstwahrscheinlich sollte das Symbol „/“ im Puffer platziert werden. IgnoreUnignoreCommand.java 107

Tatsächlich ist der Fehler lächerlich elementar: Der StringBuilder-Konstruktor hat keine Überladung, die Zeichen akzeptiert. Welcher Konstruktor heißt dann? Der Entwickler dachte offenbar, dass ein überlastungsfähiger String aufgerufen würde und der Anfangswert von StringBuilder dann dieser Schrägstrich wäre.

Es findet jedoch eine implizite Typkonvertierung statt und der Typkonstruktor, der int akzeptiert, wird aufgerufen. In unserem Fall stellt es die Anfangsgröße von StringBuilder dar. Die Übergabe von char als Argument hat keine funktionalen Auswirkungen, da es nicht in die endgültige Zeichenfolge einbezogen wird. Wenn die ursprüngliche Größe überschritten wird, vergrößert sie sich einfach von selbst, ohne Ausnahmen oder andere Nebenwirkungen zu verursachen.

Aber Moment, ich habe zwei Fehler erwähnt, nicht wahr? Wo ist die zweite und wie hängen sie zusammen? Um dies zu erkennen, müssen wir den Methodenkörper lesen und erkennen, was dieser Code bewirkt.

Es generiert einen absoluten Pfad zu einer Datei oder einem Verzeichnis. Basierend auf dem Code sollte der resultierende Pfad etwa so aussehen:

  • für eine Datei: /folder1/file
  • für ein Verzeichnis: /folder1/folder/.

Der Code sieht ganz korrekt aus. Das ist das Problem. Der Code funktioniert wirklich so, wie er sollte :) Aber wenn wir den Fehler beheben, indem wir ein Zeichen durch eine Zeichenfolge ersetzen, erhalten wir Folgendes anstelle des richtigen Ergebnisses:

  • /folder1/file/;
  • /folder1/folder//

Mit anderen Worten, wir erhalten einen zusätzlichen Schrägstrich am Ende der Zeichenfolge. Es befindet sich am Ende, da der obige Code jedes Mal neuen Text am Anfang der Zeile hinzufügt.

Daher besteht der zweite Fehler darin, dass dieser Schrägstrich überhaupt als Argument an den Konstruktor übergeben wurde. Allerdings würde ich einen solchen Fehler nicht unterschätzen, denn wenn jemand beschließt, ein Zeichen ohne Prüfung durch eine Zeichenfolge zu ersetzen, kann es zu Problemen kommen.

So belegt Code, der korrekt funktioniert, den ersten Platz in der Fehlerliste. Neujahrswunder, was haben Sie erwartet? :)

Abschluss

Ich hoffe, es hat Ihnen Spaß gemacht, meine Fehlergeschichten zu lesen. Wenn Ihnen eine bestimmte Geschichte aufgefallen ist – oder wenn Sie Vorschläge zur Anpassung des Rankings haben – teilen Sie uns Ihre Gedanken gerne in den Kommentaren mit. Ich werde sie für das nächste Mal im Hinterkopf behalten :)

Wenn Sie sich für andere Sprachen interessieren, lade ich Sie ein, sich hier die größten C#-Fehler für 2024 anzusehen – bleiben Sie gespannt auf neue Spitzen!

Alle diese Fehler wurden mit dem PVS-Studio-Analysetool erkannt und die neueste Version (7.34) wurde gerade veröffentlicht! Sie können es über diesen Link ausprobieren.

Um über neue Artikel zur Codequalität auf dem Laufenden zu bleiben, laden wir Sie ein, sich anzumelden:

  • PVS-Studio X (Twitter);
  • unser monatlicher Artikelüberblick;

Frohes Neues Jahr!

Das obige ist der detaillierte Inhalt vonDie faszinierendsten Java-Fehler in 4. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Neueste Artikel des Autors
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage