Verwenden Sie niemals Strings in Java? [geschlossen]

73

Ich bin auf einen Blog-Eintrag gestoßen, der die Verwendung von Strings in Java für das Fehlen von Semantik in Ihrem Code abschreckt, und habe vorgeschlagen, stattdessen Thin-Wrapper-Klassen zu verwenden. Dies ist das Vorher-Nachher-Beispiel, das der genannte Eintrag zur Veranschaulichung der Angelegenheit enthält:

public void bookTicket(
  String name,
  String firstName,
  String film,
  int count,
  String cinema);

public void bookTicket(
  Name name,
  FirstName firstName,
  Film film,
  Count count,
  Cinema cinema);

In meiner Erfahrung mit dem Lesen von Programmierblogs bin ich zu dem Schluss gekommen, dass 90% Unsinn sind, aber ich frage mich, ob dies ein gültiger Punkt ist. Irgendwie fühlt es sich für mich nicht richtig an, aber ich konnte nicht genau herausfinden, was mit dem Programmierstil nicht stimmt.

DPM
quelle
Relevante Diskussion im Original-Wiki: "Was ist das für ein toString ()?"
Christoffer Hammarström
5
Oh wow, dieser Blog-Beitrag hat mich körperlich krank gemacht
Rob
5
Ich habe mindestens ein Buch gelesen (möglicherweise die erste Ausgabe von Code Complete), in dem empfohlen wurde, alle primitiven Typen so zu tippen, dass sie Namen aus der Problemdomäne haben. Gleiche Idee.
user16764
9
eine Aussage, die mit "nie" beginnt, ist "immer" falsch!
Florents Tselai
1
Dieser Typ ist ein CTO ?!
Hyangelo

Antworten:

88

Die Kapselung schützt Ihr Programm vor Änderungen . Wird sich die Darstellung eines Namens ändern? Wenn nicht, dann verschwenden Sie Ihre Zeit und YAGNI bewirbt sich.

Edit: Ich habe den Blogpost gelesen und er hat eine grundsätzlich gute Idee. Das Problem ist, dass er es viel zu weit skaliert hat. Sowas String orderId ist echt schlimm, weil es vermutlich "!"£$%^&*())ADAFVFkeine Gültigkeit hat orderId. Dies bedeutet, dass es Stringviel mehr mögliche Werte gibt als gültige orderIds. Für etwas wie a namekönnen Sie jedoch unmöglich vorhersagen, was ein gültiger Name sein könnte oder nicht, und any Stringist ein gültiger name.

In erster Linie reduzieren Sie die möglichen Eingaben (korrekt) auf nur gültige. In der zweiten Instanz haben Sie keine Einschränkung möglicher gültiger Eingaben erreicht.

Erneut bearbeiten: Betrachten Sie den Fall einer ungültigen Eingabe. Wenn Sie "Gareth Gobulcoque" als Ihren Namen schreiben, wird es albern aussehen, es wird nicht das Ende der Welt sein. Wenn Sie eine ungültige OrderID eingeben, funktioniert sie wahrscheinlich nicht.

DeadMG
quelle
5
Bei Bestell-IDs würde ich es wahrscheinlich immer noch vorziehen, den Code, der die IDs akzeptiert, zu überprüfen und den String zu belassen. Die Klassen sollen Methoden für den Umgang mit Daten bereitstellen. Wenn eine Klasse nur die Gültigkeit einiger Daten prüft und dann nichts tut, scheint mir das nicht richtig zu sein. OOP ist gut, aber Sie sollten es nicht übertreiben.
Malcolm
13
@ Malcolm: Aber dann haben Sie keine Möglichkeit zu wissen, welche Strings validiert werden, außer sie immer wieder zu validieren.
DeadMG
7
"[...] Sie können unmöglich vorhersagen, was ein gültiger Name sein könnte oder nicht, und jeder String ist ein gültiger Name." Ich schätze, eine Stärke dieser Technik ist, dass NameSie nicht versehentlich einen anderen nicht verwandten String-Wert übergeben können , wenn Ihr Parameter vom Typ ist. Wo Sie ein erwarten Name, Namekompiliert nur ein Wille, und die Zeichenfolge "Ich schulde Ihnen 5 $" kann nicht versehentlich akzeptiert werden. (Bitte beachten Sie, dass dies in keinem Zusammenhang mit einer Namensvalidierung steht!)
Andres F.
6
Durch das Erstellen von Typen, die für eine bestimmte Verwendung spezifisch sind, wird die semantische Vielfalt erhöht und die Sicherheit erhöht. Außerdem hilft das Erstellen von Typen zur Darstellung bestimmter Werte bei der Verwendung eines IoC-Containers für die automatische Verdrahtung Ihrer Klassen erheblich. So lässt sich leicht die richtige Bean ermitteln, die verwendet wird, wenn sie die einzige für eine bestimmte Klasse registrierte Bean ist. Es ist viel mehr Aufwand erforderlich, wenn nur eine von vielen Zeichenfolgen registriert ist.
Dathan
3
@DeadMG Das ist argumentativ und nicht etwas, das wir in Kommentaren auflösen können. Ich würde sagen, es kommt vor, dass die Werte von primitiven Typen falsch platziert werden, und das Härten Ihrer Schnittstellen ist eine Möglichkeit, die Situation zu verbessern.
Andres F.
46

Das ist nur verrückt :)

Makach
quelle
2
Das ist auch meine Meinung, aber die Sache ist, dass ich mich an diesen Vorschlag in "Code Complete" erinnere. Natürlich ist nicht alles in diesem Buch unbestreitbar, aber es bringt mich dazu, zweimal darüber nachzudenken, bevor ich eine Idee ablehne.
DPM
8
Sie haben gerade einige Slogans erwähnt, ohne eine wirkliche Rechtfertigung. Können Sie Ihre Meinung präzisieren? Einige akzeptierte Muster und Sprachfunktionen sehen möglicherweise etwas komplexer aus, bieten jedoch eine wertvolle Gegenleistung (zum Beispiel: statische Eingabe)
Andres F.
12
Der gesunde Menschenverstand ist alles andere als normal.
Kaz Dragon
1
+1, dazu möchte ich hinzufügen, dass, wenn eine Sprache benannte Parameter unterstützt und die IDE gut ist, wie dies bei C # und VS2010 der Fall ist, man den Mangel an Funktionen in der Sprache nicht mit den verrückten Mustern ausgleichen muss . Es gibt keine Notwendigkeit für eine Klasse mit dem Namen X und eine Klasse mit dem Namen Y, wenn man schreiben var p = new Point(x: 10, y:20);kann. Es ist auch nicht so, als ob Cinema sich von einer Zeichenfolge unterscheidet. Ich würde verstehen, wenn man sich mit physikalischen Größen wie Druck, Temperatur, Energie befassen würde, bei denen Einheiten unterschiedlich sind und manche nicht negativ sein können. Der Autor des Blogs muss versuchen, funktionsfähig zu sein.
Job
1
+1 für "Überentwickle nicht!"
Ivan
23

Meistens stimme ich dem Autor zu. Wenn es ein für ein Feld spezifisches Verhalten gibt , z. B. die Validierung einer Bestell-ID, würde ich eine Klasse erstellen, die diesen Typ darstellt. Sein zweiter Punkt ist noch wichtiger: Wenn Sie eine Reihe von Feldern haben, die ein Konzept wie eine Adresse darstellen, dann erstellen Sie eine Klasse für dieses Konzept. Wenn Sie in Java programmieren, zahlen Sie einen hohen Preis für die statische Eingabe. Sie können genauso gut den Wert bekommen, den Sie können.

Kevin Cline
quelle
2
Kann der Downvoter einen Kommentar abgeben?
Kevin Cline
Was ist der hohe Preis, den wir für statisches Tippen zahlen?
Richard Tingle
@RichardTingle: Die Zeit, um den Code neu zu kompilieren und die JVM bei jeder Codeänderung neu zu starten. Die Zeit, die verloren geht, wenn Sie eine quellkompatible, aber binär inkompatible Änderung vornehmen, und die Dinge, die neu kompiliert werden müssen, werden nicht gelöscht, und Sie erhalten "MissingMethodException".
Kevin Cline
Ich habe noch nie ein Problem mit Java - Anwendungen erfahren (was mit einem kontinuierlichen Kompilation ist es in der Regel bereits durch die Zeit zusammengestellt ich laufe treffen) , aber es ist ein fairer Punkt mit Web - WARs , für die Erneutes Bereitstellen von etwas langsamer scheint
Richard Tingle
16

Tu es nicht. Es wird alles überkomplizieren und du wirst es nicht brauchen

... ist die Antwort, die ich vor 2 Jahren hier geschrieben hätte. Jetzt bin ich mir allerdings nicht so sicher; Tatsächlich habe ich in den letzten Monaten begonnen, alten Code in dieses Format zu migrieren, nicht weil ich nichts Besseres zu tun habe, sondern weil ich ihn wirklich brauchte, um neue Funktionen zu implementieren oder vorhandene zu ändern. Ich verstehe die automatischen Abneigungen, die andere hier sehen, aber ich denke, das ist etwas, das ernsthafte Gedanken verdient.


Leistungen

Der größte Vorteil ist die Möglichkeit , den Code zu ändern und zu erweitern . Wenn du benutzt

class Point {
    int x,y;
    // other point operations
}

Anstatt nur ein paar Ganzzahlen zu übergeben - was leider bei vielen Schnittstellen der Fall ist -, wird es viel einfacher, später eine weitere Dimension hinzuzufügen. Oder ändern Sie den Typ in double. Wenn Sie später List<Author> authorsoder List<Person> authorsstattdessen verwenden, ist List<String> authorses viel einfacher, dem, was ein Autor darstellt, weitere Informationen hinzuzufügen. Wenn ich es so aufschreibe, fühle ich mich so, als würde ich das Offensichtliche ausdrücken, aber in der Praxis habe ich mich schuldig gemacht, Strings so oft zu verwenden, besonders in Fällen, in denen es am Anfang nicht offensichtlich war, dann würde ich mehr brauchen als ein Faden.

Ich versuche gerade, eine String-Liste umzugestalten, die in meinem Code verflochten ist, da ich dort weitere Informationen benötige, und ich fühle den Schmerz: \

Darüber hinaus stimme ich dem Autor des Blogs zu, dass es mehr semantische Informationen enthält , die dem Leser das Verständnis erleichtern. Während Parameter häufig mit aussagekräftigen Namen versehen werden und eine eigene Dokumentationszeile erhalten, ist dies bei Feldern oder Einheimischen häufig nicht der Fall.

Der letzte Vorteil ist die Sicherheit geben , aus offensichtlichen Gründen, aber es ist eine kleine Sache hier in meinen Augen.

Nachteile

Das Schreiben dauert länger . Das Schreiben einer kleinen Klasse ist schnell und einfach, aber nicht mühelos, insbesondere wenn Sie viele dieser Klassen benötigen. Wenn Sie alle 3 Minuten anhalten, um eine neue Wrapper-Klasse zu schreiben, kann dies auch Ihre Konzentration beeinträchtigen. Ich würde jedoch gerne glauben, dass dieser Zustand der Anstrengung normalerweise erst in der ersten Phase des Schreibens von Code auftritt. Normalerweise kann ich schnell eine ziemlich gute Vorstellung davon bekommen, welche Einheiten involviert sein müssen.

Es können viele redundante Setter (oder Konstruktionen) und Getter beteiligt sein . Der Blog-Autor gibt das wirklich hässliche Beispiel von new Point(x(10), y(10))anstatt new Point(10, 10), und ich möchte hinzufügen, dass eine Verwendung auch Dinge wie Math.max(p.x.get(), p.y.get())statt beinhalten könnte Math.max(p.x, p.y). Und langer Code wird oft zu Recht als schwieriger zu lesen angesehen. Um ehrlich zu sein , habe ich das Gefühl, dass viel Code Objekte bewegt und nur mit ausgewählten Methoden erstellt wird, und noch weniger müssen auf die wichtigen Details zugreifen (was ohnehin nicht OOPy ist).

Fraglich

Ich würde sagen, ob dies zur Lesbarkeit von Code beiträgt oder nicht, ist fraglich. Ja, mehr semantische Informationen, aber längerer Code. Ja, es ist einfacher, die Rolle eines jeden Orts zu verstehen, aber es ist schwieriger zu verstehen, was Sie damit machen können, wenn Sie nicht die Dokumentation lesen.


Wie bei den meisten anderen Programmierschulen ist es meines Erachtens ungesund, diese auf die Spitze zu treiben. Ich kann mir nicht vorstellen, dass ich die x- und y-Koordinate je nach Typ trenne. Ich denke nicht, dass Countes notwendig ist, wenn eine intausreicht. Ich mag die unsigned intVerwendung in C nicht - obwohl es theoretisch gut ist, gibt es Ihnen einfach nicht genug Informationen und es verbietet Ihnen, Ihren Code später zu erweitern, um dieses magische -1 zu unterstützen. Manchmal braucht man Einfachheit.

Ich denke, dieser Blog-Beitrag ist etwas extrem. Aber insgesamt habe ich aus schmerzhaften Erfahrungen gelernt, dass die Grundidee dahinter aus den richtigen Dingen besteht.

Ich habe eine tiefe Abneigung gegen überentwickelten Code. Das tue ich wirklich. Aber richtig eingesetzt, finde ich diese Überentwicklung nicht.

Eiche
quelle
5

Obwohl es eine Art Overkill ist, denke ich oft, dass die meisten Dinge, die ich gesehen habe, stattdessen unterentwickelt sind.

Es ist nicht nur "Sicherheit", eines der wirklich schönen Dinge an Java ist, dass es Ihnen sehr dabei hilft, sich genau zu merken / herauszufinden, was ein bestimmter Aufruf einer Bibliotheksmethode benötigt / erwartet.

Die mit Abstand schlechteste Java-Bibliothek, mit der ich gearbeitet habe, wurde von jemandem geschrieben, der Smalltalk sehr mochte und die GUI-Bibliothek so modellierte, dass sie eher Smalltalk ähnelt. Das Problem bestand darin, dass für jede Methode dasselbe Basisobjekt verwendet wurde. Sie konnten jedoch nicht wirklich alles NUTZEN, auf das das Basisobjekt umgewandelt werden konnte, sodass Sie immer wieder erraten mussten, was in Methoden übergegangen werden sollte, und nicht wussten, ob Sie bis zur Laufzeit einen Fehler hatten (etwas, mit dem ich mich jedes Mal befassen musste, wenn ich arbeitete in C).

Ein weiteres Problem - Wenn Sie Zeichenfolgen, Ints, Sammlungen und Arrays ohne Objekte weitergeben, haben Sie nur Datenbälle ohne Bedeutung. Dies erscheint natürlich, wenn Sie in Bezug auf Bibliotheken denken, dass "einige Apps" verwendet werden, aber beim Entwerfen einer gesamten App ist es viel hilfreicher, allen Ihren Daten an der Stelle, an der die Daten definiert sind, eine Bedeutung (Code) zuzuweisen und nur zu denken Begriffe, wie diese übergeordneten Objekte interagieren. Wenn Sie Primitive anstelle von Objekten weitergeben, ändern Sie per Definition Daten an einem anderen Ort als dem, an dem sie definiert sind. (Aus diesem Grund mag ich Setter und Getters nicht wirklich. Sie arbeiten also mit demselben Konzept auf Daten, die nicht Ihnen gehören).

Wenn Sie separate Objekte für alles definieren, haben Sie immer eine großartige Möglichkeit, alles zu überprüfen. Wenn Sie beispielsweise ein Objekt für die Postleitzahl erstellen und später feststellen, dass Sie sicherstellen müssen, dass die Postleitzahl immer die vierstellige Erweiterung enthält, die Sie haben perfekter Ort, um es auszudrücken.

Das ist eigentlich keine schlechte Idee. Wenn ich darüber nachdenke, bin ich mir nicht einmal sicher, ob ich sagen würde, dass es überhaupt überarbeitet wurde. Es ist in fast jeder Hinsicht einfacher, damit zu arbeiten. Die einzige Ausnahme ist die Verbreitung winziger Klassen, aber Java-Klassen sind so leicht und einfach zu schreiben, dass dies kaum Kosten verursacht (sie können sogar generiert werden).

Es würde mich sehr interessieren, ein gut geschriebenes Java-Projekt zu sehen, für das tatsächlich zu viele Klassen definiert wurden (wo dies das Programmieren erschwert hat). Ich fange an zu glauben, dass es nicht möglich ist, zu viele Klassen zu haben.

Bill K
quelle
3

Ich denke, Sie müssen dieses Konzept von einem anderen Ausgangspunkt aus betrachten. Werfen Sie einen Blick aus der Perspektive eines Datenbankdesigners: Die im ersten Beispiel übergebenen Typen definieren Ihre Parameter nicht auf einzigartige Weise, geschweige denn auf nützliche Weise.

public void bookTicket(
  String name,
  String firstName,
  String film,
  int count,
  String cinema);

Es werden zwei Parameter benötigt, um den tatsächlichen Benutzer anzugeben, der Tickets bucht. Möglicherweise haben Sie zwei verschiedene Filme mit identischen Namen (z. B. Remakes). Möglicherweise haben Sie denselben Film mit unterschiedlichen Namen (z. B. Übersetzungen). Eine bestimmte Kette von Kinos hat möglicherweise unterschiedliche Zweigstellen. Wie gehen Sie also konsequent damit um (z. B. verwenden Sie $chain ($city)oder $chain in $cityoder sogar etwas anderes und wie stellen Sie sicher, dass dies der Fall ist ?) Das Schlimmste ist, dass Sie Ihren Benutzer mithilfe von zwei Parametern angeben. Die Tatsache, dass sowohl ein Vor- als auch ein Nachname angegeben werden, garantiert keinen gültigen Kunden (und Sie können zwei nicht unterscheiden John Doe).

Die Antwort darauf ist, Typen zu deklarieren, aber dies werden selten dünne Wrapper sein, wie ich oben zeige. Höchstwahrscheinlich fungieren sie als Datenspeicher oder sind mit einer Datenbank gekoppelt. Ein CinemaObjekt hat also wahrscheinlich einen Namen, einen Ort, ... und auf diese Weise werden solche Mehrdeutigkeiten beseitigt. Wenn sie dünne Hüllen sind, sind sie zufällig.

Also, IMHO der Blog-Post sagt nur "Stellen Sie sicher, dass Sie die richtigen Typen übergeben", hat sein Autor gerade die übermäßig eingeschränkte Wahl getroffen, um insbesondere bei grundlegenden Datentypen auszuwählen (was die falsche Nachricht ist).

Die vorgeschlagene Alternative ist besser:

public void bookTicket(
  Name name,
  FirstName firstName,
  Film film,
  Count count,
  Cinema cinema);

Andererseits denke ich, dass der Blog-Beitrag zu weit geht, wenn man alles einpackt. Countist zu allgemein, ich könnte Äpfel oder Orangen damit zählen, sie hinzufügen und noch eine Situation in meinen Händen haben, in der das Typensystem mir erlaubt, unsinnige Operationen durchzuführen. Sie können natürlich die gleiche Logik wie im Blog anwenden und Typen CountOfOrangesusw. definieren , aber das ist auch einfach albern.

Für das, was es wert ist, würde ich tatsächlich so etwas schreiben

public Ticket bookTicket(
  Person patron,
  Film film,
  int numberOfTickets,
  Cinema cinema);

Lange Rede kurzer Sinn: Sie sollten keine unsinnigen Variablen übergeben. Das einzige Mal, wenn Sie tatsächlich ein Objekt mit einem Wert angeben, der das tatsächliche Objekt nicht bestimmt, ist das Ausführen einer Abfrage (z. B. public Collection<Film> findFilmsWithTitle(String title)) oder das Erstellen eines Proof-of-Concept. Halten Sie Ihr Typensystem sauber, verwenden Sie also keinen zu allgemeinen (z. B. einen durch a dargestellten Film String) oder zu restriktiven / spezifischen / erfundenen (z . B. Countstatt int) Typ . Verwenden Sie einen Typ, der Ihr Objekt eindeutig definiert, wann immer dies möglich und machbar ist.

edit : eine noch kürzere zusammenfassung. Für kleine Anwendungen (zB Proof of Concepts): Warum kompliziertes Design? Einfach Stringoder benutzen intund loslegen.

Für große Anwendungen: Ist es wirklich so wahrscheinlich, dass Sie viele Klassen haben, die aus einem einzigen Feld mit einem Basisdatentyp bestehen? Wenn Sie wenig solche Klassen haben, haben Sie nur "normale" Objekte, nichts Besonderes ist dort los.

Ich bin der Meinung, dass die Idee, Zeichenfolgen zu kapseln, nur ein unvollständiges Design ist: für kleine Anwendungen zu kompliziert, für große Anwendungen nicht vollständig genug.

Egon
quelle
Ich verstehe Ihren Standpunkt, aber ich würde argumentieren, dass Sie mehr annehmen, als der Autor angibt. Soweit wir wissen, hat sein Model nur einen String für einen Benutzer, einen String für einen Film und so weiter. Diese hypothetische Zusatzfunktionalität ist wirklich der Kern des Problems. Entweder war er sehr "abwesend", um dies zu unterlassen, oder er ist der Meinung, wir sollten mehr semantische Kraft bereitstellen, nur weil. Soweit wir wissen, war es das Letzte, an das er gedacht hatte.
DPM
@Jubbat: in der Tat nehme ich mehr an, als der Autor angibt. Mein Punkt ist jedoch, dass entweder Sie eine einfache Anwendung haben, in welchem ​​Fall beide Wege zu kompliziert sind. Für diesen Maßstab ist die Wartbarkeit kein Problem, und die semantische Unterscheidung beeinträchtigt Ihre Codierungsgeschwindigkeit. Wenn Ihre Anwendung andererseits groß ist, lohnt es sich, Ihre Typen richtig zu definieren (es ist jedoch unwahrscheinlich, dass es sich um einfache Wrapper handelt). IMHO, seine Beispiele überzeugen einfach nicht oder weisen größere Designfehler auf, die über den Punkt hinausgehen, den er anstrebt.
Egon
2

Für mich ist dies dasselbe wie die Verwendung von Regionen in C #. Wenn Sie der Meinung sind, dass dies erforderlich ist, um Ihren Code lesbar zu machen, haben Sie im Allgemeinen größere Probleme, mit denen Sie Ihre Zeit verbringen sollten.

Tom Squires
quelle
2
+1 für den Hinweis auf die Behandlung von Symptomen und nicht auf die Ursache.
Job
2

Ich würde sagen, es ist eine wirklich gute Idee in einer Sprache mit einem stark typisierten typedef-ähnlichen Merkmal.

In Java gibt es das nicht. Wenn Sie also eine ganz neue Klasse für diese Dinge erstellen, überwiegen wahrscheinlich die Kosten. Sie können auch 80% des Nutzens erzielen, indem Sie vorsichtig mit der Benennung von Variablen / Parametern umgehen.

jk.
quelle
0

Es wäre gut, wenn der WENN-String (Ende Ganzzahl und ... nur String) nicht endgültig wäre. Diese Klassen könnten also ein (eingeschränkter) String mit Bedeutung sein und dennoch an ein unabhängiges Objekt gesendet werden, das weiß, wie man damit umgeht der Basistyp (ohne sich hin und her zu unterhalten).

Und "goodnes" davon nehmen zu, wenn es z. Einschränkungen für alle Namen.

Beim Erstellen einer App (nicht einer Bibliothek) ist es jedoch immer möglich, sie umzugestalten. Also fange ich lieber ohne an.

user470365
quelle
0

Ein Beispiel: In unserem derzeit entwickelten System gibt es viele verschiedene Entitäten, die durch mehrere Arten von Bezeichnern (aufgrund der Verwendung externer Systeme) identifiziert werden können, manchmal sogar dieselbe Art von Entität. Alle Bezeichner sind Strings. Wenn also jemand verwechselt, welche Art von ID als Parameter übergeben werden soll, wird kein Fehler bei der Kompilierung angezeigt, aber das Programm wird zur Laufzeit explodieren. Das passiert ziemlich oft. Daher muss ich sagen, dass der Hauptzweck dieses Prinzips nicht darin besteht, uns vor Veränderungen zu schützen (obwohl dies auch dazu dient), sondern uns vor Fehlern zu schützen. Und wenn jemand eine API entwirft, muss diese die Konzepte der Domäne widerspiegeln. Daher ist es konzeptionell nützlich, domänenspezifische Klassen zu definieren - alles hängt davon ab, ob die Entwickler die richtige Reihenfolge haben.

thSoft
quelle
@downvoter: kannst du bitte eine erklärung geben?
ThSoft