Existe alguma razão para fazer todo o trabalho de um objeto em um construtor?

48

Deixe-me começar dizendo que esse não é o código nem o código dos meus colegas de trabalho. Anos atrás, quando nossa empresa era menor, tínhamos alguns projetos que precisávamos que não tivéssemos capacidade, então eles foram terceirizados. Agora, não tenho nada contra a terceirização ou contratados em geral, mas a base de código que eles produziram é uma massa de WTFs. Dito isto, ele (na maioria das vezes) funciona, então, suponho que esteja entre os 10% dos projetos terceirizados que já vi.

Como nossa empresa cresceu, tentamos levar mais de nosso desenvolvimento internamente. Este projeto em particular pousou no meu colo, então eu passei por cima, limpando, adicionando testes, etc.

Há um padrão que eu vejo repetido muito e parece tão horripilantemente horrível que eu me perguntei se talvez houvesse uma razão e eu simplesmente não a vejo. O padrão é um objeto sem métodos ou membros públicos, apenas um construtor público que faz todo o trabalho do objeto.

Por exemplo, (o código está em Java, se isso é importante, mas espero que seja uma questão mais geral):

public class Foo {
  private int bar;
  private String baz;

  public Foo(File f) {
    execute(f);
  }

  private void execute(File f) {
     // FTP the file to some hardcoded location, 
     // or parse the file and commit to the database, or whatever
  }
}

Se você está se perguntando, esse tipo de código costuma ser chamado da seguinte maneira:

for(File f : someListOfFiles) {
   new Foo(f);
}

Agora, eu aprendi há muito tempo que os objetos instanciados em um loop geralmente são uma má idéia e que os construtores devem fazer um mínimo de trabalho. Observando esse código, parece que seria melhor descartar o construtor e tornar execute um método estático público.

Eu perguntei ao contratante por que isso foi feito dessa forma, e a resposta que recebi foi "Podemos mudá-lo se você quiser". O que não ajudou muito.

De qualquer forma, existe alguma razão para fazer algo assim, em qualquer linguagem de programação, ou esta é apenas mais uma submissão ao Daily WTF?

    
por Kane 02.08.2012 / 15:36
fonte

12 respostas

59

Ok, indo na lista:

Eu aprendi há muito tempo que objetos instanciados em um loop geralmente são uma má idéia

Não em nenhum idioma que usei.

Em C é uma boa idéia declarar suas variáveis na frente, mas isso é diferente do que você disse. Pode ser ligeiramente mais rápido se você declarar objetos acima do loop e reutilizá-los, mas há muitos idiomas em que esse aumento de velocidade não terá sentido (e provavelmente alguns compiladores que fazem a otimização para você :)).

Em geral, se você precisar de um objeto dentro de um loop, crie um.

os construtores devem fazer um mínimo de trabalho

Os construtores devem instanciar os campos de um objeto e fazer qualquer outra inicialização necessária para tornar o objeto pronto para uso. Geralmente, isso significa que os construtores são pequenos, mas há cenários em que isso seria uma quantidade substancial de trabalho.

existe alguma razão para fazer algo assim, em qualquer linguagem de programação, ou esta é apenas mais uma submissão ao Daily WTF?

É uma submissão ao Daily WTF. Concedido, existem coisas piores que você pode fazer com o código. O problema é que o autor tem um grande mal-entendido sobre o que são classes e como usá-las. Especificamente, aqui está o que vejo que está errado com este código:

  • Uso indevido de classes: A classe está basicamente agindo como uma função. Como você mencionou, ele deve ser substituído por uma função estática ou a função deve ser implementada apenas na classe que está chamando. Depende do que faz e onde é usado.
  • Sobrecarga de desempenho: dependendo do idioma, a criação de um objeto pode ser mais lenta do que chamar uma função.
  • Confusão geral: geralmente é confuso para o programador como usar este código. Sem vê-lo usado, ninguém saberia como o autor pretendia usar o código em questão.
por 02.08.2012 / 15:49
fonte
26

Para mim, é um pouco surpreendente quando você faz um objeto fazer muito trabalho no construtor, então apenas eu recomendaria contra ele (princípio da menor surpresa).

Uma tentativa de um bom exemplo :

Eu não tenho certeza se esse uso é um anti-padrão, mas em C # eu vi o código que foi ao longo das linhas de (um pouco inventado exemplo):

using (ImpersonationContext administrativeContext = new ImpersonationContext(ADMIN_USER))
{
    // Perform actions here under the administrator's security context
}
// We're back to "normal" privileges here

Nesse caso, a classe ImpersonationContext faz todo o seu trabalho no construtor e no método Dispose. Ele tira proveito da instrução C # using , que é bem compreendida pelos desenvolvedores de C # e, portanto, garante que a configuração que ocorre no construtor seja revertida quando estivermos fora da instrução using , mesmo se uma exceção ocorre. Este é provavelmente o melhor uso que eu já vi para isso (por exemplo, "trabalho" no construtor), embora eu ainda não tenha certeza se o consideraria "bom". Neste caso, é bastante autoexplicativo, funcional e sucinto.

Um exemplo de um padrão bom e semelhante seria o padrão de comando . Você definitivamente não executa a lógica no construtor, mas é semelhante no sentido de que toda a classe é equivalente a uma chamada de método (incluindo os parâmetros necessários para invocar o método). Dessa forma, as informações de invocação de método podem ser serializadas ou passadas para uso posterior, o que é imensamente útil. Talvez seus empreiteiros estivessem tentando algo semelhante, embora eu duvide muito disso.

Comentários sobre o seu exemplo:

Eu diria que é um anti-padrão muito definido. Ele não adiciona nada, vai contra o que é esperado e realiza processamento excessivamente longo no construtor (FTP no construtor? WTF, na verdade, embora eu suponha que as pessoas são conhecidas por chamar os serviços da Web dessa maneira).

Estou lutando para encontrar a citação, mas o livro de Grady Booch "Object Solutions" tem uma anedota sobre uma equipe de desenvolvedores de C em transição para C ++. Aparentemente eles tinham passado por treinamento, e foram informados pela administração que eles absolutamente tinham que fazer coisas "orientadas a objetos". Trazido para descobrir o que está errado, o autor usou uma ferramenta de métrica de código e descobriu que o número médio de métodos por classe era exatamente 1, e eram variações da frase "Do It". Devo dizer que nunca encontrei esse problema antes, mas aparentemente pode ser um sintoma de pessoas sendo forçadas a criar código orientado a objetos, sem realmente entender como fazê-lo e por quê.

    
por 02.08.2012 / 16:05
fonte
13

Não.

Se todo o trabalho é feito no construtor, significa que o objeto nunca foi necessário em primeiro lugar. Muito provavelmente, o empreiteiro estava apenas usando o objeto para modularidade. Nesse caso, uma classe não instanciada com um método estático teria obtido o mesmo benefício sem criar nenhuma instância de objeto supérflua.

Existe apenas um caso em que fazer todo o trabalho em um construtor de objetos é aceitável. É quando a finalidade do objeto é especificamente representar uma identidade única de longo prazo, em vez de executar o trabalho. Nesse caso, o objeto seria mantido por outras razões além de realizar um trabalho significativo. Por exemplo, uma instância singleton.

    
por 02.08.2012 / 19:32
fonte
6

Eu certamente criei muitas classes que fazem muito trabalho para calcular algo no construtor, mas o objeto é imutável, portanto, ele disponibiliza os resultados dessa computação por meio de propriedades e, em seguida, nunca faz mais nada. Isso é imutabilidade normal em ação.

Acho que o mais estranho aqui é colocar o código com efeitos colaterais em um construtor. Construir um objeto e jogá-lo fora sem acessar as propriedades ou métodos parece estranho (mas, pelo menos neste caso, é bastante óbvio que algo está acontecendo).

Isso seria melhor se a função fosse movida para um método público e, em seguida, tivesse uma instância da classe injetada e, em seguida, fizesse o loop for chamar o método repetidamente.

    
por 02.08.2012 / 21:09
fonte
3

Is there ever a reason to do all an object's work in a constructor?

Não.

  • Um construtor não deve ter efeitos colaterais.
    • Qualquer coisa além da inicialização do campo privado deve ser vista como um efeito colateral.
    • Um construtor com efeitos colaterais quebra o Princípio de Responsabilidade Única (Single-Responsibility-Principle - SRP) e é contrário ao espírito da programação orientada a objetos (OOP).
  • Um construtor deve ser leve e nunca deve falhar.
    • Por exemplo, eu sempre estremeço quando vejo um bloco try-catch dentro de um construtor. Um construtor não deve lançar exceções ou erros de log.

Alguém poderia razoavelmente questionar essas diretrizes e dizer: "Mas eu não sigo essas regras e meu código funciona bem!" Para isso, eu respondia: "Bem, isso pode ser verdade, até que não seja".

  • Exceções e erros dentro de um construtor são muito inesperados. A menos que eles sejam instruídos a fazê-lo, futuros programadores não estarão inclinados a cercar essas chamadas de construtor com código defensivo.
  • Se algo falhar na produção, o rastreamento de pilha gerado pode ser difícil de analisar. A parte superior do rastreamento de pilha pode apontar para a chamada de construtor, mas muitas coisas acontecem no construtor e pode não apontar para o LOC que falhou.
    • Eu analisei muitos rastreamentos de pilha do .NET onde esse era o caso.
por 09.09.2012 / 17:13
fonte
2

Parece-me que a pessoa que está fazendo isso está tentando descobrir a limitação do Java que não permite passar funções como cidadãos de primeira classe. Sempre que você tem que passar uma função, você tem que envolvê-la dentro de uma classe com um método como apply ou similar.

Então, eu acho que ele usou apenas um atalho e usou diretamente uma classe como substituto de função. Sempre que você quiser executar a função, instancie a classe.

Definitivamente não é um bom padrão, pelo menos porque estamos aqui tentando descobrir isso, mas acho que pode ser a maneira menos detalhada de fazer algo semelhante à programação funcional em Java.

    
por 02.08.2012 / 16:52
fonte
1

Para mim, a regra geral é não fazer nada no construtor, exceto pela inicialização de variáveis de membro. A primeira razão para isso é que ele ajuda a seguir o princípio do SRP, pois normalmente um longo processo de inicialização é uma indicação de que a classe faz mais do que deveria, e a inicialização deve ser feita em algum lugar fora da classe. A segunda razão é que desta forma apenas os parâmetros necessários são passados, então você cria um código menos acoplado. Um construtor com inicialização complicada geralmente precisa de parâmetros que são usados apenas para construir outro objeto, mas que não são usados pela classe original.

    
por 02.08.2012 / 17:03
fonte
1

Eu vou contra a maré aqui - em idiomas onde tudo tem que estar em alguma classe E você não pode ter uma classe estática, você pode correr para a situação onde você tem um pouco de funcionalidade isolada que precisa ser colocado em algum lugar e não se encaixa com mais nada. Suas escolhas são uma classe de utilitário que abrange vários bits não relacionados de funcionalidade, ou uma classe que basicamente faz exatamente isso.

Se você tem apenas um bit como esse, sua classe estática é sua classe específica. Então, ou você tem um construtor que faz todo o trabalho, ou uma única função que faz todo o trabalho. A vantagem de fazer tudo no construtor é que isso acontece apenas uma vez, ele permite que você use campos, propriedades e métodos privados sem se preocupar com a reutilização ou o encadeamento deles. Se você tiver um construtor / método, você pode ser tentado a deixar os parâmetros serem passados para o método único, mas se você usar campos privados ou propriedades que introduzem problemas potenciais de encadeamento.

Mesmo com uma classe de utilitário, a maioria das pessoas não pensa em ter classes estáticas aninhadas (e isso pode não ser possível, dependendo do idioma).

Basicamente, essa é uma maneira relativamente compreensível de isolar o comportamento do resto do sistema, dentro do que é permitido pela linguagem, enquanto ainda permite que você aproveite o máximo possível da linguagem.

Sinceramente, eu teria respondido tanto quanto o seu contratado, é um pequeno ajuste para transformá-lo em duas chamadas, não há muito para recomendar uma sobre a outra. Quais são as desvantagens que existem, são provavelmente mais hipotéticas do que reais, por que tentar justificar a decisão quando isso não importa e provavelmente não foi tão decidido como apenas a ação padrão?

    
por 09.09.2012 / 21:37
fonte
1

Existem padrões comuns em linguagens onde funções e classes são muito mais a mesma coisa, como Python, onde um objeto é basicamente usado para funcionar com muitos efeitos colaterais ou com vários objetos nomeados retornados.

Nesse caso, o volume, se não todo o trabalho, pode ser feito no construtor, já que o objeto em si é apenas um wrapper em torno de um único pacote de trabalho, e não há uma boa razão para colocá-lo em um separado função. Algo como

var parser = XMLParser()
var x = parser.parse(string)
string a = x.LookupTag(s)

não é melhor que

 var x = ParsedXML(string)
 string a = x.LookupTag(s)

Em vez de ter diretamente os efeitos colaterais, você pode chamar o objeto para impor o efeito colateral na sugestão, o que lhe dá melhor visibilidade. Se eu vou ter um efeito colateral ruim em um objeto, eu posso fazer todo o meu trabalho e então passar o objeto que eu vou fazer mal e fazer o meu dano a ele. Identificar o objeto e isolar a chamada dessa maneira é mais claro do que passar vários desses objetos para uma função de uma só vez.

x.UpdateView(v)

Você pode fazer os vários valores de retorno por meio dos acessadores no objeto. Todo o trabalho está feito, mas eu quero o material todo em contexto, e eu realmente não quero passar múltiplas referências para minha função.

var x = new ComputeStatistics(inputFile)
int max = x.MaxOptions;
int mean = x.AverageOption;
int sd = x.StandardDeviation;
int k = x.Kurtosis;
...

Então, como um programador comum em Python / C #, isso não parece estranho para mim.

    
por 25.09.2014 / 19:46
fonte
1

Um caso vem à mente onde o construtor / destruidor faz todo o trabalho:

Bloqueios. Você normalmente nunca interage com um cadeado durante sua vida útil. O uso de arquitetura pelo C # é bom para lidar com tais casos sem se referir explicitamente ao destruidor. Nem todos os bloqueios são implementados dessa maneira.

Eu também escrevi o que equivalia a um pouco de código somente para o construtor para corrigir um bug da biblioteca de tempo de execução. (Na verdade, corrigir o código teria causado outros problemas.) Não havia nenhum destruidor porque não havia necessidade de desfazer o patch.

    
por 26.09.2014 / 01:25
fonte
-1

Eu concordo com o AndyBursh. Parece alguma falta de conhecimento.

No entanto, é importante mencionar frameworks como o NHibernate, que requerem apenas o código no construtor (ao criar classes de mapa). No entanto, isso não é uma limitação do framework, é exatamente o que você precisa. Quando se trata de reflexão, o construtor é o único método que sempre existirá (embora possa ser privado) e isso pode ser útil se você tiver que chamar um método de uma classe dinamicamente.

    
por 03.08.2012 / 01:16
fonte
-4

"I was taught long ago that instantiated objects in a loop is generally a bad idea"

Sim, você pode estar lembrando porque precisamos do StringBuilder.

Existem duas escolas de Java.

O construtor foi promovido pelo primeiro , que nos ensina a reutilizar (porque o compartilhamento = identidade à eficiência). No entanto, no início de 2000, comecei a ler que criar objetos em Java é tão barato (em oposição a C ++) e GC é tão eficiente que a reutilização é inútil e a única coisa que importa é a produtividade do programador. Para produtividade, ele deve escrever código gerenciável, o que significa modularização (também conhecido como estreitar o escopo). Então,

isso é ruim

byte[] array = new byte[kilos or megas]
for_loop:
  use(array)

isso é bom.

for_loop:
  byte[] array = new byte[kilos or megas]
  use(array)

Eu fiz esta pergunta quando forum.java.sun existia, e as pessoas concordaram que prefeririam declarar o array o mais estreito possível.

O programador java moderno nunca hesita em declarar uma nova classe ou criar um objeto. Ele é encorajado a fazer isso. Java dá todas as razões para fazê-lo, com a ideia de que "tudo é um objeto" e criação barata.

Além disso, o estilo de programação empresarial moderno é que, quando você precisa executar uma ação, você cria uma classe especial (ou melhor ainda, uma estrutura) que executará essa ação simples. Bons IDEs Java, que ajudam aqui. Eles geram toneladas de lixo automaticamente, como respirar.

Então, acho que seus objetos não têm o padrão Builder ou Factory. Não é decente criar instâncias diretamente, por construtor. Uma classe de fábrica especial para todos os seus objetos é recomendada.

Agora, quando a programação funcional está em jogo, criar objetos se torna ainda mais simples. Você vai criar objetos a cada passo como respirar, sem nem perceber isso. Então, espero que seu código se torne ainda mais "eficiente" na nova era

"do not do anything in your constructor. It is for initialization only"

Pessoalmente, não vejo razão na diretriz. Eu considero isso apenas um outro método. O código de fatoração é necessário apenas quando você pode compartilhá-lo.

    
por 09.09.2012 / 11:14
fonte