qual deve ser a posição do registrador na lista de parâmetros [fechada]

12

No meu código, injeto um logger em muitas das minhas classes através da lista de parâmetros de seus construtores

Percebi que o colocava aleatoriamente: às vezes é o primeiro da lista, às vezes dura e às vezes entre

Você tem alguma preferência? Minha intuição diz que a consistência é útil nesse caso e minha preferência pessoal é colocá-la em primeiro lugar, para que seja mais fácil ser notado quando estiver faltando e mais fácil pular quando estiver lá.

Esdras
fonte

Respostas:

31

Os madeireiros são o que chamamos de "preocupação transversal". Eles cedem a técnicas como Programação Orientada a Aspectos; se você tem uma maneira de decorar suas classes com um atributo ou executar alguma tecelagem de código, é uma boa maneira de obter recursos de registro, mantendo seus objetos e listas de parâmetros "puros".

O único motivo para você querer passar um criador de logs é se você deseja especificar diferentes implementações de log, mas a maioria das estruturas de log possui a flexibilidade de permitir que você as configure, por exemplo, para diferentes destinos de log. (arquivo de log, Windows Event Manager, etc.)

Por esses motivos, prefiro tornar o log uma parte natural do sistema, em vez de passar um log para todas as classes para fins de log. Então, o que geralmente faço é referenciar o espaço de nome de log apropriado e simplesmente usar o logger em minhas classes.

Se você ainda deseja passar um logger, minha preferência é torná-lo o último parâmetro na lista de parâmetros (torne-o um parâmetro opcional, se possível). Ser o primeiro parâmetro não faz muito sentido; o primeiro parâmetro deve ser o mais importante, o mais relevante para a operação da classe.

Robert Harvey
fonte
11
+! mantenha o logger fora dos parâmetros do construtor; Eu prefiro um logger estático, então eu sei que todo mundo está usando a mesma coisa
Steven A. Lowe
1
@ StevenA.Lowe Observe que o uso de registradores estáticos pode levar a outros problemas na linha se você não tomar cuidado (por exemplo, fiasco de ordem de inicialização estática em C ++). Concordo que ter o criador de logs como uma entidade globalmente acessível tem seus encantos, mas deve ser cuidadosamente avaliado se e como esse design se encaixa na arquitetura geral.
ComicSansMS
@ComicSansMS: é claro, além de problemas de segmentação etc. apenas uma preferência pessoal - "o mais simples possível, mas não mais simples";)
Steven A. Lowe
Ter um registrador estático pode ser um problema. Isso torna a injeção de dependência mais difícil (a menos que você queira dizer um registrador singleton instanciado pelo seu contêiner DI) e, se você quiser alterar sua arquitetura, pode ser doloroso. No momento, por exemplo, estou usando funções do Azure que passam em um criador de logs como parâmetro para a execução de cada função, então lamento ter passado meu criador de logs através do construtor.
Slothario 18/01/19
@ Slothario: Essa é a beleza de um registrador estático. Nenhuma injeção de qualquer tipo é necessária.
Robert Harvey
7

Nas linguagens com sobrecarga de funções, eu argumentaria que, quanto mais provável um argumento for opcional, mais certo deverá ser. Isso cria consistência quando você cria sobrecargas onde elas estão ausentes:

foo(mandatory);
foo(mandatory, optional);
foo(mandatory, optional, evenMoreOptional);

Nas linguagens funcionais, o inverso é mais útil - quanto maior a probabilidade de você escolher algum padrão, mais resta deve ficar. Isso facilita a especialização da função simplesmente aplicando argumentos a ela:

addThreeToList = map (+3)

No entanto, conforme mencionado nas outras respostas, você provavelmente não deseja passar explicitamente o criador de logs na lista de argumentos de todas as classes no sistema.

Doval
fonte
3

Definitivamente, você pode dedicar muito tempo à engenharia deste problema.

Para idiomas com implementações de log canônico, instancie o logon canônico diretamente em todas as classes.

Para idiomas sem uma implementação canônica, tente encontrar uma estrutura de fachada de log e atenha-se a ela. slf4j é uma boa escolha em Java.

Pessoalmente, prefiro manter uma única implementação concreta de log e enviar tudo para o syslog. Todas as boas ferramentas de análise de log são capazes de combinar logs de sysout de vários servidores de aplicativos em um relatório abrangente.

Quando uma assinatura de função inclui um ou dois serviços de dependência, além de alguns argumentos "reais", coloco as dependências por último:

int calculateFooBarSum(int foo, int bar, IntegerSummationService svc)

Como meus sistemas tendem a ter apenas cinco ou menos desses serviços, sempre asseguro que os serviços sejam incluídos na mesma ordem em todas as assinaturas de funções. A ordem alfabética é tão boa quanto qualquer outra. (Além disso: a manutenção dessa abordagem metodológica para o manuseio de mutex também reduzirá suas chances de desenvolver impasses.)

Se você estiver injetando mais de uma dúzia de dependências em seu aplicativo, o sistema provavelmente precisará ser dividido em subsistemas separados (ouso dizer microsserviços?).

Christian Willman
fonte
Parece-me estranho usar injeção de propriedade para chamar calculFooBarSum.
An Phu
2

Os madeireiros são um caso especial porque precisam estar disponíveis literalmente em qualquer lugar.

Se você decidiu passar um logger para o construtor de cada classe, definitivamente deve definir uma convenção consistente sobre como fazer isso (por exemplo, sempre o primeiro parâmetro, sempre passado por referência, a lista de inicialização do construtor sempre começa com m_logger (theLogger), etc). Qualquer coisa que será usada em toda a sua base de código se beneficiará da consistência algum dia.

Como alternativa, todas as classes podem instanciar seu próprio objeto de logger, sem precisar que nada seja passado. O logger pode precisar saber algumas coisas "por mágica" para que isso funcione, mas codificar um caminho de arquivo na definição de classe é potencialmente um muito mais sustentável e menos tedioso do que passá-lo corretamente para centenas de classes diferentes e, sem dúvida, muito menos maligno do que usar uma variável global para contornar o referido tédio. (É certo que os registradores estão entre os poucos casos de uso legítimos para variáveis ​​globais)

Ixrec
fonte
1

Concordo com aqueles que sugerem que o criador de logs deve ser acessado estaticamente, e não passado para as classes. No entanto, se há uma forte razão que você quer passá-lo em (casos talvez diferentes quer registrar para locais diferentes ou algo assim), então eu sugiro que você não passá-lo usando o construtor, mas sim fazer uma chamada separada para o fazer, por exemplo, Class* C = new C(); C->SetLogger(logger);em vez do queClass* C = new C(logger);

A razão para preferir esse método é que o criador de logs não é essencialmente uma parte da classe, mas um recurso injetado usado para algum outro propósito. Colocá-lo na lista de construtores o torna um requisito da classe e implica que faz parte do estado lógico real da classe. É razoável esperar, por exemplo (com a maioria das classes, embora não todas), que, se houver X != Y, C(X) != C(Y)mas é improvável que você teste a desigualdade do criador de logs se estiver comparando também instâncias da mesma classe.

Jack Aidley
fonte
1
Obviamente, isso tem a desvantagem de que o criador de logs não está disponível para o construtor.
Ben Voigt
1
Eu realmente gosto desta resposta. Isso faz do log uma preocupação secundária da classe com a qual você precisa se preocupar separadamente, além de apenas usá-lo. As chances são de que, se você estiver adicionando o criador de logs ao construtor de um grande número de classes, provavelmente estará usando a injeção de dependência. Não sei falar para todos os idiomas, mas sei que em C #, algumas implementações de DI também injetam diretamente nas propriedades (getter / setter).
jpmc26
@BenVoigt: Isso é verdade, e pode ser uma boa razão para não fazer dessa maneira, mas, geralmente, você pode fazer o log que faria no construtor em resposta a um logger sendo configurado.
Jack Aidley
0

Vale a pena mencionar, algo que eu não vi as outras respostas abordar aqui, é que, ao fazer o logger injetado via propriedade ou estática, torna mais difícil testar a unidade. Por exemplo, se você injetar seu logger via propriedade, agora precisará injetar esse logger sempre que testar um método que usa o logger. Isso significa que você pode configurá-lo como uma dependência do construtor, porque a classe exige isso.

A estática se presta ao mesmo problema; se o criador de logs não funcionar, sua classe inteira falhará (se a sua classe usar o criador de logs) - mesmo que o criador de log não seja necessariamente 'parte' da responsabilidade da classe -, embora não seja tão ruim quanto a injeção de propriedade porque você pelo menos saiba que o logger está sempre "lá" em um sentido.

Apenas um pouco de reflexão, especialmente se você usar TDD. Na minha opinião, um criador de logs não deve realmente fazer parte de uma parte testável de uma classe (quando você testa a classe, você não deve testar também o seu log).

Dan Pantry
fonte
1
hmmm ... então você quer que sua classe realize o log (o log deve estar na especificação), mas não deseja testar usando um logger. Isso é possível? Eu acho que o seu ponto é irreal. Claramente, se as suas ferramentas de teste estão quebrados você não pode teste - para projetar de forma a não contar com uma ferramenta de teste é um pouco de IMHO excesso de engenharia
Hogan
1
O que quero dizer é que, se eu usar um construtor e chamar um método em uma classe e ele ainda falhar porque não defini uma propriedade, o designer da classe não entendeu o conceito por trás de um construtor. Se um criador de logs é requerido pela classe, ele deve ser injetado no construtor - é para isso que o construtor existe.
Dan Pantry
umm .. não, não realmente. Se você considerar o sistema de Log como parte da "estrutura", não faz sentido como parte do construtor. Mas isso foi afirmado em outras respostas claramente.
Hogan
1
Estou argumentando contra a injeção de propriedades. Não estou necessariamente defendendo o uso de injeção no construtor. Só estou dizendo que, na minha opinião, é preferível à injeção de propriedade.
Dan Pantry
"Isso é possível?" Também, sim, IL tecelagem é uma coisa que existe e foi mencionado em que a resposta de topo ... mono-project.com/docs/tools+libraries/libraries/Mono.Cecil
Dan Pantry
0

Estou com preguiça de passar um objeto logger para cada instância da classe. Portanto, no meu código, esse tipo de coisa fica em um campo estático ou em uma variável local de thread em um campo estático. O último é meio legal e permite que você use um logger diferente para cada thread e adicione métodos para ativar e desativar o log que fazem algo significativo e esperado em um aplicativo multiencadeado.

Atsby
fonte