Sind init () Methoden ein Codegeruch?

20

Gibt es einen Grund, eine init()Methode für einen Typ zu deklarieren ?

Ich frage nicht, ob wir einen Konstruktor vorzieheninit() sollen oder wieinit() wir es vermeiden sollen, dies zu deklarieren .

Ich frage , ob es irgendwelche Gründe eine hinter erklärt init()Methode ( zu sehen , wie häufig es ist) oder wenn es ein Codegeruch und sollte vermieden werden.


Die init()Redewendung ist weit verbreitet, aber ich habe noch keinen wirklichen Nutzen zu sehen.

Ich spreche von Typen, die die Initialisierung über eine Methode fördern:

class Demo {
    public void init() {
        //...
    }
}

Wann wird dies jemals im Produktionscode von Nutzen sein?


Ich glaube, es könnte ein Codegeruch sein, da der Konstruktor das Objekt nicht vollständig initialisiert, was zu einem teilweise erstellten Objekt führt. Das Objekt sollte nicht existieren, wenn sein Status nicht gesetzt ist.

Dies lässt mich glauben, dass dies Teil einer Technik ist, die zur Beschleunigung der Produktion im Sinne von Unternehmensanwendungen eingesetzt wird. Es ist der einzig logische Grund, warum ich mir eine solche Redewendung vorstellen kann. Ich bin mir nur nicht sicher, wie es vorteilhaft wäre, wenn dies der Fall wäre.

Vince Emigh
quelle
1
Zu "... sehen, wie häufig es ist ...": Ist es üblich? Können Sie einige Beispiele nennen? Vielleicht haben Sie es mit einem Framework zu tun, bei dem Initialisierung und Konstruktion getrennt werden müssen.
KOMMEN SIE VOM
Befindet sich die Methode in einer Basisklasse oder einer abgeleiteten Klasse oder in beiden? (Oder: init()Befindet sich die Methode in einer Klasse, die zu einer Vererbungshierarchie gehört? Ruft die Basisklasse die abgeleitete Klasse auf oder umgekehrt?) In diesem Fall kann die Basisklasse beispielsweise einen "Post-Konstruktor" ausführen "der nur ausgeführt werden kann, nachdem die am meisten abgeleitete Klasse die Konstruktion beendet hat. Dies ist ein Beispiel für eine mehrphasige Initialisierung.
Rwong
Wenn Sie zum Zeitpunkt der Instanziierung nicht initialisieren möchten, ist es sinnvoll, die beiden zu trennen.
31.
du könntest interessiert sein. is-a-start-run-or-
execute

Antworten:

39

Ja, es ist ein Codegeruch. Ein Codegeruch muss nicht unbedingt immer entfernt werden. Es ist etwas, das Sie dazu bringt, einen zweiten Blick darauf zu werfen.

Hier haben Sie ein Objekt in zwei grundverschiedenen Zuständen: vor und nach dem Start. Diese Staaten haben unterschiedliche Zuständigkeiten, unterschiedliche Methoden, die aufgerufen werden dürfen, und unterschiedliches Verhalten. Es sind effektiv zwei verschiedene Klassen.

Wenn Sie sie physisch in zwei separate Klassen unterteilen, entfernen Sie statisch eine ganze Klasse potenzieller Bugs, was dazu führt, dass Ihr Modell möglicherweise nicht ganz so genau mit dem Modell der "realen Welt" übereinstimmt. Sie nennen in der Regel die ersten Configoder Setupoder so ähnlich.

Versuchen Sie also das nächste Mal, Ihre construct-init-Idiome in Zwei-Klassen-Modelle umzugestalten, und sehen Sie, wie es für Sie ausgeht.

Karl Bielefeldt
quelle
6
Ihr Vorschlag, das Zwei-Klassen-Modell auszuprobieren, ist gut. Es ist nützlich, einen konkreten Schritt vorzuschlagen, um einen Codegeruch zu bekämpfen.
Ivan
1
Dies ist die bisher beste Antwort. Eines stört mich jedoch: " Diese Staaten haben unterschiedliche Verantwortlichkeiten, unterschiedliche Methoden, die aufgerufen werden dürfen, und unterschiedliches Verhalten " - Wenn diese Verantwortlichkeiten nicht getrennt werden, wird die SRP verletzt. Wenn der Zweck der Software darin bestand, ein reales Szenario in jeder Hinsicht zu replizieren, wäre dies sinnvoll. Aber in der Produktion, Devs meist Code schreiben , die die problemlose Verwaltung ermutigt, die Überarbeitung des Modells benötigt , wenn besser eine softwarebasierte passen enviornment (Fortsetzung nächste Kommentar)
Vince Emigh
1
Die reale Welt ist eine andere Umgebung. Programme, die versuchen, es zu replizieren, scheinen domänenspezifisch zu sein, da Sie dies in den meisten Projekten nicht berücksichtigen würden / sollten. Viele Dinge, die missbilligt werden, werden akzeptiert, wenn es um domänenspezifische Projekte geht. thisDaher versuche ich, dies so allgemein wie möglich zu halten (z. B. wie das Aufrufen des Konstruktors ein Codegeruch ist und zu fehleranfälligem Code führen kann). und es wird empfohlen, dies zu vermeiden, unabhängig davon, unter welche Domäne Ihr Projekt fällt).
Vince Emigh
14

Es hängt davon ab, ob.

Eine initMethode ist ein Codegeruch, wenn die Objektinitialisierung nicht vom Konstruktor getrennt werden muss. Manchmal ist es sinnvoll, diese Schritte zu trennen.

Eine schnelle Google-Suche brachte mir dieses Beispiel. Ich kann mir leicht weitere Fälle vorstellen, in denen der während der Objektzuweisung ausgeführte Code (der Konstruktor) möglicherweise besser von der Initialisierung selbst getrennt ist. Möglicherweise haben Sie ein geebnetes System, und die Zuordnung / Konstruktion erfolgt in Ebene X, die Initialisierung jedoch nur in Ebene Y, da nur Y die erforderlichen Parameter bereitstellen kann. Möglicherweise ist "init" kostspielig und muss nur für eine Teilmenge der zugewiesenen Objekte ausgeführt werden, und die Ermittlung dieser Teilmenge kann nur in Stufe Y erfolgen. Oder Sie möchten die (virtuelle) "init" -Methode in einer abgeleiteten Methode überschreiben Klasse, die mit einem Konstruktor nicht durchgeführt werden kann. Möglicherweise stellt Ihnen Stufe X zugewiesene Objekte aus einem Vererbungsbaum zur Verfügung, aber Stufe Y kennt die konkrete Ableitung nicht, sondern nur die gemeinsame Schnittstelle (woinit vielleicht definiert).

Nach meiner Erfahrung stellen diese Fälle natürlich nur einen kleinen Prozentsatz des Standardfalls dar, in dem die gesamte Initialisierung direkt im Konstruktor durchgeführt werden kann. Wenn Sie eine separate initMethode sehen, ist es möglicherweise eine gute Idee, deren Notwendigkeit in Frage zu stellen.

Doc Brown
quelle
2
Die Antwort in diesem Link besagt, dass es nützlich ist, den Arbeitsaufwand im Konstruktor zu reduzieren, aber teilweise erstellte Objekte fördert. Kleinere Konstruktoren können durch Zerlegung erreicht werden, daher scheint es mir, dass Antworten ein neues Problem schaffen (das Potenzial, zu vergessen, alle erforderlichen Initialisierungsmethoden aufzurufen, wodurch das Objekt fehleranfällig wird) und in die Kategorie Code-Geruch fallen
Vince Emigh
@VinceEmigh: ok, es war das erste Beispiel, das ich hier auf der SE-Plattform finden konnte, vielleicht nicht das beste, aber es gibt legitime Anwendungsfälle für eine separate initMethode. Wenn Sie jedoch eine solche Methode sehen, können Sie deren Notwendigkeit in Frage stellen.
Doc Brown
Ich stelle jeden Anwendungsfall in Frage / fordere ihn heraus, da ich der Meinung bin, dass es keine Situation gibt, in der dies notwendig wäre. Für mich ist es ein schlechtes Timing für die Objekterstellung, und es sollte vermieden werden, da es ein Kandidat für Fehler ist, die durch korrektes Design vermieden werden können. Wenn eine init()Methode richtig angewendet würde, würde ich sicher davon profitieren, wenn ich etwas über ihren Zweck erfahre. Entschuldigen Sie meine Unwissenheit, ich bin nur erstaunt darüber, wie schwer es mir fällt, eine Verwendung dafür zu finden, und ich kann nicht darüber
nachdenken
1
@VinceEmigh: Wenn du dir eine solche Situation nicht vorstellen kannst, musst du an deiner Vorstellungskraft arbeiten ;-). Oder lies meine Antwort noch einmal, reduziere sie nicht einfach auf "Zuordnung". Oder arbeiten Sie mit mehr Frameworks von verschiedenen Anbietern.
Doc Brown
1
@DocBrown Die Verwendung von Imagination anstelle von bewährten Methoden führt zu etwas unkonventionellem Code, z. Ich weiß, dass Anbieter es verwenden, aber das bedeutet nicht, dass die Verwendung gerechtfertigt ist. Wenn Sie glauben, dass dies der Fall ist, lassen Sie es mich bitte wissen, da ich versucht habe, das herauszufinden. Sie schienen fest entschlossen zu sein, einen nützlichen Zweck zu haben, aber es hört sich so an, als ob Sie Schwierigkeiten haben, ein Beispiel zu nennen, das gutes Design fördert. Ich weiß, unter welchen Umständen es verwendet werden könnte , aber sollte es verwendet werden?
Vince Emigh
5

Meine Erfahrung gliedert sich in zwei Gruppen:

  1. Code, in dem init () tatsächlich benötigt wird. Dies kann auftreten, wenn eine Superklasse oder ein Framework verhindert, dass der Konstruktor Ihrer Klasse während der Erstellung alle Abhängigkeiten abruft.
  2. Code, in dem init () verwendet wird, der jedoch vermieden werden könnte.

In meiner persönlichen Erfahrung habe ich nur ein paar Fälle von (1) gesehen, aber viel mehr Fälle von (2). Folglich gehe ich normalerweise davon aus, dass ein init () ein Code-Geruch ist, aber das ist nicht immer der Fall. Manchmal kommt man einfach nicht darum herum.

Ich habe festgestellt, dass die Verwendung des Builder-Musters häufig dazu beitragen kann, die Notwendigkeit / den Wunsch nach einem init () zu beseitigen.

Ivan
quelle
1
Wenn eine Superklasse oder ein Framework nicht zulässt, dass ein Typ die benötigten Abhängigkeiten über einen Konstruktor erhält, wie würde das Hinzufügen einer init()Methode dies beheben? Die init()Methode würde entweder Parameter benötigen, um Abhängigkeiten zu akzeptieren, oder Sie müssten die Abhängigkeiten innerhalb der init()Methode instanziieren , was Sie auch mit einem Konstruktor tun könnten. Könnten Sie ein Beispiel geben?
Vince Emigh
1
@VinceEmigh: init () kann manchmal verwendet werden, um eine Konfigurationsdatei von einer externen Quelle zu laden, eine Datenbankverbindung zu öffnen oder etwas Ähnliches. Auf diese Weise wird die DoFn.initialize () -Methode (aus dem Apache Crunch-Framework) verwendet. Es kann auch zum Laden nicht serialisierbarer interner Felder verwendet werden (DoFns müssen serialisierbar sein). Die beiden Probleme hierbei sind, dass (1) sichergestellt werden muss, dass die Initialisierungsmethode aufgerufen wird, und (2) dass das Objekt weiß, wo es diese Ressourcen abrufen (oder wie es sie aufbauen wird).
Ivan
1

Ein typisches Szenario, in dem sich eine Init-Methode als nützlich erweist, ist, wenn Sie eine Konfigurationsdatei haben, die Sie ändern möchten, und die Änderung berücksichtigt werden soll, ohne die Anwendung neu zu starten. Dies bedeutet natürlich nicht, dass eine Init-Methode getrennt von einem Konstruktor aufgerufen werden muss. Sie können eine Init-Methode von einem Konstruktor aus aufrufen und sie später aufrufen, wenn / wenn sich die Konfigurationsparameter ändern.

Fazit: Wie bei den meisten Dilemmata hängt es von der Situation und den Umständen ab, ob es sich um einen Codegeruch handelt oder nicht.

Vladimir Stokic
quelle
Wenn das Konfigurations - Updates, und das das Objekt erfordert zurückgesetzt / seinen Zustand ändern aufgrund der Konfiguration, glauben Sie nicht , es wäre besser, das Objekt wirkt als ein haben Beobachter auf Config?
Vince Emigh
@Vince Emigh Nicht unbedingt nötig. Observer würde funktionieren, wenn ich den genauen Zeitpunkt kenne, zu dem sich die Konfiguration ändert. Wenn die Konfigurationsdaten jedoch in einer Datei gespeichert sind, die außerhalb einer Anwendung geändert werden kann, gibt es keinen wirklich legitimen Ansatz. Wenn ich beispielsweise ein Programm habe, das einige Dateien analysiert und die Daten in ein internes Modell umwandelt, und eine separate Konfigurationsdatei Standardwerte für die fehlenden Daten enthält, werden diese beim nächsten Ausführen erneut gelesen, wenn ich die Standardwerte ändere das Parsen. In diesem Fall wäre es sehr praktisch, eine Init-Methode in meiner Anwendung zu haben.
Vladimir Stokic
Wenn die Konfigurationsdatei extern zur Laufzeit geändert wird, gibt es keine Möglichkeit , diese Variablen ohne nachzuladen irgendeiner Art von Meldung zu informieren Ihre Anwendung , dass es aufrufen muss init ( update/ reloadwürde wahrscheinlich für diese Art von Verhalten mehr beschreibend sein) , um tatsächlich jene Änderungen registrieren . In diesem Fall würde diese Benachrichtigung dazu führen, dass sich die Konfigurationswerte in Ihrer Anwendung intern ändern, was meiner Meinung nach dadurch zu beobachten ist, dass Ihre Konfiguration beobachtbar ist. Dadurch werden Beobachter benachrichtigt, wenn die Konfiguration angewiesen wird, einen ihrer Werte zu ändern. Oder missverstehe ich Ihr Beispiel?
Vince Emigh
Eine Anwendung verwendet einige externe Dateien (die aus einem GIS oder einem anderen System exportiert wurden), analysiert diese Dateien und wandelt sie in ein internes Modell um, das von den Systemen verwendet wird, zu denen die App gehört. Während dieses Vorgangs können einige Datenlücken gefunden werden und diese Datenlücken können durch Vorgabe der Werte gefüllt werden. Diese Standardwerte können in einer Konfigurationsdatei gespeichert werden, die zu Beginn des Transformationsprozesses gelesen werden kann. Sie wird aufgerufen, wenn neue Dateien zu transformieren sind. Hier könnte eine Init-Methode nützlich sein.
Vladimir Stokic
1

Kommt darauf an, wie du sie benutzt.

Ich verwende dieses Muster in Sprachen mit Speicherbereinigung wie Java / C #, wenn ich ein Objekt nicht ständig auf dem Heap neu zuordnen möchte (z. B. wenn ich ein Videospiel mache und die Leistung hoch halten möchte, Garbage Collectors die Leistung töten). Ich verwende den Konstruktor, um andere benötigte Heap-Zuordnungen vorzunehmen und initden grundlegenden nützlichen Status unmittelbar vor jeder Wiederverwendung zu erstellen. Dies hängt mit dem Konzept der Objektpools zusammen.

Es ist auch nützlich, wenn Sie über mehrere Konstruktoren verfügen, die eine gemeinsame Teilmenge der Initialisierungsanweisungen verwenden, in diesem Fall initjedoch privat sind. Auf diese Weise kann ich jeden Konstruktor so weit wie möglich minimieren, sodass jeder nur seine eindeutigen Anweisungen und einen einzigen Aufruf enthält, initum den Rest zu erledigen.

Im Allgemeinen ist es jedoch ein Codegeruch.

Cody
quelle
Wäre eine reset()Methode für Ihre erste Aussage nicht aussagekräftiger? Was den zweiten betrifft (viele Konstruktoren), so ist das Vorhandensein vieler Konstruktoren ein Codegeruch. Es wird davon ausgegangen, dass das Objekt mehrere Zwecke / Verantwortlichkeiten hat, was auf eine SRP-Verletzung hinweist. Ein Objekt sollte eine Verantwortung haben, und der Konstruktor sollte die erforderlichen Abhängigkeiten für diese eine Verantwortung definieren. Wenn Sie aufgrund optionaler Werte mehrere Konstruktoren haben, sollten diese teleskopieren (was auch ein Codegeruch ist, sollte stattdessen einen Builder verwenden).
Vince Emigh
@VinceEmigh Sie können Init oder Reset verwenden, es ist immerhin nur ein Name. Init ist in dem Kontext, in dem ich es tendenziell verwende, sinnvoller, da es wenig Sinn macht, ein Objekt zurückzusetzen, das bei der ersten Verwendung nie gesetzt wurde. Was das Konstruktorproblem betrifft, versuche ich, viele Konstruktoren zu vermeiden, aber gelegentlich ist es hilfreich. Schauen Sie sich die stringKonstruktorliste einer beliebigen Sprache an , mit unzähligen Optionen. Für mich sind es normalerweise maximal 3 Konstruktoren, aber die gemeinsame Untermenge von Anweisungen als Initialisierung ist sinnvoll, wenn sie sich einen Code teilen, sich aber in irgendeiner Weise unterscheiden.
Cody
Namen sind extrem wichtig. Sie beschreiben das Verhalten, ohne dass der Kunde den Vertrag lesen muss. Eine falsche Benennung kann zu falschen Annahmen führen. Für mehrere Konstruktoren in Stringkönnte dies gelöst werden, indem die Erstellung von Zeichenfolgen entkoppelt wird. Am Ende ist a Stringein Stringund sein Konstruktor sollte nur das akzeptieren, was für die Ausführung nach Bedarf erforderlich ist. Die meisten dieser Konstruktoren werden für Konvertierungszwecke bereitgestellt, was ein Missbrauch von Konstruktoren ist. Konstruktoren sollten keine Logik ausführen. Andernfalls besteht die Gefahr, dass die Initialisierung fehlschlägt und Sie ein unbrauchbares Objekt erhalten.
Vince Emigh
Das JDK ist voller schrecklicher Designs, ich könnte ungefähr zehn davon auflisten. Das Softwaredesign hat sich weiterentwickelt, seit die Kernaspekte vieler Sprachen öffentlich bekannt gemacht wurden, und sie verweilen aufgrund der Möglichkeit, Code zu brechen, wenn er für die moderne Zeit neu gestaltet wird.
Vince Emigh
1

init()Methoden können durchaus sinnvoll sein, wenn Sie Objekte haben, die externe Ressourcen benötigen (z. B. eine Netzwerkverbindung), die gleichzeitig von anderen Objekten verwendet werden. Möglicherweise möchten / müssen Sie die Ressource während der gesamten Lebensdauer des Objekts nicht auslasten. In solchen Situationen möchten Sie die Ressource möglicherweise nicht im Konstruktor zuordnen, wenn die Ressourcenzuordnung wahrscheinlich fehlschlägt.

Insbesondere in der eingebetteten Programmierung möchten Sie einen deterministischen Speicherbedarf haben. Daher ist es gängige (gute?) Praxis, Konstruktoren frühzeitig, möglicherweise sogar statisch, aufzurufen und erst später zu initialisieren, wenn bestimmte Bedingungen erfüllt sind.

Abgesehen von solchen Fällen denke ich, dass alles in einen Konstruktor gehen sollte.

Tofro
quelle
Wenn der Typ eine Verbindung erfordert, sollten Sie DI verwenden. Wenn beim Herstellen der Verbindung ein Problem auftritt, sollten Sie das erforderliche Objekt nicht erstellen. Wenn Sie die Erstellung einer Verbindung innerhalb der Klasse verschieben, erstellen Sie ein Objekt, das die Abhängigkeiten (die Verbindung) instanziiert. Wenn die Instanziierung von Abhängigkeiten fehlschlägt, erhalten Sie ein Objekt, das Sie nicht verwenden können, wodurch Ressourcen verschwendet werden.
Vince Emigh
Nicht unbedingt. Sie haben ein Objekt, das Sie vorübergehend nicht für alle Zwecke verwenden können. In solchen Fällen kann das Objekt nur als Warteschlange oder Proxy fungieren, bis die Ressource verfügbar wird. initMethoden gänzlich zu verurteilen ist zu einschränkend, IMHO.
Tofro
0

Generell bevorzuge ich einen Konstruktor, der alle für eine Funktionsinstanz erforderlichen Argumente erhält. Das macht alle Abhängigkeiten dieses Objekts deutlich.

Andererseits verwende ich ein einfaches Konfigurationsframework, das einen parameterlosen öffentlichen Konstruktor und Schnittstellen zum Einfügen von Abhängigkeiten und Konfigurationswerten erfordert. Danach ruft das Konfigurationsframework die initMethode des Objekts auf: Jetzt haben Sie alles erhalten, was ich für Sie habe. Führen Sie die letzten Schritte aus, um sich für die Arbeit vorzubereiten. Aber beachten Sie: Es ist das Konfigurationsframework, das die init-Methode automatisch aufruft, so dass Sie nicht vergessen werden, sie aufzurufen.

Bernhard Hiller
quelle
0

Es gibt keinen Codegeruch, wenn die init () - Methode semantisch in den Statuslebenszyklus des Objekts eingebettet ist.

Wenn Sie init () aufrufen müssen, um das Objekt in einen konsistenten Zustand zu versetzen, handelt es sich um einen Codegeruch.

Es gibt mehrere technische Gründe für die Existenz einer solchen Struktur:

  1. Rahmenhaken
  2. Zurücksetzen des Objekts in den Ausgangszustand (Redundanz vermeiden)
  3. Möglichkeit zur Übersteuerung bei Tests
oopexpert
quelle
1
Aber warum sollte ein Softwareentwickler sie in den Lebenszyklus einbetten? Gibt es einen Zweck, der gerechtfertigt ist (wenn man bedenkt, dass er teilweise konstruierte Objekte fördert) und nicht durch eine effizientere Alternative abgeschossen werden könnte? Ich bin der Meinung, dass das Einbetten in den Lebenszyklus ein Codegeruch wäre und dass es durch ein besseres Timing der Objekterstellung ersetzt werden sollte Objekt, das teilweise konstruiert ist, wenn Sie nur warten können, bis Sie das Objekt tatsächlich benötigen, bevor Sie es erstellen?)
Vince Emigh
Der Punkt ist, dass das Objekt verwendbar sein muss, bevor Sie die init-Methode aufrufen. Vielleicht anders als nach dem Aufruf. Siehe Statusmuster. Im Sinne einer partiellen Objektkonstruktion handelt es sich um einen Codegeruch.
oopexpert
-4

Der Name init kann manchmal undurchsichtig sein. Nehmen wir ein Auto und einen Motor. Um das Auto zu starten (nur einschalten, Radio hören), müssen Sie sicherstellen, dass alle Systeme betriebsbereit sind.

Sie konstruieren also einen Motor, eine Tür, ein Rad usw. Ihr Bildschirm zeigt Motor = Aus.

Es ist nicht erforderlich, den Motor usw. zu überwachen, da diese alle teuer sind. Wenn Sie dann den Zündschlüssel drehen, rufen Sie engine-> start auf. Es werden alle teuren Prozesse ausgeführt.

Jetzt sehen Sie engine = on. Und der Prozess für die Zündung beginnt.

Das Auto lässt sich nicht starten, wenn der Motor nicht verfügbar ist.

Sie können den Motor durch Ihre komplexe Berechnung ersetzen. Wie eine Excel-Zelle. Es müssen nicht immer alle Zellen mit allen Ereignishandlern aktiv sein. Wenn Sie sich auf eine Zelle konzentrieren, können Sie sie starten. Auf diese Weise die Leistung steigern.

Luc Franken
quelle