Permitam-me que antecipe dizendo que este não é o meu 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 realizar e que não tínhamos capacidade para, portanto eles eram terceirizados. Agora, não tenho nada contra terceirização ou contratados em geral, mas a base de código que eles produziram é uma massa de WTFs. Dito isto, ele funciona (principalmente), então suponho que esteja entre os 10% dos projetos terceirizados que já vi.
À medida que nossa empresa cresce, tentamos levar mais do nosso desenvolvimento internamente. Esse projeto em particular caiu no meu colo, então eu o reparei, limpei, adicionei testes, etc.
Há um padrão que vejo repetido muito e parece tão terrivelmente horrível que me perguntei se talvez houvesse um motivo e simplesmente não o vi. 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 importa, mas espero que seja uma pergunta 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 geralmente é chamado da seguinte maneira:
for(File f : someListOfFiles) {
new Foo(f);
}
Agora, fui ensinado há muito tempo que os objetos instanciados em um loop geralmente são uma má idéia e que os construtores devem fazer um trabalho mínimo. Olhando para este código, parece que seria melhor descartar o construtor e criar execute
um método público estático.
Perguntei ao empreiteiro por que isso foi feito dessa maneira, e a resposta que tive foi "Podemos mudar, se você quiser". O que não foi realmente útil.
De qualquer forma, existe alguma razão para fazer algo assim, em qualquer linguagem de programação, ou é apenas mais uma submissão ao Daily WTF?
public static void main(string[] args)
e ouviram falar de objetos e depois tentaram misturá-los.Respostas:
Ok, descendo a lista:
Fui ensinado há muito tempo que objetos instanciados em um loop geralmente são uma má ideia
Não em nenhum idioma que eu usei.
Em C, é uma boa ideia declarar suas variáveis antecipadamente, mas isso é diferente do que você disse. Pode ser um pouco mais rápido se você declarar objetos acima do loop e reutilizá-los, mas há muitas linguagens em que esse aumento na velocidade não faz sentido (e provavelmente alguns compiladores por aí que fazem a otimização para você :)).
Em geral, se você precisar de um objeto em um loop, crie um.
construtores devem fazer um trabalho mínimo
Os construtores devem instanciar os campos de um objeto e fazer qualquer outra inicialização necessária para tornar o objeto pronto para uso. Isso geralmente 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 isso é apenas mais uma submissão ao Daily WTF?
É uma submissão ao Daily WTF. É verdade que existem coisas piores que você pode fazer com o código. O problema é que o autor tem um vasto mal-entendido sobre o que são classes e como usá-las. Especificamente, aqui está o que vejo de errado com este código:
fonte
Para mim, é uma surpresa quando você obtém um objeto que faz muito trabalho no construtor, por isso recomendo que não seja recomendado (princípio da menor surpresa).
Uma tentativa de um bom exemplo :
Não tenho certeza se esse uso é um antipadrão, mas em C # eu vi código que estava ao longo das linhas de (exemplo um pouco inventado):
Nesse caso, a
ImpersonationContext
classe faz todo o seu trabalho no construtor e no método Dispose. Ele tira proveito dausing
instrução C # , que é bastante bem entendida pelos desenvolvedores de C # e, portanto, garante que a instalação que ocorre no construtor seja revertida quando estivermos fora dausing
instrução, mesmo que ocorra uma exceção. Este é provavelmente o melhor uso que eu já vi para isso (ou seja, "trabalho" no construtor), embora ainda não tenha certeza se consideraria "bom". Nesse caso, é bastante auto-explicativo, 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 invocação de método (incluindo os parâmetros necessários para invocar o método). Dessa maneira, as informações de chamada de método podem ser serializadas ou transmitidas para uso posterior, o que é imensamente útil. Talvez seus contratados estivessem tentando algo semelhante, embora eu duvide muito.
Comentários sobre o seu exemplo:
Eu diria que é um anti-padrão muito definido. Ele não acrescenta nada, contraria o esperado e executa um processamento excessivamente longo no construtor (FTP no construtor? WTF, de fato, embora eu ache que as pessoas chamam 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 haviam passado por treinamento e a gerência lhes disse que eles absolutamente tinham que fazer coisas "orientadas a objetos". Trazido para descobrir o que há de 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 específico antes, mas aparentemente pode ser um sintoma de pessoas serem forçadas a criar código orientado a objetos, sem realmente entender como fazê-lo e por quê.
fonte
Não.
Se todo o trabalho for realizado no construtor, significa que o objeto nunca foi necessário. Muito provavelmente, o contratado estava apenas usando o objeto para modularidade. Nesse caso, uma classe não instanciada com um método estático teria ganho o mesmo benefício sem criar nenhuma instância de objeto supérflua.
Há apenas um caso em que todo o trabalho em um construtor de objetos é aceitável. É quando o objetivo 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 da realização de um trabalho significativo. Por exemplo, uma instância singleton.
fonte
Certamente criei muitas classes que fazem muito trabalho para calcular algo no construtor, mas o objeto é imutável, por isso disponibiliza os resultados dessa computação por meio de propriedades e nunca mais faz nada. Isso é imutabilidade normal em ação.
Eu acho que o estranho aqui é colocar 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 nesse 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, injetada uma instância da classe, e o loop for chamasse o método repetidamente.
fonte
Não.
Alguém poderia razoavelmente questionar essas diretrizes e dizer: "Mas eu não sigo essas regras, e meu código funciona bem!" A isso, eu respondia: "Bem, isso pode ser verdade, até que não seja".
fonte
Parece-me que a pessoa que estava fazendo isso estava tentando burlar a limitação do Java que não permite passar funções como cidadãos de primeira classe. Sempre que você precisa passar uma função, é necessário envolvê-la em uma classe com um método semelhante
apply
ou semelhante.Então eu acho que ele apenas usou um atalho e usou diretamente uma classe como uma substituição de função. Sempre que você quiser executar a função, basta instanciar a classe.
Definitivamente, não é um bom padrão, pelo menos porque estamos aqui tentando descobrir, mas acho que pode ser a maneira menos detalhada de fazer algo semelhante à programação funcional em Java.
fonte
Para mim, a regra geral é não fazer nada no construtor, exceto para inicializar variáveis de membro. A primeira razão para isso é que ajuda a seguir o princípio do SRP, pois geralmente 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, dessa maneira, apenas os parâmetros necessários são passados, para que você crie menos código 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.
fonte
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, pode se deparar com a situação em que você tem um pouco isolado de funcionalidade que precisa ser coloque em algum lugar e não se encaixa em mais nada. Suas escolhas são uma classe de utilidade que cobre vários bits de funcionalidade não relacionados, ou uma classe que faz basicamente exatamente isso.
Se você tiver apenas um pouco assim, sua classe estática é sua classe específica. Portanto, 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, permitindo que você use campos particulares, propriedades e métodos sem se preocupar com a reutilização ou a segmentação. Se você possui um construtor / método, pode ficar tentado a permitir que os parâmetros sejam passados para o método único, mas se usar campos ou propriedades particulares que apresentem possíveis problemas de encadeamento.
Mesmo com uma classe de utilitário, a maioria das pessoas não pensa 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 pelo idioma, enquanto ainda permite que você aproveite o máximo possível do idioma.
Sinceramente, eu teria respondido da mesma forma que o contratado, é um pequeno ajuste para transformá-lo em duas chamadas, não há muito o que recomendar uma sobre a outra. Quais são as dis / advatanges, provavelmente mais hipotéticas do que reais, por que tentar justificar a decisão quando ela não importa e provavelmente não foi tão decidida quanto apenas a ação padrão?
fonte
Existem padrões comuns em linguagens em que funções e classes são muito mais a mesma coisa, como Python, onde se usa um objeto basicamente para funcionar com muitos efeitos colaterais ou com vários objetos nomeados retornados.
Nesse caso, o trabalho em massa, se não todo, pode ser feito no construtor, pois o próprio objeto é apenas um invólucro em torno de um único pacote de trabalho e não há um bom motivo para colocá-lo em uma função separada. Algo como
realmente não é melhor do que
Em vez de ter diretamente os efeitos colaterais diretamente, você pode chamar o objeto para aplicar o efeito colateral na sugestão, o que oferece uma melhor visibilidade. Se eu tiver um efeito colateral ruim em um objeto, posso fazer todo o meu trabalho e, em seguida, passar pelo objeto, afetarei mal e causarei danos a ele. Identificar o objeto e isolar a chamada dessa maneira é mais claro do que passar vários desses objetos para uma função ao mesmo tempo.
Você pode fazer os vários valores de retorno por meio de acessadores no objeto. Todo o trabalho está feito, mas quero tudo em contexto e não quero passar várias referências à minha função.
Portanto, como um programador conjunto de Python / C #, isso não parece tão estranho para mim.
fonte
Um caso vem à mente em que o construtor / destruidor faz todo o trabalho:
Fechaduras. Você normalmente nunca interage com um cadeado durante a vida inteira. O C # que usa arquitetura é bom para lidar com esses casos sem se referir explicitamente ao destruidor. Porém, nem todos os bloqueios são implementados dessa maneira.
Também escrevi o que equivalia a um bit de código apenas para 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 destruidor porque não havia necessidade de desfazer o patch.
fonte
Eu concordo com AndyBursh. Parece alguma falta de conhecimento.
No entanto, é importante mencionar estruturas como NHibernate, que exigem que você codifique apenas no construtor (ao criar classes de mapa). No entanto, isso não é uma limitação da estrutura, é 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ê precisar chamar um método de uma classe dinamicamente.
fonte
Sim, talvez você esteja se lembrando por que precisamos do StringBuilder.
Existem duas escolas de Java.
O construtor foi promovido pelo primeiro , que nos ensina a reutilizar (porque compartilhar = identidade com eficiência). No entanto, no começo de 2000, comecei a ler que a criação de objetos em Java é tão barata (em oposição ao C ++) e o 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 conhecida como redução do escopo). Assim,
isto é mau
isso é bom.
Fiz essa pergunta quando o forum.java.sun existia e as pessoas concordaram que prefeririam declarar a matriz o mais estreita possível.
O programador java moderno nunca hesita em declarar uma nova classe ou criar um objeto. Ele é encorajado a fazê-lo. Java dá todo o motivo para fazê-lo, com a ideia de que "tudo é um objeto" e criação barata.
Além disso, o estilo moderno de programação corporativa é que, quando você precisa executar uma ação, cria uma classe especial (ou melhor ainda, uma estrutura) que executará essa ação simples. Bons Java IDEs, que ajudam aqui. Eles geram toneladas de lixo automaticamente, como a respiração.
Então, acho que seus objetos não possuem o padrão Builder ou Factory. Não é decente criar instâncias diretamente, pelo construtor. Uma classe de fábrica especial para cada objeto é recomendada.
Agora, quando a programação funcional entra em ação, a criação de objetos se torna ainda mais simples. Você criará objetos a cada passo como respiração, sem nem perceber. Então, espero que seu código se torne ainda mais "eficiente" na nova era
Pessoalmente, não vejo razão na diretriz. Eu considero isso apenas mais um método. O código de fatoração é necessário somente quando você pode compartilhá-lo.
fonte