Ich habe in letzter Zeit viele Artikel gelesen, die primitive Obsession als Code-Geruch beschreiben.
Es gibt zwei Vorteile, wenn man primitive Obsessionen vermeidet:
Dadurch wird das Domänenmodell expliziter. Beispielsweise kann ich mit einem Geschäftsanalysten über eine Postleitzahl anstelle einer Zeichenfolge sprechen, die eine Postleitzahl enthält.
Die gesamte Validierung findet an einer Stelle statt in der gesamten Anwendung.
Es gibt viele Artikel, die beschreiben, wann es sich um einen Codegeruch handelt. Zum Beispiel kann ich den Vorteil der Beseitigung der primitiven Obsession für eine Postleitzahl wie diese sehen:
public class Address
{
public ZipCode ZipCode { get; set; }
}
Hier ist der Konstruktor des ZipCodes:
public ZipCode(string value)
{
// Perform regex matching to verify XXXXX or XXXXX-XXXX format
_value = value;
}
Sie würden das DRY- Prinzip brechen, indem Sie diese Validierungslogik überall dort einsetzen, wo eine Postleitzahl verwendet wird.
Was ist jedoch mit den folgenden Objekten:
Geburtsdatum: Vergewissern Sie sich, dass das Geburtsdatum größer als der Mindate und kleiner als das heutige Datum ist.
Gehalt: Überprüfen Sie, dass größer oder gleich Null ist.
Würden Sie ein DateOfBirth-Objekt und ein Salary-Objekt erstellen? Der Vorteil ist, dass Sie bei der Beschreibung des Domänenmodells darüber sprechen können. Ist dies jedoch ein Fall von Überentwicklung, da es nicht viele Validierungen gibt? Gibt es eine Regel, die beschreibt, wann und wann primitive Obsessionen beseitigt werden sollen, oder sollten Sie dies nach Möglichkeit immer tun?
Ich schätze, ich könnte einen Typalias anstelle einer Klasse erstellen, was bei Punkt eins oben helfen würde.
DateOfBirth
damit er es vergleicht?Salary
undDistance
Gegenstand haben, können Sie sie nicht versehentlich austauschbar verwenden. Sie können, wenn sie beide vom Typ sinddouble
.Antworten:
Das Gegenteil wäre "Domain Modeling" oder vielleicht "Over Engineering".
Die Einführung eines Salary-Objekts kann aus folgendem Grund sinnvoll sein: Zahlen stehen im Domain-Modell selten für sich, sie haben fast immer eine Dimension und eine Einheit. Normalerweise modellieren wir nichts Nützliches, wenn wir einer Zeit oder Masse eine Länge hinzufügen, und wir erzielen selten gute Ergebnisse, wenn wir Meter und Fuß mischen .
Bei DateOfBirth sind wahrscheinlich zwei Punkte zu beachten. Wenn Sie ein nicht-primitives Datum erstellen, können Sie zunächst alle seltsamen Aspekte der Datumsberechnung in den Mittelpunkt stellen. Viele Sprachen bieten eine sofort einsatzbereite Lösung. DateTime , java.util.Date . Dies sind domänenunabhängige Implementierungen von Datumsangaben, aber keine primitiven Elemente.
Zweitens
DateOfBirth
ist nicht wirklich eine Datumszeit; Hier in den USA ist "Geburtsdatum" ein kulturelles Konstrukt / eine gesetzliche Fiktion. Wir neigen dazu, das Geburtsdatum anhand des lokalen Geburtsdatums einer Person zu messen . Bob, geboren in Kalifornien, könnte ein "früheres" Geburtsdatum haben als Alice, geboren in New York, obwohl er der jüngere von beiden ist.Sicher nicht immer; an den grenzen sind anwendungen nicht objektorientiert . Primitive werden häufig verwendet, um das Verhalten in Tests zu beschreiben .
quelle
java.util.Date
durchjava.time.LocalDate
Um ehrlich zu sein: es kommt darauf an.
Es besteht immer die Gefahr, dass Ihr Code überarbeitet wird. Wie weit verbreitet sind Geburtsdatum und Gehalt? Verwenden Sie sie nur in drei eng gekoppelten Klassen oder werden sie in der gesamten Anwendung verwendet? Würden Sie sie "nur" in ihren eigenen Typ / Klasse einkapseln, um diese eine Einschränkung zu erzwingen, oder können Sie sich mehr Einschränkungen / Funktionen vorstellen, die tatsächlich dazugehören?
Nehmen wir zum Beispiel Gehalt: Haben Sie Operationen mit "Gehalt" (z. B. Umgang mit verschiedenen Währungen oder vielleicht eine toString () - Funktion)? Überlegen Sie, was Gehalt ist / tut, wenn Sie es nicht als einfaches Grundelement betrachten, und es besteht eine gute Chance, dass Gehalt seine eigene Klasse ist.
quelle
Eine mögliche Faustregel kann von der Ebene des Programms abhängen. Für die Domain (DDD) aka Entities Layer (Martin, 2018) könnte dies auch bedeuten, "Primitive für alles zu vermeiden, was ein Domain- / Geschäftskonzept darstellt". Die Begründungen lauten wie im OP: ein aussagekräftigeres Domänenmodell, die Validierung von Geschäftsregeln, die explizite Darstellung impliziter Konzepte (Evans, 2004).
Ein Typ-Alias kann eine einfache Alternative sein (Ghosh, 2017) und bei Bedarf zu einer Entitätsklasse umgestaltet werden. Zum Beispiel können wir zunächst , dass verlangen
Salary
sei>=0
, und später entscheiden, nicht zuzulassen$100.33333
oben und etwas$10,000,000
(was den Client bankrott wäre). Die Verwendung vonNonnegative
Primitiven zur DarstellungSalary
und anderer Konzepte würde dieses Refactoring erschweren.Das Vermeiden von Grundelementen kann auch dazu beitragen, eine Überentwicklung zu vermeiden. Angenommen, wir müssen Gehalt und Geburtsdatum in einer Datenstruktur kombinieren , um z. B. weniger Methodenparameter zu haben oder Daten zwischen Modulen zu übertragen. Dann können wir ein Tupel mit Typ verwenden
(Salary, DateOfBirth)
. In der Tat ist ein Tupel mit Primitiven(Nonnegative, Nonnegative)
nicht informativ, während einige aufgeblähteclass EmployeeData
Felder unter anderem die erforderlichen Felder verbergen würden. Die Signatur in saycalcPension(d: (Salary, DateOfBirth))
ist fokussierter als incalcPension(d: EmployeeData)
, was gegen das Prinzip der Schnittstellentrennung verstößt. Ebensoclass SalaryAndDateOfBirth
scheint ein Spezialist umständlich und ist wahrscheinlich ein Overkill. Später können wir eine Datenklasse definieren. Tupel und elementare Domänentypen lassen uns solche Entscheidungen aufschieben.In einer äußeren Schicht (z. B. GUI) kann es sinnvoll sein, die Entitäten auf ihre einzelnen Grundelemente zu "zerlegen" (z. B. in ein DAO einzufügen). Dies verhindert, dass Domänenabstraktionen in äußere Schichten gelangen, wie in Martin (2018) vertreten.
Literaturverzeichnis
E. Evans, "Domain-Driven Design", 2004
D. Ghosh, "Functional and Reactive Domain Modeling", 2017
RC Martin, "Clean architecture", 2018
quelle
Lieber unter primitiver Obsession leiden oder Architekturastronaut sein ?
Beide Fälle sind pathologisch, in einem Fall gibt es zu wenig Abstraktionen, was zu einer Wiederholung führt und leicht dazu führt, dass ein Apfel für eine Orange gehalten wird, und in dem anderen Fall haben Sie vergessen, bereits damit aufzuhören und Dinge zu erledigen, was es schwierig macht, etwas zu erledigen .
Wie fast immer möchten Sie Mäßigung, einen hoffentlich gut durchdachten Mittelweg.
Denken Sie daran, dass eine Eigenschaft zusätzlich zu einem Typ einen Namen hat. Außerdem kann das Zerlegen einer Adresse in ihre Bestandteile zu einschränkend sein, wenn immer auf die gleiche Weise vorgegangen wird. Nicht die ganze Welt ist in der Innenstadt von NY.
quelle
Wenn Sie eine Gehaltsklasse hatten, könnte diese über Methoden wie ApplyRaise verfügen.
Auf der anderen Seite muss Ihre ZipCode-Klasse nicht über eine interne Validierung verfügen, um zu vermeiden, dass die Validierung überall dort dupliziert wird, wo eine ZipCodeValidator-Klasse injiziert werden kann. Wenn Ihr System also sowohl für US- als auch für UK-Adressen ausgeführt werden soll, können Sie die einfach injizieren korrekter Validator und wenn Sie auch AUS-Adressen bearbeiten müssen, können Sie einfach einen neuen Validator hinzufügen.
Wenn Sie Daten über EntityFramework in eine Datenbank schreiben müssen, müssen Sie außerdem wissen, wie mit Gehalt oder Postleitzahl umgegangen wird.
Es gibt keine eindeutige Antwort darauf, wo die Grenze zwischen intelligenten Klassen gezogen werden sollte, aber ich werde sagen, dass ich Geschäftslogik wie die Validierung auf Geschäftslogikklassen verlagere, bei denen die Datenklassen scheinbar reine Daten sind um besser mit EntityFramework zu arbeiten.
Bei der Verwendung von Typ-Aliasen sollte der Name des Elements / der Eigenschaft alle erforderlichen Informationen zum Inhalt enthalten, damit ich keine Typ-Aliasen verwende.
quelle
(Was ist die Frage wahrscheinlich wirklich)
Wann ist die Verwendung des primitiven Typs kein Codegeruch?
(Antworten)
Wenn der Parameter keine Regeln enthält, verwenden Sie einen primitiven Typ.
Verwenden Sie den primitiven Typ für:
Verwenden Sie ein Objekt für:
Das letztere Beispiel haben Regeln darin, das heißt das Objekt
SimpleDate
besteht ausYear
,Month
undDay
. Durch die Verwendung von Object kann in diesem Fall das Konzept derSimpleDate
Gültigkeit in das Objekt eingeschlossen werden.quelle
Abgesehen von den kanonischen Beispielen für E-Mail-Adressen oder Postleitzahlen, die an anderer Stelle in dieser Frage angegeben sind, kann das Umgestalten außerhalb von Primitive Obsession besonders hilfreich sein, wenn Entity-IDs verwendet werden (siehe https://andrewlock.net/using-strongly-typed-entity -ids-to-avoid-primitive-obsession-part-1 / für ein Beispiel, wie es in .NET gemacht wird).
Ich habe die Anzahl verloren, wie oft sich ein Fehler eingeschlichen hat, weil eine Methode die folgende Signatur hatte:
Kompiliert einwandfrei, und wenn Sie Ihre Unit-Tests nicht rigoros durchführen, besteht dies möglicherweise auch. Ordnen Sie diese Entitäts-IDs jedoch in domänenspezifische Klassen um, und hey presto, Kompilierungsfehler:
Stellen Sie sich vor, diese Methode skaliert auf 10 oder mehr Parameter, alle
int
Datentypen ( unabhängig vom Geruch der langen Parameterliste ). Es wird noch schlimmer, wenn Sie zum Austauschen zwischen Domänenobjekten und DTOs so etwas wie AutoMapper verwenden und ein Refactoring durchführen wird vom automagischen Mapping nicht erfasst.quelle
Wenn Sie jedoch mit vielen verschiedenen Ländern und deren unterschiedlichen Postleitzahlensystemen zu tun haben, können Sie eine Postleitzahl nur dann validieren, wenn Sie das betreffende Land kennen. Ihre
ZipCode
Klasse muss also auch das Land speichern.Aber speichern Sie das Land dann separat als Teil
Address
der Postleitzahl (zu der auch die Postleitzahl gehört) und als Teil der Postleitzahl (zur Validierung)?ZipCode
Klasse, sondern eineAddress
Klasse, die wieder eine enthält,string ZipCode
was bedeutet, dass wir den Kreis geschlossen haben.Ich verstehe Ihre zugrunde liegende Behauptung nicht, dass Sie, wenn eine Information einen bestimmten Variablentyp hat, irgendwie verpflichtet sind, diesen Typ zu erwähnen, wenn Sie mit einem Geschäftsanalysten sprechen.
Warum? Warum können Sie nicht einfach über "die Postleitzahl" sprechen und den bestimmten Typ komplett weglassen? Welche Art von Gesprächen führen Sie mit Ihrem (nicht technischen!) Geschäftsanalysten, bei denen die Art der Immobilie für das Gespräch ausschlaggebend ist?
Woher ich komme, sind Postleitzahlen immer numerisch. Wir haben also die Wahl, wir könnten es als
int
oder als speichernstring
. Wir neigen dazu, eine Zeichenfolge zu verwenden, da keine mathematischen Operationen für die Daten zu erwarten sind, aber noch nie von einem Geschäftsanalysten angegeben wurde, dass es sich um eine Zeichenfolge handeln muss. Diese Entscheidung ist dem Entwickler überlassen (oder wohl dem technischen Analysten, obwohl er sich meiner Erfahrung nach nicht direkt mit dem Problem auseinandersetzt).Ein Geschäftsanalyst kümmert sich nicht um den Datentyp, solange die Anwendung das tut, was von ihr erwartet wird.
Validierung ist ein schwieriges Unterfangen, da es sich auf das stützt, was Menschen erwarten.
Zum einen stimme ich dem Validierungsargument nicht zu, um zu zeigen, warum primitive Besessenheit vermieden werden sollte, da ich nicht der Meinung bin, dass (als universelle Wahrheit) Daten immer zu jeder Zeit validiert werden müssen.
Was ist zum Beispiel, wenn dies eine kompliziertere Suche ist? Was passiert, wenn Sie bei der Validierung keine einfache Formatprüfung durchführen, sondern eine externe API kontaktieren und auf eine Antwort warten? Möchten Sie Ihre Anwendung wirklich dazu zwingen, diese externe API für jedes von
ZipCode
Ihnen instanziierte Objekt aufzurufen ?Vielleicht ist es eine strenge Geschäftsanforderung, und dann ist es natürlich gerechtfertigt. Dies ist jedoch keine universelle Wahrheit. Es wird viele Anwendungsfälle geben, in denen dies eher eine Belastung als eine Lösung darstellt.
Als zweites Beispiel, wenn Sie Ihre Adresse in ein Formular eingeben, ist es üblich, Ihre Postleitzahl vor Ihrem Land einzugeben. Es ist zwar schön, ein sofortiges Feedback zur Validierung in der Benutzeroberfläche zu haben, aber es würde mich (als Benutzer) tatsächlich behindern, wenn die Anwendung mich auf ein "falsches" Postleitzahlformat aufmerksam macht, da die eigentliche Ursache des Problems (z. B.) darin besteht Mein Land ist nicht das Land, das standardmäßig ausgewählt ist. Daher wurde die Validierung für das falsche Land durchgeführt.
Es ist die falsche Fehlermeldung, die den Benutzer ablenkt und unnötige Verwirrung stiftet.
Genauso wie die ständige Validierung keine universelle Wahrheit ist, sind es auch meine Beispiele. Es ist kontextabhängig . Einige Anwendungsdomänen erfordern vor allem eine Datenüberprüfung. In anderen Domänen steht die Validierung nicht so weit oben auf der Prioritätenliste, da der damit verbundene Aufwand im Widerspruch zu den tatsächlichen Prioritäten steht (z. B. Benutzererfahrung oder die Möglichkeit, fehlerhafte Daten zunächst zu speichern, damit sie korrigiert werden können, anstatt sie niemals zuzulassen) gelagert)
Das Problem bei diesen Validierungen ist, dass sie unvollständig, redundant sind oder auf ein viel größeres Problem hinweisen .
Das Überprüfen, ob ein Datum größer als der Mindate ist, ist überflüssig. Der Mindate bedeutet wörtlich, dass es das kleinstmögliche Datum ist. Und wo ziehen Sie die relevante Linie? Was bringt es, zu verhindern,
DateTime.MinDate
aber zu erlaubenDateTime.MinDate.AddSeconds(1)
? Sie wählen einen bestimmten Wert aus, der im Vergleich zu vielen anderen Werten nicht besonders falsch ist.Mein Geburtstag ist der 2. Januar 1978 (ist es nicht, aber nehmen wir an, es ist es). Angenommen, die Daten in Ihrer Bewerbung sind falsch. Stattdessen heißt es, dass ich Geburtstag habe:
Alle diese Daten sind falsch. Keiner von ihnen ist "richtiger" als der andere. Ihre Validierungsregel würde jedoch nur eines dieser drei Beispiele aufgreifen.
Sie haben auch den Kontext, wie Sie diese Daten verwenden, vollständig weggelassen. Wenn dies zB in einem Geburtstagserinnerungs-Bot verwendet wird, würde ich sagen, dass die Validierung sinnlos ist, da es keine besonders schlechten Konsequenzen gibt, wenn ein falsches Datum eingegeben wird.
Handelt es sich hingegen um Regierungsdaten und benötigen Sie das Geburtsdatum, um die Identität einer Person zu bestätigen (und wenn Sie dies nicht tun, hat dies schlimme Konsequenzen, z. B. die Verweigerung der sozialen Sicherheit einer Person), ist die Richtigkeit der Daten von größter Bedeutung und muss vollständig überprüft werden Überprüfen Sie die Daten. Die vorgeschlagene Validierung, die Sie jetzt haben, ist nicht ausreichend.
Für ein Gehalt gibt es einen gesunden Menschenverstand, der nicht negativ sein kann. Wenn Sie jedoch realistisch davon ausgehen, dass unsinnige Daten eingegeben werden, würde ich vorschlagen, dass Sie die Quelle dieser unsinnigen Daten untersuchen. Denn wenn Sie nicht vertrauen können, dass sie sensible Daten eingeben, können Sie auch nicht vertrauen, dass sie korrekte Daten eingeben .
Wenn stattdessen das Gehalt von Ihrer Bewerbung berechnet wird und es irgendwie möglich ist, eine negative (und korrekte) Zahl zu erhalten, ist es besser
Math.Max(myValue, 0)
, negative Zahlen in 0 umzuwandeln, als die Validierung nicht zu bestehen. Denn wenn Ihre Logik entscheidet, dass das Ergebnis eine negative Zahl ist, bedeutet ein Fehlschlagen der Validierung, dass die Berechnung erneut durchgeführt werden muss, und es besteht kein Grund zu der Annahme, dass beim zweiten Mal eine andere Zahl erstellt wird.Und wenn es zu einer anderen Zahl kommt, können Sie wiederum vermuten, dass die Berechnung nicht konsistent ist und daher nicht als vertrauenswürdig eingestuft werden kann.
Dies soll nicht heißen, dass die Validierung nicht nützlich ist. Aber sinnlose Validierung ist schlecht, weil sie Mühe kostet, während sie ein Problem nicht wirklich löst, und den Menschen ein falsches Sicherheitsgefühl vermittelt.
quelle