Os métodos init () têm cheiro de código?


20

Existe algum propósito para declarar um init()método para um tipo?

Não estou perguntando se devemos preferir init()a um construtor ou como evitar a declaraçãoinit() .

Estou perguntando se existe alguma justificativa por trás da declaração de um init()método (ver quão comum é) ou se é um cheiro de código e deve ser evitado.


O init()idioma é bastante comum, mas ainda não vi nenhum benefício real.

Estou falando de tipos que incentivam a inicialização por meio de um método:

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

Quando isso será útil no código de produção?


Eu sinto que pode ser um cheiro de código, pois sugere que o construtor não inicializa totalmente o objeto, resultando em um objeto parcialmente criado. O objeto não deve existir se seu estado não estiver definido.

Isso me faz acreditar que pode fazer parte de algum tipo de técnica usada para acelerar a produção, no sentido de aplicativos corporativos. É a única razão lógica pela qual consigo pensar em ter esse idioma. Só não tenho certeza de como isso seria benéfico.


1
Para "... ver como é comum ...": é comum? Voce pode dar alguns exemplos? Talvez você esteja lidando com uma estrutura que requer separação entre inicialização e construção.
Vem

O método foi encontrado em uma classe base ou em uma classe derivada ou em ambas? (Ou: o método é encontrado em uma classe que pertence a uma hierarquia de herança? A classe base chama a init()classe derivada ou vice-versa?) Em caso afirmativo, é um exemplo de deixar a classe base executar um "pós-construtor" "que só pode ser executado após a construção da classe mais derivada. É um exemplo de inicialização multifásica.
Rwong 31/10/16

Se você não deseja inicializar no momento da instanciação, faria sentido separar os dois.
31416


Respostas:


39

Sim, é um cheiro de código. Um cheiro de código não é algo que necessariamente sempre precise ser removido. É algo que faz você dar uma segunda olhada.

Aqui você tem um objeto em dois estados fundamentalmente diferentes: pré-inicialização e pós-inicialização. Esses estados têm responsabilidades diferentes, métodos diferentes que podem ser chamados e comportamento diferente. São efetivamente duas classes diferentes.

Se você criar fisicamente duas classes separadas, removerá estaticamente toda uma classe de possíveis bugs, ao custo de talvez fazer com que seu modelo não corresponda tanto ao "modelo do mundo real". Você costuma nomear o primeiro Configou Setupou algo parecido.

Portanto, da próxima vez, tente refatorar seus idiomas de construção-init em modelos de duas classes e veja como isso acontece para você.


6
Sua sugestão para experimentar o modelo de duas classes é boa. Propor uma medida concreta para resolver um cheiro de código é útil.
Ivan

1
Esta é a melhor resposta até agora. Porém, uma coisa me incomoda: " Esses estados têm responsabilidades diferentes, métodos diferentes que podem ser chamados e comportamento diferente " - Não separar essas responsabilidades viola o SRP. Se o objetivo do software era replicar um cenário do mundo real em todos os aspectos, isso faria sentido. Mas, na produção, desenvolvedores de código principalmente gravação que incentiva fácil gerenciamento, revisão do modelo, se necessário para melhor atender a enviornment baseada em software (continua na próxima comentário)
Vince Emigh

1
O mundo real é um ambiente diferente. Os programas que tentam replicá-lo parecem específicos do domínio, pois você não deveria / não deveria explicar isso na maioria dos projetos. Muitas coisas que são desaprovadas são aceitas quando se trata de projetos específicos de domínio. Por isso, estou tentando manter isso o mais geral possível (por exemplo, como chamar thiso construtor é um cheiro de código e pode resultar em código sujeito a erros, e evitá-lo é recomendado, independentemente do domínio em que seu projeto se encaixa).
Vince Emigh

14

Depende.

Um initmétodo é um cheiro de código quando não é necessário separar a inicialização do objeto do construtor. Às vezes, há casos em que faz sentido separar essas etapas.

Uma rápida pesquisa no Google me deu esse exemplo. Posso facilmente imaginar mais casos em que o código executado durante a alocação de objetos (o construtor) pode ser melhor separado da própria inicialização. Talvez você tenha um sistema nivelado e a alocação / construção ocorra no nível X, mas a inicialização apenas no nível Y, porque apenas Y pode fornecer os parâmetros necessários. Talvez o "init" seja caro e precise ser executado apenas para um subconjunto dos objetos alocados, e a determinação desse subconjunto só pode ser feita no nível Y. Ou você deseja substituir o método "init" (virtual) em um derivado classe que não pode ser feita com um construtor. Talvez o nível X forneça objetos alocados de uma árvore de herança, mas o nível Y não tenha conhecimento da derivação concreta, apenas sobre a interface comum (ondeinit talvez definido).

Obviamente, para minha experiência, esses casos são apenas uma pequena porcentagem do caso padrão, onde toda inicialização pode ser feita diretamente no construtor e, sempre que você initvir um método separado , pode ser uma boa idéia questionar sua necessidade.


2
A resposta nesse link diz que é útil para reduzir a quantidade de trabalho no construtor, mas incentiva objetos parcialmente criados. Construtores menores podem ser alcançados através da decomposição, então para mim parece que as respostas criam um novo problema (o potencial de esquecer de chamar todos os métodos de inicialização necessários, deixando o objeto propenso a erros) e se enquadra na categoria de cheiro de código
Vince Emigh

@VinceEmigh: ok, foi o primeiro exemplo que eu poderia encontrar aqui no platfform SE, talvez não o melhor, mas não são casos de uso legítimo para um separar initmétodo. No entanto, sempre que vir esse método, fique à vontade para questionar sua necessidade.
Doc Brown

Estou questionando / desafiando todos os casos de uso, pois sinto que não há situação em que isso seja necessário. Para mim, é um tempo ruim para a criação de objetos e deve ser evitado, pois é um candidato a erros que podem ser evitados através do design adequado. Se houvesse um uso adequado de um init()método, tenho certeza de que me beneficiaria de aprender sobre seu objetivo. Desculpem a minha ignorância, eu só estou surpreendido com o quão difícil de um tempo que estou tendo de encontrar um uso para ele, impedindo-me de considerá-la algo que deve ser evitado
Vince Emigh

1
@VinceEmigh: quando você não consegue pensar em tal situação, precisa trabalhar sua imaginação ;-). Ou leia minha resposta novamente, não a reduza apenas para "alocação". Ou trabalhe com mais estruturas de diferentes fornecedores.
Doc Brown

1
@DocBrown O uso da imaginação em vez de práticas comprovadas leva a algum código descolado, como a inicialização com chaves duplas: inteligente, mas ineficiente e deve ser evitado. Eu sei que os fornecedores usam, mas isso não significa que o uso seja justificado. Se você acha que é, por favor, deixe-me saber o porquê, pois é isso que eu tenho tentado descobrir. Você parecia estar determinado a ter ALGUM propósito benéfico, mas parece que você está tendo dificuldades para dar um exemplo que incentive o bom design. Eu sei sob quais circunstâncias ele poderia ser usado, mas deveria ser usado?
Vince Emigh

5

Minha experiência se divide em dois grupos:

  1. Código em que init () é realmente necessário. Isso pode ocorrer quando uma superclasse ou estrutura impede o construtor de sua classe de obter todas as suas dependências durante a construção.
  2. Código em que init () é usado, mas poderia ter sido evitado.

Na minha experiência pessoal, vi apenas algumas instâncias de (1), mas muitas outras instâncias de (2). Consequentemente, eu normalmente assumo que init () é um cheiro de código, mas nem sempre é esse o caso. Às vezes você simplesmente não consegue contornar isso.

Descobri que o uso do Padrão do Construtor geralmente pode ajudar a remover a necessidade / desejo de ter um init ().


1
Se uma superclasse ou estrutura não permitir que um tipo obtenha as dependências necessárias através do construtor, como a adição de um init()método a resolveria? O init()método seria nem precisa parâmetros para aceitar as dependências, ou você teria que instanciar as dependências dentro do init()método, que você também poderia fazer com um construtor. Você poderia dar um exemplo?
Vince Emigh

1
@VinceEmigh: init () às vezes pode ser usado para carregar um arquivo de configuração de uma fonte externa, abrir uma conexão com o banco de dados ou algo desse tipo. O método DoFn.initialize () (da estrutura apache Crunch) é usado dessa maneira. Também pode ser usado para carregar campos internos não serializáveis ​​(os DoFns devem ser serializáveis). Os dois problemas aqui são que (1) algo precisa garantir que o método de inicialização seja chamado e (2) o objeto precisa saber onde ele irá obter (ou como ele irá criar) esses recursos.
Ivan

1

Um cenário típico quando um método Init é útil é quando você tem um arquivo de configuração que deseja alterar e que a alteração seja levada em consideração sem reiniciar o aplicativo. Obviamente, isso não significa que um método Init deva ser chamado separadamente de um construtor. Você pode chamar um método Init de um construtor e depois mais tarde quando / se os parâmetros de configuração mudarem.

Resumindo: quanto à maioria dos dilemas existentes, seja um cheiro de código ou não, depende da situação e das circunstâncias.


Se as atualizações de configuração, e isso requer o objeto para redefinir / mudá-lo de estado com base na configuração, você não acha que seria melhor ter o ato objeto como um observador em direção Config?
Vince Emigh

@Vince Emigh Não é necessário. O observador funcionaria se eu souber o momento exato em que a configuração muda. No entanto, se os dados de configuração forem mantidos em um arquivo que possa ser alterado fora de um aplicativo, não haverá uma abordagem realmente elegante. Por exemplo, se eu tiver um programa que analise alguns arquivos e transforme os dados em algum modelo interno, e um arquivo de configuração separado contenha valores padrão para os dados ausentes, se eu alterar os valores padrão, os lerei novamente na próxima execução a análise. Ter um método Init no meu aplicativo seria bastante útil nesse caso.
Vladimir Stokic

Se o arquivo de configuração for modificado externamente em tempo de execução, não há como recarregar essas variáveis ​​sem algum tipo de notificação informando seu aplicativo que ele precisa chamar init ( update/ reloadprovavelmente seria mais descritivo para esse tipo de comportamento) para realmente registrar essas alterações . Nesse caso, essa notificação faria com que os valores da configuração fossem alterados internamente em seu aplicativo, o que eu acredito que poderia ser observado fazendo com que sua configuração fosse observável, notificando os observadores quando a configuração é solicitada a alterar um de seus valores. Ou estou entendendo mal o seu exemplo?
Vince Emigh

Um aplicativo pega alguns arquivos externos (exportados de algum GIS ou outro sistema), analisa esses arquivos e os transforma em algum modelo interno que os sistemas dos quais o aplicativo faz parte. Durante esse processo, algumas lacunas de dados podem ser encontradas e essas lacunas de dados podem ser preenchidas padronizando os valores. Esses valores padrão podem ser armazenados no arquivo de configuração que pode ser lido no início do processo de transformação, que é chamado sempre que houver novos arquivos a serem transformados. É aí que um método Init pode ser útil.
Vladimir Stokic 02/11/19

1

Depende de como você os usa.

Eu uso esse padrão em linguagens de coleta de lixo como Java / C # quando não quero realocar um objeto na pilha (como quando estou criando um videogame e preciso manter o desempenho alto, os coletores de lixo diminuem o desempenho). Eu uso o construtor para fazer outras alocações de heap necessárias e initcriar o estado útil básico antes de cada vez que eu quiser reutilizá-lo. Isso está relacionado ao conceito de conjuntos de objetos.

Também é útil se você tiver vários construtores que compartilhem um subconjunto comum de instruções de inicialização, mas nesse caso initserão privados. Dessa forma, posso minimizar cada construtor o máximo possível, para que cada um contenha apenas instruções exclusivas e uma única chamada initpara fazer o resto.

Em geral, porém, é um cheiro de código.


Um reset()método não seria mais descritivo para sua primeira declaração? Quanto ao segundo (muitos construtores), ter muitos construtores é um cheiro de código. Ele assume que o objeto tem vários propósitos / responsabilidades, sugerindo uma violação do SRP. Um objeto deve ter uma responsabilidade e o construtor deve definir as dependências necessárias para essa responsabilidade. Se você tiver vários construtores devido a valores opcionais, eles devem fazer o telescópio (que também é um cheiro de código, deve-se usar um construtor).
perfil completo de Vince Emigh

@VinceEmigh você pode usar init ou reset, afinal é apenas um nome. O Init faz mais sentido no contexto em que costumo usá-lo, pois faz pouco sentido redefinir um objeto que nunca foi definido pela primeira vez. Quanto à questão do construtor, tento evitar muitos construtores, mas ocasionalmente é útil. Veja a stringlista de construtores de qualquer idioma , com várias opções. Para mim, geralmente são talvez três construtores no máximo, mas o subconjunto comum de instruções como inicialização faz sentido quando eles compartilham qualquer código, mas diferem de alguma forma.
Cody

Os nomes são extremamente importantes. Eles descrevem o comportamento que está sendo executado sem forçar o cliente a ler o contrato. Uma nomeação incorreta pode levar a falsas suposições. Quanto a vários construtores String, isso pode ser resolvido dissociando a criação de strings. No final, a Stringé a String, e seu construtor deve aceitar apenas o necessário para que ele funcione conforme necessário. A maioria desses construtores é exposta para fins de conversão, que é o uso indevido de construtores. Os construtores não devem executar lógica, ou eles correm o risco de falha na inicialização, deixando você com um objeto inútil.
Vince Emigh

O JDK está cheio de designs horríveis, eu poderia listar cerca de 10 em cima da minha cabeça. O design do software evoluiu desde que os aspectos principais de muitas linguagens foram expostos publicamente, e eles perduram devido à possibilidade de quebrar o código, caso ele fosse redesenhado para os tempos modernos.
Vince Emigh

1

init()Os métodos podem fazer bastante sentido quando você possui objetos que precisam de recursos externos (como, por exemplo, uma conexão de rede) que são usados ​​simultaneamente por outros objetos. Você pode não querer / precisar monopolizar o recurso durante a vida útil do objeto. Nessas situações, talvez você não queira alocar o recurso no construtor quando a alocação de recursos provavelmente falhar.

Especialmente na programação incorporada, você deseja ter uma pegada de memória determinística, por isso é uma prática comum (boa?) Chamar seus construtores mais cedo, talvez até estáticos, e somente inicializar mais tarde quando certas condições forem atendidas.

Fora desses casos, acho que tudo deve entrar em um construtor.


Se o tipo exigir uma conexão, você deve usar DI. Se houver um problema ao criar a conexão, você não deve criar o objeto que a exige. Se você empurrar a criação de uma conexão dentro da classe, criará um objeto, que instanciará suas dependências (a conexão). Se a instanciação de dependências falhar, você acaba com um objeto que não pode usar, desperdiçando recursos.
Vince Emigh

Não necessariamente. Você acaba com um objeto que você temporariamente não pode usar para todos os fins. Nesses casos, o objeto pode apenas agir como uma fila ou proxy até que o recurso fique disponível. initMétodos totalmente condenadores é muito restritivo, IMHO.
tofro

0

Geralmente, prefiro um construtor que recebe todos os argumentos necessários para uma instância funcional. Isso deixa claro todas as dependências desse objeto.

Por outro lado, uso uma estrutura de configuração simples que requer um construtor público sem parâmetros e interfaces para injetar dependências e valores de configuração. Depois disso, a estrutura de configuração chama o initmétodo do objeto: agora você recebeu tudo o que tenho para você, execute as últimas etapas para se preparar para o trabalho. Mas observe: é a estrutura de configuração que chama automaticamente o método init, para que você não esqueça de chamá-lo.


0

Não há cheiro de código se o método init () - é semanticamente incorporado no ciclo de vida do objeto.

Se você precisar chamar init () para colocar o objeto em um estado consistente, é um cheiro de código.

Existem várias razões técnicas para que essa estrutura exista:

  1. gancho de estrutura
  2. redefinindo o objeto para o estado inicial (evite redundância)
  3. possibilidade de substituir durante os testes

1
Mas por que um engenheiro de software o incorporaria no ciclo de vida? Existe algum objetivo que seja justificável (considerando que incentiva objetos parcialmente construídos) e não possa ser derrubado por uma alternativa mais eficiente? Eu sinto que incorporá-lo no ciclo de vida seria um cheiro de código e que ele deveria ser substituído por um melhor tempo de criação do objeto (por que alocar memória para um objeto se você não planeja usá-lo naquele momento? objeto que está parcialmente construído quando você pode simplesmente esperar até que você realmente precisa do objeto antes de criá-la)?
Vince Emigh

O ponto é que o objeto precisa ser utilizável ANTES de você chamar o método init. Talvez de outra maneira do que depois que foi chamado. Veja padrão de estado. No sentido de construção parcial de objetos, é um cheiro de código.
oopexpert

-4

O nome init às vezes pode ser opaco. Vamos pegar um carro e um motor. Para dar partida no carro (apenas ligar, ouvir rádio), você deseja verificar se todos os sistemas estão prontos para o uso.

Então você constrói um motor, uma porta, uma roda, etc. sua tela mostra motor = desligado.

Não há necessidade de começar a monitorar o motor etc., pois todos são caros. Então, quando você liga a chave para ligar, ligue para engine-> start. Começa a executar todos os processos caros.

Agora você vê engine = on. E o processo de ignição começa.

O carro não liga sem o motor disponível.

Você pode substituir o mecanismo pelo seu cálculo complexo. Como uma célula do Excel. Nem todas as células precisam estar ativas com todos os manipuladores de eventos o tempo todo. Quando você se concentra em uma célula, pode iniciá-la. Aumentando o desempenho dessa maneira.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.