Name für dieses Antimuster? Felder als lokale Variablen [geschlossen]


68

In einem Code, den ich überprüfe, sehe ich Dinge, die dem folgenden moralischen Äquivalent entsprechen:

public class Foo
{
    private Bar bar;

    public MethodA()
    {
        bar = new Bar();
        bar.A();
        bar = null;
    }

    public MethodB()
    {
        bar = new Bar();
        bar.B();
        bar = null;
    }
}

Das Feld barhier ist logischerweise eine lokale Variable, da sein Wert niemals für Methodenaufrufe beibehalten werden soll. Da jedoch viele der Methoden Fooein Objekt vom Typ benötigen Bar, hat der ursprüngliche Codeautor gerade ein Feld vom Typ erstellt Bar.

  1. Das ist offensichtlich schlecht, oder?
  2. Gibt es einen Namen für dieses Antimuster?

60
Nun, das ist neu. Hoffe, diese Klasse wird nicht in einem Multithread-Kontext verwendet, oder Sie haben interessante Zeiten vor sich!
TMN

8
Das ist wirklich ungewöhnlich? Das habe ich in meiner Karriere schon oft gesehen.
JSB ձոգչ

32
@TMN Es ist nicht threadsicher, aber was noch schlimmer ist, es ist nicht einmal wiedereintrittsfähig. Wenn jeder Code in MethodAoder MethodBUrsache MethodAoder MethodBin irgendeiner Weise aufgerufen werden (und barwieder verwendet wird, in dem Fall der bar.{A,B}()Täter zu sein) haben Sie ähnliche Probleme auch ohne Gleichzeitigkeit.

73
Müssen wir einen Namen für jede dumme Sache haben, die jemand tun könnte? Nennen Sie es einfach eine schlechte Idee.
Caleb

74
Es ist schwer, es nicht das herabhängende Muster zu nennen.
PSR

Antworten:


86

Das ist offensichtlich schlecht, oder?

Ja.

  • Dadurch werden die Methoden nicht mehr wiedereintrittsfähig. Dies ist ein Problem, wenn sie in derselben Instanz rekursiv oder in einem Kontext mit mehreren Threads aufgerufen werden.

  • Dies bedeutet, dass der Status von einem Anruf zu einem anderen Anruf durchgesickert ist (wenn Sie vergessen haben, die Neuinitialisierung durchzuführen).

  • Dies erschwert das Verständnis des Codes, da Sie überprüfen müssen, ob der Code tatsächlich funktioniert. (Im Gegensatz zur Verwendung lokaler Variablen.)

  • Es macht jede FooInstanz größer als es sein muss. (Und stellen Sie sich dies für N Variablen vor ...)

Gibt es einen Namen für dieses Antimuster?

IMO, das verdient es nicht, Antimuster genannt zu werden. Es ist nur eine schlechte Programmiergewohnheit / ein Missbrauch von Java-Konstrukten / Mistcode. Dies ist die Art von Dingen, die im Code eines Studenten zu sehen sind, wenn der Student Vorlesungen übersprungen hat und / oder keine Programmierkenntnisse hat. Wenn Sie es in Seriencode sehen, ist es ein Zeichen dafür, dass Sie viel mehr Codeüberprüfungen durchführen müssen ...


Für die Aufzeichnung lautet die richtige Schreibweise:

public class Foo
{
    public methodA()
    {
        Bar bar = new Bar();  // Use a >>local<< variable!!
        bar.a();
    }

    // Or more concisely (in this case) ...
    public methodB()
    {
        new Bar().b();
    }
}

Beachten Sie, dass ich auch die Methoden- und Variablennamen so korrigiert habe, dass sie den akzeptierten Stilregeln für Java-Bezeichner entsprechen.


11
Ich würde sagen "Wenn Sie es im Produktionscode sehen, ist es ein Zeichen, dass Sie Ihren Einstellungsprozess überprüfen sollten "
Beta

@Beta - auch das, obwohl Sie in der Lage sein sollten, solche schlechten Codierungsgewohnheiten durch gründliche Codeüberprüfung zu korrigieren.
Stephen C

+1 für das bisschen mehr Code-Bewertungen. Ungeachtet dessen, was die Leute denken, wird eine Verbesserung der Einstellungspraxis diese Art von Problem nicht verhindern - das Einsetzen ist ein Mistschießen. Das Beste, was Sie tun können, ist, die Würfel zu Ihren Gunsten zu laden.
Mattnz

@StephenC "Dadurch werden die Methoden nicht mehr wiedereintrittsfähig. Dies ist ein Problem, wenn sie in derselben Instanz rekursiv oder in einem Kontext mit mehreren Threads aufgerufen werden." . Ich weiß, was Wiedereintritt ist, kann aber hier keinen Sinn erkennen, da die Methoden keine Sperren verwenden. Kannst du bitte erklären .
Geek

@Geek - Sie konzentrieren sich zu sehr auf das konkrete Beispiel. Ich spreche über den allgemeinen Fall, in dem die Methode von mehreren Threads oder rekursiv aufgerufen werden kann.
Stephen C

39

Ich würde es als unnötig großen Bereich für eine Variable bezeichnen. Diese Einstellung berücksichtigt auch Race-Bedingungen, wenn mehrere Threads auf MethodA und MethodB zugreifen.


12
Insbesondere denke ich, "variabler Bereich" ist der Name des Problems hier. Diese Person muss lernen, was lokale Variablen sind und wie / wann sie verwendet werden sollen. Das positive Muster oder die allgemeine Regel lautet, für jede Variable den kleinsten verfügbaren Bereich zu verwenden und ihn nur nach Bedarf zu erweitern. Klar ist, dass dieser Fehler nicht nur ein schlechter Stil ist. Das Codieren einer Rennbedingung wie dieser ist ein Fehler.
GlenPeterson

30

Dies ist ein spezieller Fall eines allgemeinen Musters, bekannt als global doorknobbing. Wenn Sie ein Haus bauen, ist es verlockend, nur einen Türknauf zu kaufen und herumliegen zu lassen. Wenn jemand eine Tür oder einen Schrank benutzen möchte, schnappt er sich einfach diesen einen globalen Türknauf. Wenn Türklinken teuer sind, kann es ein gutes Design sein.

Leider zerbricht die Realität, wenn dein Freund vorbeikommt und der Türknauf gleichzeitig an zwei verschiedenen Orten benutzt wird. Wenn die Türklinke so teuer ist, dass Sie sich nur eine leisten können, lohnt es sich, ein System zu bauen, mit dem die Leute sicher auf die Türklinke warten können.

In diesem Fall ist der Türknauf nur eine Referenz, es ist also billig. Wenn der Türknauf ein tatsächliches Objekt war (und das Objekt anstelle der Referenz wiederverwendet wurde), ist es möglicherweise teuer genug, um ein Objekt zu sein prudent global doorknob. Wenn es jedoch billig ist, nennt man es a cheapskate global doorknob.

Ihr Beispiel ist von der cheapskateVielfalt.


19

Das Hauptanliegen hier wäre Parallelität - Wenn das Foo von mehreren Threads verwendet wird, haben Sie ein Problem.

Darüber hinaus ist es einfach albern - lokale Variablen verwalten ihre eigenen Lebenszyklen perfekt. Das Ersetzen durch Instanzvariablen, die ungültig gemacht werden müssen, wenn sie nicht mehr nützlich sind, ist eine Aufforderung zum Fehler.


15

Es ist ein spezieller Fall von "inkorrektem Scoping" mit einer Seite von "variabler Wiederverwendung".


7

Es ist kein Anti-Muster. Anti-Patterns haben eine Eigenschaft, die es wie eine gute Idee erscheinen lässt, die die Leute dazu bringt, es absichtlich zu tun. Sie sind als Muster geplant und dann geht es fürchterlich schief.

Dies ist auch der Grund für Debatten darüber, ob es sich bei etwas um ein Muster, ein Anti-Muster oder ein häufig falsch angewendetes Muster handelt, das an einigen Stellen noch Verwendung findet.

Das ist einfach falsch.

Um ein bisschen mehr hinzuzufügen.

Dieser Code ist abergläubisch oder bestenfalls eine Frachtkultpraxis.

Ein Aberglaube ist etwas, das ohne klare Begründung getan wird. Es mag mit etwas Realem zusammenhängen, aber die Verbindung ist nicht logisch.

Eine Frachtkult-Praxis ist eine, bei der Sie versuchen, etwas zu kopieren, was Sie aus einer sachkundigeren Quelle gelernt haben, aber Sie kopieren tatsächlich eher die Oberflächenartefakte als den Prozess (so genannt für einen Kult in Papua-Neuguinea, der dies machen würde) Flugzeugsteuerradios aus Bambus in der Hoffnung, dass die japanischen und amerikanischen Flugzeuge des Zweiten Weltkriegs zurückkehren).

In beiden Fällen ist kein wirklicher Fall zu machen.

Ein Anti-Pattern ist ein Versuch, eine vernünftige Verbesserung zu erzielen, sei es in dem kleinen (dieser zusätzlichen Verzweigung, die zu dem zusätzlichen Fall führt, der behandelt werden muss, der zu Spaghetti-Code führt) oder in dem großen, in dem Sie ein Pattern sehr bewusst implementieren Das ist entweder diskreditiert oder umstritten (viele würden die Singletons als solche bezeichnen, wobei einige Objekte nur zum Schreiben (z. B. Protokollieren von Objekten) oder nur zum Lesen (z. B. Konfigurationseinstellungen) ausschließen und einige sogar diese verurteilen würden) oder wo Sie das lösen falsches Problem (als .NET zum ersten Mal herauskam, empfahl MS ein Muster für den Umgang mit der Entsorgung, wenn Sie sowohl nicht verwaltete als auch verwaltete Einwegfelder hatten - es geht in der Tat sehr gut mit dieser Situation um, aber das eigentliche Problem ist, dass Sie es haben beide Feldtypen derselben Klasse).

Als solches ist ein Anti-Pattern etwas, das eine kluge Person, die die Sprache, die Problemdomäne und die verfügbaren Bibliotheken gut kennt, absichtlich tut und das immer noch eine Kehrseite hat (oder von der behauptet wird), die die Kehrseite überwältigt.

Da keiner von uns anfängt, eine bestimmte Sprache, Problemdomäne und verfügbare Bibliotheken gut zu kennen, und jeder etwas übersehen kann, wenn er von einer vernünftigen Lösung zur nächsten wechselt (z. B. etwas in einem Feld für eine gute Verwendung ablegen und dann versuchen zu Refactor es weg, aber nicht den Job, und Sie werden am Ende mit Code wie in der Frage), und da wir alle Dinge von Zeit zu Zeit im Lernen vermissen, haben wir alle einige abergläubische oder Fracht-Kult-Code irgendwann erstellt . Das Gute ist, dass sie tatsächlich klarer zu identifizieren und zu korrigieren sind als Anti-Patterns. Wahre Anti-Muster sind entweder wohl keine Anti-Muster oder haben eine attraktive Qualität oder haben zumindest eine Möglichkeit, einen in sie zu locken, selbst wenn sie als schlecht identifiziert werden (zu viele und zu wenige Schichten sind beide unumstritten schlecht, aber vermeiden eine führt zum anderen).


1
Aber die Leute tun denken , dass dies eine gute Idee, wahrscheinlich , weil sie denken , dass es eine Form der Wiederverwendung von Code ist.
JSB ձոգչ

Anfänger scheinen oft zu glauben, dass das Deklarieren von zwei Variablen doppelt so viel Platz erfordert, und ziehen es daher vor, Variablen wiederzuverwenden. Dies zeigt ein Missverständnis des Speichermodells (Free Store vs. Stack, Möglichkeit der Wiederverwendung von Stack-Positionen) und Optimierungen, die alle Compiler durchführen können. Ich würde argumentieren, dass ein Anfänger die Wiederverwendung von Variablen als eine gute Idee betrachten könnte, sodass sie als Antimuster eingestuft werden könnte.
Philipp

Das macht es zu einem Aberglauben, nicht zu einem Gegenmuster.
Jon Hanna

3

(1) Ich würde sagen, dass es nicht gut ist.

Es führt eine manuelle Hausverwaltung durch, die der Stack automatisch mit lokalen Variablen durchführen kann.

Es gibt keinen Leistungsvorteil, da "neu" als Methodenaufruf bezeichnet wird.

BEARBEITEN: Der Speicherbedarf der Klasse ist größer, da der Balkenzeiger während der gesamten Lebensdauer der Klasse immer Speicher belegt. Wenn der Balkenzeiger lokal wäre, würde er nur für die Lebensdauer des Methodenaufrufs Speicher belegen. Der Speicher für den Zeiger ist nur ein Tropfen im Ozean, aber es ist immer noch ein unnötiger Tropfen.

Die Leiste ist für andere Methoden in der Klasse sichtbar. Methoden, die bar nicht verwenden, sollten es nicht sehen können.

Zustandsbasierte Fehler. In diesem speziellen Fall sollten Zustandsbasisfehler nur beim Multithreading auftreten, da jedes Mal "new" aufgerufen wird.

(2) Ich weiß nicht, ob es einen Namen dafür gibt, da es sich tatsächlich um mehrere Probleme handelt, nicht um eines.
- manuelle Reinigung, wenn Automatik verfügbar ist -
notwendiger Zustand -
Zustand, der länger lebt als seine Lebensdauer -
Sichtbarkeit oder Umfang ist zu hoch


2

Ich würde es "Wandering Xenophobe" nennen. Es möchte in Ruhe gelassen werden, weiß aber nicht genau, wo oder was es ist. Daher ist es eine schlechte Sache, wie andere gesagt haben.


2

Hier geht es gegen den Strich, aber obwohl ich das angegebene Beispiel in seiner aktuellen Form nicht als guten Codierungsstil betrachte, gibt es das Muster beim Testen von Einheiten.

Dies ist eine ziemlich übliche Methode zum Testen von Einheiten

public class BarTester
{
    private Bar bar;

    public Setup() { bar = new Bar(); }
    public Teardown() { bar = null; }

    public TestMethodA()
    {
        bar.A();        
    }

    public TestMethodB()
    {
        bar.B();
    }
}

ist nur eine Überarbeitung dieses Äquivalents des OP-Codes

public class BarTester
{
    private Bar bar;

    public TestMethodA()
    {
        bar = new Bar();
        bar.A();
        bar = null;
    }

    public TestMethodB()
    {
        bar = new Bar();
        bar.B();
        bar = null;
    }
}

Ich würde niemals Code schreiben, wie es in OPs Beispiel angegeben ist, es schreit nach Umgestaltung, aber ich betrachte das Muster weder als ungültig noch als Antimuster .


In diesem Fall wird das Fixture für jeden Test unabhängig instanziiert, und die Probleme im Zusammenhang mit der Wiederverwendung von Variablen oder der gleichzeitigen Verwendung treten nicht auf. Im allgemeinen Fall (Outside Unit Tests) ist nicht bekannt, ob für jedes Objekt höchstens eine Methode aufgerufen wird.
Philipp

1
@Philipp - Ich bestreite nicht das Problem der Wiederverwendung oder Parallelität, aber die Frage von OP betraf das Muster, das im Unit-Test häufig verwendet wird. Die Tatsache, dass das Fixture für jeden Test instanziiert wird, ist ein Implementierungsdetail des Testframeworks. Selbst bei Unit-Tests ist das Muster nur dann sicher, wenn das Testframework so verwendet wird, wie es verwendet werden soll. Die gleiche Überlegung gilt für die Verwendung dieses Musters im Produktionscode.
Lieven Keersmaekers

Es besteht keine Notwendigkeit zu setzen , barum null, schafft JUnit ohnehin eine neue Testinstanz für jede Testmethode.
oberlies

2

Dieser Codegeruch wird bei Refactoring von Beck / Fowler als temporäres Feld bezeichnet.

http://sourcemaking.com/refactoring/temporary-field

Bearbeitet, um auf Anfrage der Moderatoren weitere Erklärungen hinzuzufügen: Weitere Informationen zum Codegeruch finden Sie in der oben angegebenen URL oder in der gedruckten Ausgabe des Buches. Da das OP nach dem Namen für diesen bestimmten Antimuster- / Codegeruch suchte, dachte ich, dass es hilfreich wäre, wenn ich einen zuvor nicht erwähnten Verweis von einer etablierten Quelle zitiere, die über 10 Jahre alt ist, obwohl mir völlig klar ist, dass ich zu spät bin Spiel auf diese Frage, so ist es unwahrscheinlich, dass die Antwort abgestimmt oder akzeptiert wird.

Wenn es nicht zu persönlich ist, verweise und erkläre ich diesen speziellen Code-Geruch auch in meinem bevorstehenden Pluralsight-Kurs Refactoring Fundamentals, der voraussichtlich im August 2013 veröffentlicht wird.

Lassen Sie uns darüber sprechen, wie Sie dieses spezielle Problem beheben können, um einen Mehrwert zu erzielen. Es gibt verschiedene Möglichkeiten. Wenn das Feld wirklich nur von einer Methode verwendet wird, kann es zu einer lokalen Variablen herabgestuft werden. Wenn jedoch mehrere Methoden das Feld für die Kommunikation verwenden (anstelle von Parametern), ist dies der in Refactoring beschriebene klassische Fall, der durch Ersetzen des Felds durch Parameter korrigiert werden kann, oder wenn die mit diesem Code arbeitenden Methoden eng beieinander liegen Im Zusammenhang kann Extract Class verwendet werden, um die Methoden und das / die Feld (er) in eine eigene Klasse zu ziehen, in der sich das Feld immer in einem gültigen Zustand befindet (möglicherweise während der Erstellung festgelegt) und den Zustand dieser neuen Klasse darstellt. Wenn Sie die Parameterroute durchlaufen, aber zu viele Werte übergeben werden müssen, besteht eine andere Option darin, ein neues Parameterobjekt einzuführen.


Es ist wichtig zu beachten, dass temporäre Felder häufig für ein Refactoring einer Extraktklasse erforderlich sind. Aber jemand, der sich dessen bewusst ist, würde den Code in diesem Zwischenzustand wahrscheinlich nicht einchecken.
oberlies

1

Ich würde sagen, wenn Sie es auf den Punkt bringen, ist dies wirklich ein Mangel an Zusammenhalt, und ich könnte ihm den Namen "Falscher Zusammenhalt" geben. Ich würde diesen Begriff prägen (oder, wenn ich ihn irgendwoher bekomme und es vergesse, "ausleihen"), weil die Klasse insofern zusammenhängend zu sein scheint, als ihre Methoden auf einem Mitgliedsfeld zu funktionieren scheinen. In Wirklichkeit ist dies jedoch nicht der Fall, was bedeutet, dass der scheinbare Zusammenhalt tatsächlich falsch ist.

Ob es schlecht ist oder nicht, ich würde sagen, es ist klar, und ich denke, Stephen C macht einen ausgezeichneten Job, um zu erklären, warum. Unabhängig davon, ob es den Namen "Anti-Pattern" verdient oder nicht, denke ich, dass es und jedes andere Codierungsparadigma mit einem Label zu tun haben könnte, da dies die Kommunikation im Allgemeinen erleichtert.


1

Abgesehen von den bereits erwähnten Problemen würde die Verwendung dieses Ansatzes in Umgebungen ohne automatische Garbage Collection (z. B. C ++) zu Speicherverlusten führen, da die temporären Objekte nach der Verwendung nicht freigegeben werden. (Obwohl dies ein bisschen weit hergeholt zu sein scheint, gibt es Leute da draußen, die einfach 'null' in 'NULL' ändern und froh sind, dass der Code kompiliert wird.)


1

Dies scheint mir eine schreckliche Fehlanwendung des Flyweight-Musters zu sein. Ich habe leider ein paar Entwickler gekannt, die das gleiche "Ding" mehrere Male in verschiedenen Methoden verwenden und es einfach in ein privates Feld ziehen müssen, um irrtümlich Speicher zu sparen oder die Leistung zu verbessern oder eine andere seltsame Pseudooptimierung durchzuführen. In ihren Gedanken versuchen sie, ein ähnliches Problem zu lösen, wie Flyweight es nur in einer Situation tun soll, in der es eigentlich kein Problem ist, das gelöst werden muss.


-1

Ich habe dies oft erlebt, normalerweise beim Aktualisieren von Code, der zuvor von jemandem geschrieben wurde, der VBA For Dummies als ersten Titel aufnahm und dann ein relativ großes System in der Sprache schrieb, in die er gestolpert war.

Zu sagen, dass es wirklich schlechte Programmierpraktiken sind, ist richtig, aber es könnte nur eine Folge davon sein, dass wir keine von Internet und Fachleuten geprüften Ressourcen haben, wie wir sie heute alle genießen, die den ursprünglichen Entwickler möglicherweise auf einen "sichereren" Weg geführt haben.

Es hat das "Antipattern" -Tag zwar nicht wirklich verdient - aber es war gut, die Vorschläge zu sehen, wie das OP neu codiert werden könnte.


1
Willkommen bei Stack Exchange-Programmierern, vielen Dank für die Eingabe Ihrer ersten Antwort. Es sieht so aus, als hätten Sie eine Ablehnung erhalten, möglicherweise aus Gründen, die in den häufig gestellten Fragen (FAQ) unter programmers.stackexchange.com/faq aufgeführt sind . Die FAQ bietet großartige Vorschläge, wie Sie effektive (und bereits gestimmte) Antworten geben können. Manchmal sind die Leute so freundlich, eine Notiz über die Abwärtsabstimmung hinzuzufügen, um anzuzeigen, ob sie in irgendeiner Weise falsch ist, dupliziert, Meinung ohne Beweise ist oder auch ein Ich, das eine frühere Antwort wiederholt. Die meisten von uns haben Antworten abgelehnt, geschlossen oder entfernt, seien Sie also nicht besorgt. Es ist nur ein Teil der ersten Schritte. Herzlich willkommen.
DeveloperDon
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.