Ist Code wie dieser ein „Zugunglück“ (Verstoß gegen das Gesetz von Demeter)?

23

Beim Durchsuchen von Code, den ich geschrieben habe, bin ich auf das folgende Konstrukt gestoßen, das mich zum Nachdenken gebracht hat. Auf den ersten Blick scheint es sauber genug zu sein. Ja, im eigentlichen Code hat die getLocation()Methode einen etwas spezifischeren Namen, der genauer beschreibt, welchen Ort sie erhält.

service.setLocation(this.configuration.getLocation().toString());

In diesem Fall servicehandelt es sich um eine Instanzvariable eines bekannten Typs, die innerhalb der Methode deklariert wurde. this.configurationwird an den Klassenkonstruktor übergeben und ist eine Instanz einer Klasse, die eine bestimmte Schnittstelle implementiert (die eine öffentliche getLocation()Methode erfordert). Daher ist der Rückgabetyp des Ausdrucks this.configuration.getLocation()bekannt. speziell in diesem Fall ist es ein java.net.URL, während ein service.setLocation()will String. Da die beiden Typen String und URL nicht direkt kompatibel sind, einige ist eine Art Umwandlung erforderlich , um den quadratischen Pflock in dem runden Loch zu passen.

Jedoch , nach dem Gesetz von Demeter , wie in der zitierten Clean Code , ein Verfahren f in der Klasse C sollten nur Methoden aufrufen C , Objekte erstellt von oder als Argumente zu übergeben f , und Objekte in Instanzvariablen gehalten C . Alles darüber hinausgehende (das letzte toString()in meinem speziellen obigen Fall, es sei denn, Sie betrachten ein temporäres Objekt, das als Ergebnis des Methodenaufrufs selbst erstellt wurde; in diesem Fall scheint das gesamte Gesetz streitig zu sein), ist unzulässig.

Gibt es einen stichhaltigen Grund, warum ein Anruf wie der oben genannte angesichts der aufgeführten Einschränkungen abgelehnt oder sogar abgelehnt werden sollte? Oder bin ich nur zu pingelig?

Wenn ich eine Methode implementieren würde, URLToString()die einfach toString()ein URLObjekt (wie das von zurückgegebene getLocation()) aufruft , das als Parameter übergeben wurde, und das Ergebnis zurückgibt, könnte ich den getLocation()Aufruf darin einschließen, um genau dasselbe Ergebnis zu erzielen. effektiv würde ich die umwandlung nur einen schritt nach außen verschieben. Wäre das irgendwie akzeptabel? (Es scheint mir intuitiv, dass es in keiner Weise einen Unterschied machen sollte, da sich die Dinge nur ein wenig bewegen. Nach dem zitierten Wortlaut des Gesetzes von Demeter wäre es jedoch akzeptabel, da ich würde dann direkt an einem Parameter zu einer Funktion arbeiten.)

Würde es einen Unterschied machen, wenn es sich um etwas Exotischeres handeln würde, als toString()einen Standardtyp anzurufen?

Denken Sie bei der Beantwortung daran, dass servicees nicht praktikabel ist , das Verhalten oder die API des Typs zu ändern, von dem die Variable ist. Nehmen wir aus Gründen der Argumentation an, dass das Ändern des Rückgabetyps von getLocation()ebenfalls unpraktisch ist.

ein CVn
quelle

Antworten:

34

Das Problem hier ist die Unterschrift setLocation. Es ist streng getippt .

Zu erarbeiten: Warum sollte es erwarten String? A steht Stringfür jede Art von Textdaten . Dies kann möglicherweise alles andere als ein gültiger Ort sein.

Dies wirft in der Tat die Frage auf: Was ist ein Standort? Woher weiß ich , ohne in Ihren Code zu schauen? Wenn es ein URLwäre, würde ich viel mehr darüber wissen, was diese Methode erwartet.
Vielleicht wäre es sinnvoller, eine benutzerdefinierte Klasse zu sein Location. Ok, ich würde zuerst nicht wissen, was das ist, aber irgendwann (wahrscheinlich this.configuration.getLocation()würde ich vor dem Schreiben eine Minute brauchen, um herauszufinden, was diese Methode zurückgibt).
Zugegeben, in beiden Fällen muss ich an einem anderen Ort nachsehen, um zu verstehen, was erwartet wird. Wenn ich jedoch im letzteren Fall verstehe, was eine Locationist, kann ich Ihre API verwenden. Wenn ich im ersten Fall verstehe, was eine Stringist (was zu erwarten ist), weiß ich immer noch nicht, was Ihre API erwartet.

In dem unwahrscheinlichen Szenario, dass ein Ort irgendeine Art von Textdaten ist, würde ich dies in irgendeine Art von Daten uminterpretieren , die eine Textdarstellung haben . Angesichts der Tatsache, dass Objectdas eine toStringMethode hat, könnten Sie damit einverstanden sein, obwohl dies von den Kunden Ihres Codes einen ziemlichen Vertrauenssprung verlangt.

Sie sollten auch berücksichtigen, dass dies Java ist, von dem Sie sprechen, das nur sehr wenige Funktionen hat. Das ist es, was dich dazu zwingt, toStringam Ende tatsächlich anzurufen .
Wenn Sie zum Beispiel C # nehmen, das ebenfalls statisch geschrieben ist, können Sie diesen Aufruf tatsächlich auslassen, indem Sie das Verhalten für eine implizite Umwandlung definieren .
In dynamisch getippten Sprachen wie Objective-C brauchen Sie die Konvertierung auch nicht wirklich, denn solange sich der Wert wie eine Zeichenfolge verhält, sind alle zufrieden.

Man könnte argumentieren, dass der letzte Anruf toStringweniger ein Anruf ist, als nur der Lärm, der durch Javas Forderung nach expliziter Aussage erzeugt wird. Sie rufen eine Methode auf, die ein Java-Objekt besitzt, Sie codieren also eigentlich kein Wissen über eine "entfernte Einheit" und verletzen damit nicht das Prinzip des geringsten Wissens. Es gibt keine Möglichkeit, egal was getLocationzurückkehrt, dass es keine toStringMethode gibt.

Aber bitte, verwenden Sie keine Zeichenketten, es sei denn, sie sind wirklich die natürlichste Wahl (oder Sie verwenden eine Sprache, die nicht einmal Aufzählungen enthält ...).

back2dos
quelle
Ich stimme Ihnen eher zu, aber er hat bereits gesagt, dass er die Service-API nicht ändern kann.
jhocking
1
@ jhocking: Er hat nicht gesagt, dass er nicht kann. Er sagte, es sei unpraktisch. Ich stimme dir nicht zu. Der Versuch, Best Practices auf Code anzuwenden, der Fehler im API-Design umgeht, ist nur dann sinnvoll, wenn solche Problemumgehungen die einzig mögliche Option sind. Hier ist jedoch die Einstellung der API die beste Option. Die Beseitigung von Mängeln ist immer der Umgehung vorzuziehen.
back2dos
gut +1 für "ist weniger ein Anruf als nur der Lärm, der durch Javas Forderung nach expliziter
Aussage
1
Ich würde diese +1 geben, auch wenn nur für "Stringly Typed". In meinem Code versuche ich, Typen zu übergeben, die so viel wie möglich Bedeutung / Absicht ausdrücken. Bei der Arbeit mit Bibliotheken und APIs von Drittanbietern kommt es jedoch manchmal vor, dass Sie sich nicht an das halten, worüber die Autoren dieser API entschieden haben. Und IIRC, ein Ort kann in der Tat "jede Art von Textdaten" sein, aber für die Implementierung, an der ich arbeite, ist ein Ort ohne URL völlig bedeutungslos.
ein Lebenslauf vom
1
Wie zum Ändern der API; Da es sich um Computer-Software handelt, ist es praktisch immer möglich , Dinge zu ändern. (Wenn nichts anderes möglich ist, können Sie immer eine Abstraktionsschicht schreiben.) Manchmal ist dies jedoch sowohl kurz- als auch langfristig viel zu aufwendig, um gerechtfertigt zu sein. Dann ist es durchaus möglich, solche Änderungen vorzunehmen, aber immer noch unpraktisch.
ein Lebenslauf vom
21

Demeters Gesetz ist eine Gestaltungsrichtlinie, kein Gesetz, das religiös befolgt werden muss.

Wenn Sie der Meinung sind, dass Ihre Klassen ausreichend entkoppelt sind, liegt nichts an der Zeile, this.configuration.getLocation()insbesondere wenn es, wie Sie sagen, unpraktisch ist, andere Teile der API zu ändern.

Ich bin mir ziemlich sicher, dass der Kunde vollkommen glücklich sein wird, selbst wenn Sie das Demeter-Gesetz in Stücke schneiden, solange Sie das liefern, was er will und pünktlich. Dies ist keine Entschuldigung für schlechte Software, sondern eine Mahnung, bei der Entwicklung von Software pragmatisch vorzugehen.

Trasplazio Garzuglio
quelle
1
Da this.configurationes sich um eine Instanzvariable eines Typs einer bekannten Schnittstelle handelt, ist der Aufruf einer von dieser Schnittstelle definierten Methode auch nach strenger Interpretation in Ordnung. Ja, ich weiß, dass es eine Richtlinie ist, genau wie KISS, SOLID, YAGNI und so weiter. Es gibt nur sehr wenige "Gesetze" (im rechtlichen Sinne) in der allgemeinen Softwareentwicklung.
ein Lebenslauf vom
4
+1 für pragmatisch zu sein ;-)
Treb
1
Ich denke nicht, dass das Dementer-Gesetz in einem solchen Fall überhaupt gelten sollte - Konfiguration ist im Grunde genommen ein Container. In jeder API, mit der ich jemals gearbeitet habe, ist das Betrachten von Containern normal und das erwartete Verhalten.
Loren Pechtel
2

Das einzige, was ich mir vorstellen kann, keinen Code wie diesen zu schreiben, ist was, wenn this.configuration.getLocation()null zurückgegeben wird? Dies hängt von Ihrem Umgebungscode und der Zielgruppe ab, die diesen Code verwendet. Aber wie Marco sagt - Demeters Gesetz ist eine Faustregel - ist es gut zu befolgen, aber brechen Sie sich dabei nicht unnötig den Rücken.

Martyn
quelle
Wenn this.configuration.getLocation()null zurückgegeben würde, wären wir entweder (a) höchstwahrscheinlich nie so weit gekommen, oder (b) es ist in der Zwischenzeit etwas Katastrophales passiert. In diesem Fall möchte ich, dass der Code fehlschlägt. Während dies definitiv ein allgemein gültiger Punkt ist, ist es in diesem speziellen Fall ziemlich sicher zu sagen, dass er nicht zutrifft. All dies und noch viel mehr ist ein Ausnahmehandler, der speziell für diese Art von unerwarteten Fehlern entwickelt wurde.
ein Lebenslauf
2

Die strikte Befolgung des Gesetzes von Demeter würde bedeuten, dass Sie eine Methode im Konfigurationsobjekt implementieren sollten, wie:

function getLocationAsString() {
  return getLocation().toString();
}

aber ich persönlich würde mich nicht darum kümmern, weil dies eine so kleine Situation ist und eure Hände wegen der API, die ihr nicht ändern könnt, irgendwie gebunden sind. Bei Programmierregeln geht es darum, was Sie tun sollten, wenn Sie eine Wahl haben, aber manchmal haben Sie keine Wahl.

jhocking
quelle
1
Das gleiche wird hier vorgeschlagen: c2.com/cgi/wiki?TrainWreck "Erstellen Sie eine Methode, die das gewünschte Verhalten darstellt und dem Client mitteilt, was zu tun ist. Dies erfolgt nach dem Prinzip" Tell, Don't Ask "."
heltonbiker