I metodi init () hanno un odore di codice?


20

C'è uno scopo per dichiarare un init()metodo per un tipo?

Non sto chiedendo se dovremmo preferire init()un costruttore o come evitare di dichiarareinit() .

Sto chiedendo se c'è qualche logica dietro la dichiarazione di un init()metodo (visto quanto sia comune) o se è un odore di codice e dovrebbe essere evitato.


Il init()linguaggio è abbastanza comune, ma devo ancora vedere alcun beneficio reale.

Sto parlando di tipi che incoraggiano l'inizializzazione tramite un metodo:

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

Quando sarà mai utile nel codice di produzione?


Sento che potrebbe essere un odore di codice poiché suggerisce che il costruttore non inizializza completamente l'oggetto, risultando in un oggetto parzialmente creato. L'oggetto non dovrebbe esistere se lo stato non è impostato.

Questo mi fa credere che potrebbe far parte di un qualche tipo di tecnica utilizzata per accelerare la produzione, nel senso delle applicazioni aziendali. È l'unica ragione logica che posso pensare di avere un tale idioma, non sono sicuro di come sarebbe utile in tal caso.


1
A "... vedere quanto è comune ...": è comune? Puoi fare qualche esempio? Forse hai a che fare con un framework che richiede la separazione tra inizializzazione e costruzione.
VIENI DAL

Il metodo si trova su una classe base o su una classe derivata o su entrambi? (Oppure: è il metodo trovato su una classe che appartiene a una gerarchia di ereditarietà? La classe base chiama la classe init()on derivata o viceversa?) In tal caso, è un esempio di come consentire alla classe base di eseguire un "post-costruttore "che può essere eseguito solo dopo che la classe più derivata ha terminato la costruzione. È un esempio di inizializzazione multifase.
rwong,

Se non si desidera inizializzare nel punto di istanza, sarebbe logico separare i due.
JᴀʏMᴇᴇ


Risposte:


39

Sì, è un odore di codice. Un odore di codice non è qualcosa che deve necessariamente essere sempre rimosso. È qualcosa che ti fa dare una seconda occhiata.

Qui hai un oggetto in due stati fondamentalmente diversi: pre-init e post-init. Tali stati hanno responsabilità diverse, metodi diversi che possono essere chiamati e comportamento diverso. Sono effettivamente due diverse classi.

Se li trasformi fisicamente in due classi separate, rimuoverai staticamente un'intera classe di potenziali bug, a costo forse di far sì che il tuo modello non corrisponda al "modello del mondo reale" abbastanza da vicino. Di solito è il nome del primo Configo Setupo qualcosa del genere.

Quindi, la prossima volta, prova a refactoring i tuoi idiomi di costruzione-init in modelli di due classi e vedi come ti risulta.


6
Il tuo suggerimento di provare il modello a due classi è buono. È utile proporre un passo concreto per affrontare un odore di codice.
Ivan,

1
Questa è la risposta migliore finora. Una cosa però mi dà fastidio: " Questi stati hanno responsabilità diverse, metodi diversi che possono essere chiamati e comportamento diverso " - Non separare queste responsabilità viola SRP. Se lo scopo del software fosse replicare uno scenario del mondo reale in ogni aspetto, questo avrebbe senso. Ma nella produzione, gli sviluppatori scrivono principalmente codice che incoraggia una facile gestibilità, rivedendo il modello se necessario per adattarsi meglio a un ambiente basato su software (continua al prossimo commento)
Vince Emigh,

1
Il mondo reale è un ambiente diverso. I programmi che tentano di replicarlo sembrano specifici del dominio, in quanto non lo si dovrebbe / non si dovrebbe rendere conto nella maggior parte dei progetti. Molte cose che sono disapprovate sono accettate quando si tratta di progetti specifici di dominio, quindi sto cercando di mantenerlo il più generale possibile (come il modo in cui chiamare thisnel costruttore è un odore di codice e potrebbe comportare un codice soggetto a errori, ed è consigliabile evitarlo indipendentemente dal dominio in cui rientra il progetto).
Vince Emigh,

14

Dipende.

Un initmetodo è un odore di codice quando non è necessario che l'inizializzazione dell'oggetto sia separata dal costruttore. A volte ci sono casi in cui ha senso separare questi passaggi.

Una rapida ricerca su Google mi ha dato questo esempio. Posso facilmente immaginare più casi in cui il codice eseguito durante l'allocazione degli oggetti (il costruttore) potrebbe essere meglio separato dall'inizializzazione stessa. Forse hai un sistema livellato e l'allocazione / costruzione avviene nel livello X, ma l'inizializzazione solo nel livello Y, perché solo Y può fornire i parametri necessari. Forse "init" è costoso e deve essere eseguito solo per un sottoinsieme degli oggetti allocati, e la determinazione di quel sottoinsieme può essere eseguita solo nel livello Y. Oppure si desidera sovrascrivere il metodo (virtuale) "init" in un derivato classe che non può essere eseguita con un costruttore. Forse il livello X ti fornisce oggetti allocati da un albero ereditario, ma il livello Y non è a conoscenza della derivazione concreta, ma solo dell'interfaccia comune (doveinit forse definito).

Naturalmente, per la mia esperienza, questi casi sono solo una piccola percentuale del caso standard in cui tutta l'inizializzazione può essere eseguita direttamente nel costruttore e ogni volta che vedi un initmetodo separato , potrebbe essere una buona idea mettere in discussione la sua necessità.


2
La risposta in quel link dice che è utile per ridurre la quantità di lavoro nel costruttore, ma incoraggia gli oggetti parzialmente creati. I costruttori più piccoli possono essere raggiunti attraverso la decomposizione, quindi a me sembra che le risposte creino un nuovo problema (il potenziale di dimenticare di chiamare tutti i metodi di inizializzazione richiesti, lasciando l'oggetto soggetto a errori) e rientra nella categoria degli odori del codice
Vince Emigh

@VinceEmigh: ok, è stato il primo esempio che ho trovato qui sulla piattaforma SE, forse non il migliore, ma ci sono casi d'uso legittimi per un initmetodo separato . Tuttavia, ogni volta che vedi un tale metodo, sentiti libero di mettere in discussione la sua necessità.
Doc Brown,

Sto mettendo in discussione / sfidando ogni caso d'uso, poiché ritengo che non vi sia alcuna situazione in cui sarebbe necessario. Per me, è un cattivo tempismo nella creazione di oggetti, e dovrebbe essere evitato poiché è un candidato per errori che possono essere evitati attraverso una progettazione adeguata. Se ci fosse un uso corretto di un init()metodo, sono sicuro che trarrebbe beneficio dall'apprendere sul suo scopo. Scusa la mia ignoranza, sono solo stupito da quanto sia difficile trovare un uso per questo, impedendomi di considerarlo qualcosa che dovrebbe essere evitato
Vince Emigh,

1
@VinceEmigh: quando non riesci a pensare a una situazione del genere, devi lavorare sulla tua immaginazione ;-). Oppure leggi di nuovo la mia risposta, non ridurla solo a "allocazione". Oppure lavora con più framework di diversi fornitori.
Doc Brown,

1
@DocBrown L'uso dell'immaginazione piuttosto che di pratiche comprovate porta ad alcuni codici funky, come l'inizializzazione di doppie parentesi graffe: intelligente, ma inefficiente e dovrebbe essere evitata. So che i venditori lo usano, ma ciò non significa che l'uso sia giustificato. Se lo senti, per favore fammi sapere perché, perché è quello che ho cercato di capire. Sembravi morto sul fatto che avesse ALCUNO scopo benefico, ma sembra che tu abbia difficoltà a dare un esempio che incoraggi un buon design. So in quali circostanze potrebbe essere usato, ma dovrebbe essere usato?
Vince Emigh,

5

La mia esperienza si divide in due gruppi:

  1. Codice in cui init () è effettivamente richiesto. Questo può accadere quando una superclasse o un framework impedisce al costruttore della tua classe di ottenere tutte le sue dipendenze durante la costruzione.
  2. Codice in cui viene utilizzato init () ma che avrebbe potuto essere evitato.

Nella mia esperienza personale ho visto solo alcuni esempi di (1) ma molti più esempi di (2). Di conseguenza, di solito presumo che init () sia un odore di codice, ma non è sempre così. A volte non riesci proprio ad aggirarlo.

Ho scoperto che usare il Builder Pattern può spesso aiutare a rimuovere la necessità / il desiderio di avere un init ().


1
Se una superclasse o un framework non consente a un tipo di ottenere le dipendenze di cui ha bisogno tramite il costruttore, come potrebbe init()risolverlo aggiungendo un metodo? Il init()metodo avrebbe bisogno di parametri per accettare le dipendenze o dovresti istanziare le dipendenze all'interno del init()metodo, cosa che potresti fare anche con un costruttore. Potresti fare un esempio?
Vince Emigh,

1
@VinceEmigh: init () a volte può essere usato per caricare un file di configurazione da una fonte esterna, aprire una connessione al database o qualcosa del genere. In questo modo viene utilizzato il metodo DoFn.initialize () (dal framework Crunch di Apache). Può anche essere utilizzato per caricare campi interni non serializzabili (i DoFn devono essere serializzabili). I due problemi qui sono che (1) qualcosa deve garantire che venga chiamato il metodo di inizializzazione e (2) l'oggetto deve sapere dove sta andando (o come costruirà) quelle risorse.
Ivan,

1

Uno scenario tipico in cui un metodo Init è utile è quando si dispone di un file di configurazione che si desidera modificare e di cui tenere conto della modifica senza riavviare l'applicazione. Questo, ovviamente, non significa che un metodo Init debba essere chiamato separatamente da un costruttore. È possibile chiamare un metodo Init da un costruttore, quindi richiamarlo in seguito quando / se i parametri di configurazione cambiano.

Per riassumere: come per la maggior parte dei dilemmi là fuori, che si tratti di un odore di codice o meno, dipende dalla situazione e dalle circostanze.


Se gli aggiornamenti di configurazione, e questo richiede l'oggetto da ripristinare / cambiarlo dello Stato in base alla configurazione, non pensi che sarebbe meglio avere l'atto oggetto come un osservatore verso Config?
Vince Emigh,

@Vince Emigh Non necesarilly. Observer funzionerebbe se conosco il momento esatto in cui la configurazione cambia. Tuttavia, se i dati di configurazione sono conservati in un file che può essere modificato al di fuori di un'applicazione, non esiste un approccio veramente ellegante. Ad esempio, se ho un programma che analizza alcuni file e trasforma i dati in un modello interno e un file di configurazione separato contiene valori predefiniti per i dati mancanti, se modifico i valori predefiniti, li rileggerò alla prossima esecuzione l'analisi. Avere un metodo Init nella mia applicazione sarebbe abbastanza utile in quel caso.
Vladimir Stokic,

Se il file di configurazione viene modificato esternamente in fase di runtime, non è possibile ricaricare tali variabili senza alcun tipo di notifica che informa l'applicazione che deve chiamare init ( update/ reloadprobabilmente sarebbe più descrittivo per questo tipo di comportamento) per registrare effettivamente tali modifiche . In tal caso, tale notifica comporterebbe un cambiamento interno dei valori della configurazione nella tua applicazione, cosa che credo possa essere osservata facendo in modo che la tua configurazione sia osservabile, notificando agli osservatori quando viene detto alla configurazione di cambiare uno dei suoi valori. O sto fraintendendo il tuo esempio?
Vince Emigh,

Un'applicazione prende alcuni file esterni (esportati da alcuni GIS o altri sistemi), analizza quei file e li trasforma in un modello interno che i sistemi di cui l'app fa parte. Durante tale processo, è possibile trovare alcuni spazi vuoti di dati e tali spazi vuoti di dati possono essere riempiti impostando i valori per impostazione predefinita. Tali valori predefiniti possono essere memorizzati in un file di configurazione che può essere letto all'inizio del processo di trasformazione, che viene invocato ogni volta che ci sono nuovi file da trasformare. È qui che un metodo Init potrebbe tornare utile.
Vladimir Stokic,

1

Dipende da come li usi.

Uso questo modello in linguaggi di immondizia raccolti come Java / C # quando non voglio continuare a riallocare un oggetto nell'heap (come quando sto realizzando un videogioco e ho bisogno di mantenere alte le prestazioni, i garbage collector ne uccidono). Uso il costruttore per fare altre allocazioni di heap di cui ha bisogno einit per creare lo stato utile di base prima di ogni volta che voglio riutilizzarlo. Ciò è legato al concetto di pool di oggetti.

È inoltre utile se si dispone di più costruttori che condividono un sottoinsieme comune di istruzioni di inizializzazione, ma in tal caso initsarà privato. In questo modo posso minimizzare ogni costruttore il più possibile, quindi ognuno contiene solo le sue istruzioni uniche e una sola chiamata ainit per fare il resto.

In generale, tuttavia, è un odore di codice.


Un reset()metodo non sarebbe più descrittivo per la tua prima affermazione? Per quanto riguarda il secondo (molti costruttori), avere molti costruttori è un odore di codice. Presuppone che l'oggetto abbia molteplici scopi / responsabilità, suggerendo una violazione di SRP. Un oggetto dovrebbe avere una responsabilità e il costruttore dovrebbe definire le dipendenze richieste per quella responsabilità. Se hai più costruttori a causa di valori opzionali, dovrebbero telescopare (che è anche un odore di codice, dovrebbe invece usare un builder).
Vince Emigh,

@VinceEmigh puoi usare init o reset, dopo tutto è solo un nome. Init ha più senso nel contesto in cui tendo a usarlo poiché ha poco senso ripristinare un oggetto che non è mai stato impostato per la prima volta che lo uso. Per quanto riguarda il problema dei costruttori, cerco di evitare di avere molti costruttori, ma a volte è utile. Guarda l' stringelenco dei costruttori di qualsiasi lingua , tonnellate di opzioni. Per me di solito è forse un massimo di 3 costruttori, ma il sottoinsieme comune di istruzioni come inizializzazione ha senso quando condividono qualsiasi codice ma differiscono in qualche modo.
Cody

I nomi sono estremamente importanti. Descrivono il comportamento eseguito senza forzare il cliente a leggere il contratto. Una cattiva denominazione può portare a presupposti falsi. Per quanto riguarda più costruttori in String, questo potrebbe essere risolto disaccoppiando la creazione di stringhe. Alla fine, a Stringè a Stringed il suo costruttore dovrebbe accettare solo ciò che è necessario per farlo funzionare come necessario. La maggior parte di questi costruttori sono esposti a scopi di conversione, che è un uso improprio dei costruttori. I costruttori non devono eseguire la logica, altrimenti rischiano di inizializzare non riuscendo, lasciando un oggetto inutile.
Vince Emigh

Il JDK è pieno di disegni orribili, potrei elencare circa 10 dalla parte superiore della mia testa. La progettazione del software si è evoluta da quando gli aspetti fondamentali di molte lingue sono stati esposti pubblicamente e persistono a causa della possibilità di violare il codice se dovesse essere riprogettato per i tempi moderni.
Vince Emigh,

1

init()i metodi possono avere un certo senso quando si hanno oggetti che richiedono risorse esterne (come, ad esempio, una connessione di rete) che vengono utilizzati contemporaneamente da altri oggetti. Potrebbe non essere necessario / necessario limitare la risorsa per la durata dell'oggetto. In tali situazioni, potresti non voler allocare la risorsa nel costruttore quando è probabile che l'allocazione delle risorse fallisca.

Soprattutto nella programmazione integrata, si desidera avere un footprint di memoria deterministico, quindi è pratica (buona?) Comune chiamare i costruttori in anticipo, forse anche statici, e inizializzare solo in seguito quando si verificano determinate condizioni.

A parte questi casi, penso che tutto dovrebbe andare in un costruttore.


Se il tipo richiede una connessione, è necessario utilizzare DI. Se si verifica un problema durante la creazione della connessione, non è necessario creare l'oggetto che lo richiede. Se si spinge la creazione di una connessione all'interno della classe, verrà creato un oggetto, quell'oggetto creerà un'istanza delle sue dipendenze (la connessione). Se l'istanza delle dipendenze fallisce, si finisce con un oggetto che non si può usare, sprecando così risorse.
Vince Emigh,

Non necessariamente. Si finisce con un oggetto che non è temporaneamente possibile utilizzare per tutti gli scopi. In tali casi, l'oggetto potrebbe semplicemente fungere da coda o proxy fino a quando la risorsa non diventa disponibile. Condannare totalmente i initmetodi è troppo restrittivo, IMHO.
fino al

0

In genere preferisco un costruttore che riceve tutti gli argomenti richiesti per un'istanza funzionale. Ciò chiarisce tutte le dipendenze di quell'oggetto.

D'altra parte, utilizzo un semplice framework di configurazione che richiede un costruttore pubblico senza parametri e interfacce per l'iniezione di dipendenze e valori di configurazione. Fatto ciò, il framework di configurazione chiama il initmetodo dell'oggetto: ora hai ricevuto tutte le cose che ho per te, fai gli ultimi passi per prepararti al lavoro. Ma nota: è il framework di configurazione che chiama automaticamente il metodo init, in modo da non dimenticare di chiamarlo.


0

Non c'è odore di codice se il metodo init () - è semanticamente incorporato nel ciclo di vita dello stato dell'oggetto.

Se devi chiamare init () per mettere l'oggetto in uno stato coerente è un odore di codice.

Esistono diversi motivi tecnici per cui esiste una struttura del genere:

  1. gancio quadro
  2. reimpostazione dell'oggetto allo stato iniziale (evitare la ridondanza)
  3. possibilità di scavalcare durante i test

1
Ma perché un ingegnere del software dovrebbe integrarlo nel ciclo di vita? C'è uno scopo che sia giustificabile (considerando che incoraggia oggetti parzialmente costruiti) e che non potrebbe essere abbattuto da un'alternativa più efficiente? Sento che incorporarlo nel ciclo di vita sarebbe un odore di codice e che dovrebbe essere sostituito con una migliore tempistica della creazione di un oggetto (Perché allocare memoria per un oggetto se non si prevede di usarlo in quel momento? Perché creare un oggetto che è parzialmente costruito quando puoi aspettare fino a quando non hai effettivamente bisogno dell'oggetto prima di crearlo?)
Vince Emigh

Il punto è che l'oggetto deve essere utilizzabile PRIMA di chiamare il metodo init. Forse in un altro modo rispetto a quando è stato chiamato. Vedi modello di stato. Nel senso della costruzione parziale dell'oggetto è un odore di codice.
oopexpert,

-4

Il nome init a volte può essere opaco. Prendiamo una macchina e un motore. Per avviare l'auto (solo accendere, per ascoltare la radio) si desidera verificare che tutti i sistemi siano pronti per partire.

Quindi costruisci un motore, una porta, una ruota ecc. Lo schermo mostra engine = off.

Non è necessario avviare il monitoraggio del motore, ecc. Poiché sono tutti costosi. Quindi quando si gira la chiave per accendere si chiama engine-> start. Inizia a eseguire tutti i processi costosi.

Ora vedi engine = on. E inizia il processo di accensione.

L'auto non si accenderà senza il motore disponibile.

Puoi sostituire il motore con i tuoi calcoli complessi. Come una cella di Excel. Non tutte le celle devono essere sempre attive con tutti i gestori di eventi. Quando ti concentri su una cella, puoi avviarla. Aumentare le prestazioni in questo modo.

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.