Nome per questo antipasto? Campi come variabili locali [chiuso]


68

In alcuni codici che sto recensendo, vedo cose che sono l'equivalente morale di quanto segue:

public class Foo
{
    private Bar bar;

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

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

Il campo barqui è logicamente una variabile locale, poiché il suo valore non è destinato a persistere tra le chiamate di metodo. Tuttavia, poiché molti dei metodi Foorichiedono un oggetto di tipo Bar, l'autore del codice originale ha appena creato un campo di tipo Bar.

  1. Questo è ovviamente negativo, vero?
  2. C'è un nome per questo antipasto?

60
Bene, quello è nuovo. Spero che questa classe non venga utilizzata in un contesto multithread, o avrai tempi interessanti in vista!
TMN,

8
Questo è davvero insolito? L'ho visto molte volte nella mia carriera.
JSB

32
@TMN Non è thread-safe, ma peggio ancora, non è nemmeno rientrato. Se un codice viene inserito MethodAo MethodBcausa MethodAo MethodBviene chiamato in qualche modo (e barviene utilizzato di nuovo, nel caso in cui bar.{A,B}()sia l'autore del reato) hai problemi simili anche senza concorrenza.

73
Dobbiamo avere un nome per ogni cosa stupida che qualcuno potrebbe fare? Chiamala semplicemente una cattiva idea.
Caleb,

74
Difficile non chiamarlo l'anti-schema privato penzolante.
psr

Risposte:


86

Questo è ovviamente negativo, vero?

Sì.

  • Rende i metodi non rientranti, il che è un problema se vengono chiamati sulla stessa istanza in modo ricorsivo o in un contesto multi-thread.

  • Significa che lo stato da una chiamata perde a un'altra chiamata (se si dimentica di reinizializzare).

  • Rende il codice difficile da capire perché devi controllare quanto sopra per essere sicuro di ciò che il codice sta effettivamente facendo. (Contrasto con l'utilizzo di variabili locali.)

  • Rende ogni Fooistanza più grande di quanto deve essere. (E immagina di farlo per N variabili ...)

C'è un nome per questo antipasto?

IMO, questo non merita di essere chiamato antipattern. È solo una cattiva abitudine di codifica / un uso improprio di costrutti Java / codice di merda. È il genere di cose che potresti vedere nel codice di uno studente universitario quando lo studente ha saltato le lezioni e / o non ha attitudine alla programmazione. Se lo vedi nel codice di produzione, è un segno che devi fare molte più revisioni del codice ...


Per la cronaca, il modo corretto di scrivere questo è:

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();
    }
}

Si noti che ho anche corretto il metodo e i nomi delle variabili in modo che siano conformi alle regole di stile accettate per gli identificatori Java.


11
Direi "Se lo vedi nel codice di produzione, è un segno che dovresti riesaminare il tuo processo di assunzione "
Beta

@Beta - anche quello, anche se dovresti essere in grado di correggere cattive abitudini di codifica come quella con una vigorosa revisione del codice.
Stephen C,

+1 per un po 'di più recensioni di codice. Nonostante ciò che la gente pensa, migliorare la pratica delle assunzioni non impedirà questo tipo di problema: assumere è una cazzata, il meglio che puoi fare è caricare i dadi a tuo favore.
mattnz,

@StephenC "Rende i metodi non rientranti, il che è un problema se vengono chiamati in modo ricorsivo sulla stessa istanza o in un contesto multi-thread." . So cos'è il rientro ma non riesco a vedere un punto per questo qui poiché i metodi non utilizzano alcun blocco? Puoi spiegare per favore.
Geek,

@Geek - Ti stai concentrando troppo sull'esempio specifico. Sto parlando del caso generale in cui il metodo potrebbe essere chiamato da più thread o in modo ricorsivo.
Stephen C,

39

Lo definirei ampio ambito non necessario per una variabile. Questa impostazione consente anche le condizioni di gara se più thread accedono al Metodo A e al Metodo B.


12
In particolare, penso che "portata variabile" sia il nome del problema qui. Questa persona deve imparare quali sono le variabili locali e come / quando usarle. Il modello positivo o la regola generale è di utilizzare l'ambito più piccolo disponibile per ciascuna variabile e ampliarlo solo se necessario. Per essere chiari, questo errore non è solo un cattivo stile. Codificare una condizione di gara come questa è un errore.
GlenPeterson,

30

Questo è un caso specifico di un modello generale noto come global doorknobbing. Quando si costruisce una casa, è allettante acquistare solo una maniglia e lasciarla in giro. Quando qualcuno vuole usare una porta o un armadio, afferrano semplicemente quella maniglia della porta globale. Se le maniglie delle porte sono costose, può essere un buon design.

Sfortunatamente, quando il tuo amico si avvicina e la maniglia della porta viene utilizzata contemporaneamente in due luoghi diversi, la realtà si frantuma. Se la maniglia della porta è così costosa che puoi permetterne solo una, allora vale la pena costruire un sistema che consenta alle persone di attendere il proprio turno per la maniglia della porta.

In questo caso la maniglia della porta è solo un riferimento, quindi è economica. Se la maniglia della porta fosse un oggetto reale (e stavano riutilizzando l'oggetto invece del solo riferimento), allora potrebbe essere abbastanza costoso da essere un prudent global doorknob. Tuttavia, quando è economico, è noto come a cheapskate global doorknob.

Il tuo esempio è della cheapskatevarietà.


19

La preoccupazione principale qui sarebbe la concorrenza: se Foo viene utilizzato da più thread, hai un problema.

Oltre a ciò, è semplicemente stupido: le variabili locali gestiscono perfettamente i propri cicli di vita. La loro sostituzione con variabili di istanza che devono essere annullate quando non sono più utili è un invito all'errore.


15

È un caso specifico di "scoping improprio", con un lato di "riutilizzo variabile".


7

Non è un anti-pattern. Gli anti-pattern hanno delle proprietà che lo fanno sembrare una buona idea, che porta le persone a farlo apposta; sono pianificati come schemi e poi va terribilmente storto.

È anche ciò che rende dibattiti sul fatto che qualcosa sia un modello, un anti-modello o un modello comunemente applicato in modo errato che ha ancora usi in alcuni punti.

Questo è semplicemente sbagliato.

Per aggiungere un po 'di più.

Questo codice è superstizioso o, nella migliore delle ipotesi, una pratica di culto delle merci.

Una superstizione è qualcosa fatta senza una chiara giustificazione. Potrebbe essere correlato a qualcosa di reale, ma la connessione non è logica.

Una pratica di culto delle merci è quella in cui si tenta di copiare qualcosa che hai imparato da una fonte più informata, ma in realtà stai copiando i manufatti di superficie piuttosto che il processo (così chiamato per un culto in Papua Nuova Guinea che farebbe le radio di controllo degli aerei in bambù sperano di far tornare gli aerei giapponesi e americani della Seconda Guerra Mondiale).

In entrambi questi casi non c'è nessun caso reale da formulare.

Un anti-pattern è un tentativo di un ragionevole miglioramento, sia nel piccolo (quella ramificazione extra per affrontare quel caso extra che deve essere affrontato, che porta al codice spaghetti) o nel grande in cui si implementa deliberatamente un pattern che sia screditato o discusso (molti descrivono i singoli in quanto tali, con alcuni che escludono una sola scrittura - ad es. oggetti di registrazione o di sola lettura ad es. oggetti delle impostazioni di configurazione - e alcuni condannerebbero anche quelli) oppure dove stai risolvendo il problema sbagliato (quando .NET è stato presentato per la prima volta, MS ha raccomandato un modello per gestire lo smaltimento quando si disponevano sia di campi non gestiti che di campi gestiti usa e getta - si occupa davvero molto bene di quella situazione, ma il vero problema è che hai entrambi i tipi di campo nella stessa classe).

Come tale, un anti-pattern è qualcosa che una persona intelligente che conosce bene la lingua, il dominio problematico e le librerie disponibili farà deliberatamente, che ha ancora (o si sostiene che abbia) un aspetto negativo che travolge il lato positivo.

Dal momento che nessuno di noi inizia a conoscere bene una determinata lingua, il dominio problematico e le librerie disponibili, e poiché tutti possono perdere qualcosa mentre passano da una soluzione ragionevole a un'altra (ad es. Iniziare a memorizzare qualcosa in un campo per un buon uso, e quindi provare a rifattalo via ma non completi il ​​lavoro, e finirai con il codice come nella domanda), e dato che ci mancano tutte le cose di volta in volta nell'apprendimento, a un certo punto tutti abbiamo creato un codice superstizioso o di culto . La cosa buona è che sono in realtà più chiari da identificare e correggere rispetto agli anti-schemi. I veri anti-pattern non sono probabilmente anti-pattern, o hanno una qualità attraente, o almeno hanno qualche modo di attirarne uno anche se identificati come cattivi (troppi e troppo pochi strati sono entrambi indiscutibilmente cattivi, ma evitandone uno porta all'altro).


1
Ma la gente non pensa che questa sia una buona idea, probabilmente perché pensano che si tratta di una forma di riutilizzo del codice.
JSB ձոգչ

I principianti spesso sembrano pensare che dichiarare due variabili in qualche modo richieda il doppio dello spazio, e quindi preferiscono riutilizzare le variabili. Ciò mostra un fraintendimento del modello di memoria (archivio libero contro stack, possibilità di riutilizzo delle posizioni dello stack) e ottimizzazioni che tutti i compilatori sono in grado di eseguire. Direi che un principiante potrebbe quindi considerare il riutilizzo variabile una buona idea, quindi potrebbe essere classificato come antipasto.
Philipp,

Questo lo rende una superstizione, non un antipattern.
Jon Hanna,

3

(1) Direi che non va bene.

Sta eseguendo house house mantenendo lo stack in grado di fare automaticamente con le variabili locali.

Non vi è alcun vantaggio in termini di prestazioni poiché "nuovo" è chiamato invocazione di ciascun metodo.

EDIT: il footprint di memoria della classe è maggiore perché il puntatore a barra occupa sempre memoria per tutta la durata della classe. Se il puntatore a barra fosse locale, utilizzerebbe la memoria solo per la durata della chiamata al metodo. La memoria per il puntatore è solo una goccia nell'oceano, ma è ancora una goccia non necessaria.

La barra è visibile ad altri metodi nella classe. I metodi che non usano la barra non dovrebbero essere in grado di vederlo.

Errori basati sullo stato. In questo caso particolare, gli errori di base dello stato dovrebbero verificarsi solo nel multithreading poiché ogni volta viene chiamato "new".

(2) Non so se c'è un nome perché è in realtà diversi problemi, non uno.
servizio di pulizia --manual quando automatica è disponibile
state --unnecessary
--state che vive più a lungo che si tratta di vita
--visibility o entità è troppo alto


2

Lo definirei "Xenophobe errante". Vuole essere lasciato solo ma finisce per non sapere bene dove o cosa sia. Quindi è una brutta cosa come altri hanno affermato.


2

Andando controcorrente qui, ma mentre non considero l'esempio dato come un buon stile di codifica come nella sua forma attuale, il modello esiste durante i test unitari.

Questo modo abbastanza standard di eseguire test unitari

public class BarTester
{
    private Bar bar;

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

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

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

è solo un refactoring di questo equivalente del codice OP

public class BarTester
{
    private Bar bar;

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

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

Non scriverei mai il codice come indicato nell'esempio di OP, urla il refactoring, ma considero il modelloinvalidoantipattern .


In tal caso, l'apparecchiatura viene istanziata in modo indipendente per ogni test e non si verificano problemi relativi al riutilizzo o alla concorrenza delle variabili. Nel caso generale (test di unità esterne) non è noto se al massimo venga chiamato un metodo su ciascun oggetto.
Philipp,

1
@Philipp - Non ho contestato il problema del riutilizzo o della concorrenza, ma la domanda di OP riguardava il modello che è quello che viene comunemente usato nei test unitari. Il fatto che il dispositivo sia istanziato per ogni test è un dettaglio di implementazione del framework di test. Anche nei test unitari, il modello è sicuro solo quando viene utilizzato il framework di test in quanto dovrebbe essere utilizzato. Lo stesso ragionamento si applica quando si utilizza questo modello nel codice di produzione.
Lieven Keersmaekers,

Non è necessario impostare barsu null, JUnit crea comunque una nuova istanza di test per ogni metodo di test.
oberlies,

2

Questo odore di codice viene chiamato Campo temporaneo nel refactoring da Beck / Fowler.

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

Modificato per aggiungere ulteriori spiegazioni su richiesta dei moderatori: puoi leggere ulteriori informazioni sull'odore del codice nell'URL di riferimento sopra o nella copia cartacea del libro. Poiché l'OP stava cercando il nome di questo particolare odore di antipattern / code, ho pensato che sarebbe stato utile citare un riferimento senza precedenti ad esso da una fonte affermata che ha più di 10 anni, anche se mi rendo pienamente conto che sono in ritardo per gioco su questa domanda, quindi è improbabile che la risposta venga votata o accettata.

Se non è troppo promozionale personalmente, faccio anche riferimento e spiego questo particolare odore di codice nel mio prossimo corso Pluralsight, Refactoring Fundamentals, che mi aspetto sarà pubblicato ad agosto 2013.

Per aggiungere più valore, parliamo di come risolvere questo particolare problema. Esistono diverse opzioni. Se il campo è veramente utilizzato solo da un metodo, può essere retrocesso in una variabile locale. Tuttavia, se più metodi utilizzano il campo per comunicare (al posto dei parametri), questo è il caso classico descritto in Refactoring e può essere corretto sostituendo il campo con parametri o se i metodi che funzionano con questo codice sono strettamente correlata, la classe di estrazione può essere utilizzata per estrarre i metodi e i campi nella propria classe, in cui il campo è sempre in uno stato valido (forse impostato durante la costruzione) e rappresenta lo stato di questa nuova classe. Se si percorre il percorso del parametro, ma ci sono troppi valori per passare, un'altra opzione è quella di introdurre un nuovo oggetto parametro,


È importante notare che i campi temporanei sono spesso richiesti per un refactoring della classe di estrazione. Ma qualcuno che è a conoscenza di questo probabilmente non verificherebbe il codice in questo stato intermedio.
oberlies,

1

Direi che quando ci si arriva, questa è davvero una mancanza di coesione, e potrei dargli il nome di "falsa coesione". Conterrei (o se lo sto ottenendo da qualche parte e dimenticarmene, "prendere in prestito") questo termine perché la classe sembra essere coerente in quanto i suoi metodi sembrano funzionare su un campo membro. Tuttavia, in realtà non lo fanno, il che significa che l'apparente coesione è in realtà falsa.

Per quanto riguarda il fatto che sia negativo o no, direi che lo è chiaramente, e penso che Stephen C faccia un ottimo lavoro nel spiegare il perché. Tuttavia, che meriti di essere chiamato "anti-pattern" o no, penso che qualsiasi altro paradigma di codifica potrebbe fare con un'etichetta, dal momento che in genere rende più semplice la comunicazione.


1

A parte i problemi già menzionati, l'utilizzo di questo approccio in ambiente senza garbage collection automatica (es. C ++) porterebbe a perdite di memoria, poiché gli oggetti temporanei non vengono liberati dopo l'uso. (Anche se questo può sembrare un po 'inverosimile, ci sono persone là fuori che cambiano semplicemente' null 'in' NULL 'e sono contenti che il codice venga compilato.)


1

Mi sembra un'orribile applicazione errata del modello Flyweight. Ho purtroppo conosciuto alcuni sviluppatori che devono usare la stessa "cosa" più volte in metodi diversi e semplicemente estrarla in un campo privato nel tentativo sbagliato di salvare memoria o migliorare le prestazioni o qualche altra pseudo-ottimizzazione. Nella loro mente stanno cercando di risolvere un problema simile in quanto Flyweight è pensato per affrontare solo lo stanno facendo in una situazione in cui non è in realtà un problema che deve essere risolto.


-1

L'ho incontrato molte volte, di solito durante l'aggiornamento del codice precedentemente scritto da qualcuno che aveva scelto VBA For Dummies come primo titolo e ha continuato a scrivere un sistema relativamente grande in qualunque lingua si fosse imbattuto.

Dire che è davvero una cattiva pratica di programmazione è giusto, ma potrebbe essere stato solo il risultato di non avere risorse Internet e peer review come quelle di cui tutti godiamo oggi che potrebbero aver guidato lo sviluppatore originale lungo un percorso "più sicuro".

Tuttavia, non merita davvero il tag "antipattern", ma è stato bello vedere i suggerimenti su come l'OP potrebbe essere ricodificato.


1
Benvenuto in Stack Exchange Programmers, grazie per aver inserito la tua prima risposta. Sembra che tu abbia avuto un voto negativo, probabilmente a causa di una delle ragioni elencate nelle FAQ programmers.stackexchange.com/faq . Le FAQ offrono ottimi suggerimenti su come rendere efficaci (e votate) le risposte. A volte le persone sono abbastanza gentili da aggiungere una nota sul voto negativo per indicare se è in qualche modo sbagliato, duplicato, opinione senza prove o anche io che ripete una risposta precedente. Molti di noi hanno avuto risposte votate, chiuse o rimosse, quindi non preoccupatevi. È solo una parte dell'inizio. Benvenuto.
Sviluppatore:
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.