O registro ao lado de uma implementação é uma violação do SRP?

19

Ao pensar no desenvolvimento ágil de software e em todos os princípios (SRP, OCP, ...), pergunto-me como tratar o log.

O registro ao lado de uma implementação é uma violação do SRP?

Eu diria yesporque a implementação também deve ser capaz de executar sem o log. Então, como posso implementar o log de uma maneira melhor? Eu verifiquei alguns padrões e cheguei à conclusão de que a melhor maneira de não violar os princípios de uma maneira definida pelo usuário, mas usar qualquer padrão conhecido por violar um princípio é usar um padrão decorador.

Digamos que temos um monte de componentes completamente sem violação do SRP e, em seguida, queremos adicionar o log.

  • componente A
  • componente B usa A

Queremos registrar para A, então criamos outro componente D decorado com A, ambos implementando uma interface I.

  • interface I
  • componente L (componente de log do sistema)
  • componente A implementa I
  • o componente D implementa I, decora / usa A, usa L para registrar
  • componente B usa um I

Vantagens: - Posso usar A sem registrar - testando A significa que não preciso de zombarias - os testes são mais simples

Desvantagem: - mais componentes e mais testes

Sei que essa parece ser outra questão de discussão aberta, mas na verdade quero saber se alguém usa melhores estratégias de log do que um decorador ou uma violação do SRP. E o registrador singleton estático, que é o NullLogger padrão e, se o log de syslog for desejado, altere o objeto de implementação no tempo de execução?

Aitch
fonte
possível duplicação de padrões
30615
Eu já li e a resposta não é satisfatória, desculpe.
Aitch
@ MarkRogers obrigado por compartilhar esse artigo interessante. O tio Bob diz em 'Clean Code', que um bom componente SRP está lidando com outros componentes no mesmo nível de abstração. Para mim, essa explicação é mais fácil de entender, pois o contexto também pode ser muito grande. Mas não posso responder à pergunta, porque qual é o nível ou abstração de um logger?
Aitch
3
"não é uma resposta para mim" ou "a resposta não é satisfatória" é um pouco desdenhoso. Você pode refletir sobre o que especificamente é insatisfatório (qual requisito você tem que não foi atendido por essa resposta? O que especificamente é único sobre sua pergunta?) E, em seguida, edite sua pergunta para garantir que esse requisito / aspecto exclusivo seja explicado claramente. O objetivo é fazer com que você edite sua pergunta para melhorá-la para torná-la mais clara e mais focada, não para pedir clichês afirmando que sua pergunta é diferente / não deve ser fechada sem justificativa. (Você também pode comentar sobre a outra resposta.)
DW

Respostas:

-1

Sim, é uma violação do SRP, pois o registro é uma preocupação transversal.

A maneira correta é delegar o log para uma classe de logger (interceptação) cujo único objetivo é cumprir o log pelo SRP.

Veja este link para um bom exemplo: https://msdn.microsoft.com/en-us/library/dn178467%28v=pandp.30%29.aspx

Aqui está um pequeno exemplo :

public interface ITenantStore
{
    Tenant GetTenant(string tenant);
    void SaveTenant(Tenant tenant);
}

public class TenantStore : ITenantStore
{
    public Tenant GetTenant(string tenant)
    {....}

    public void SaveTenant(Tenant tenant)
    {....}
} 

public class TenantStoreLogger : ITenantStore
{
    private readonly ILogger _logger; //dep inj
    private readonly ITenantStore _tenantStore;

    public TenantStoreLogger(ITenantStore tenantStore)
    {
        _tenantStore = tenantStore;
    }

    public Tenant GetTenant(string tenant)
    {
        _logger.Log("reading tenant " + tenant.id);
        return _tenantStore.GetTenant(tenant);
    }

    public void SaveTenant(Tenant tenant)
    {
        _tenantStore.SaveTenant(tenant);
        _logger.Log("saving tenant " + tenant.id);
    }
}

Os benefícios incluem

  • Você pode testar isso sem registrar - teste de unidade verdadeiro
  • você pode facilmente ativar / desativar o logon - mesmo em tempo de execução
  • você pode substituir o registro por outras formas de registro, sem precisar alterar o arquivo TenantStore.
z0mbi3
fonte
Obrigado pelo bom link. A Figura 1 nessa página é, na verdade, o que eu chamaria de minha solução favorita. A lista de preocupações transversais (registro em log, armazenamento em cache etc.) e um padrão de decorador é a solução mais genérica e fico feliz por não estar completamente errado com meus pensamentos, embora a comunidade maior queira descartar essa abstração e registro em linha .
Aitch
2
Não vejo você atribuindo a variável _logger em nenhum lugar. Você estava pensando em usar a injeção de construtor e acabou de se esquecer? Nesse caso, você provavelmente receberá um aviso do compilador.
user2023861
27
Em vez de o TenantStore ser mergulhado em um Logger de uso geral, que requer classes N + 1 (quando você adiciona um LandlordStore, um FooStore, um BarStore etc.), você tem um TenantStoreLogger sendo mergulhado em um TenantStore, um FooStoreLogger sendo mergulhado em um FooStore , etc ... requerendo classes 2N. Tanto quanto eu posso dizer, para benefício zero. Quando você deseja fazer testes de unidade sem registro em log, precisará rejeitar as classes N, em vez de apenas configurar um NullLogger. IMO, esta é uma abordagem muito pobre.
user949300
6
Fazer isso para cada classe que precisa de log aumenta drasticamente a complexidade da sua base de código (a menos que poucas classes tenham log que você nem chamaria de preocupação transversal). Em última análise, torna o código menos sustentável, simplesmente devido ao grande número de interfaces a serem mantidas, o que contraria tudo o que o princípio de responsabilidade única foi criado.
jpmc26
9
Votado. Você removeu a preocupação de log da classe Inquilino, mas agora sua TenantStoreLoggeralteração é alterada toda vez TenantStore. Você não está separando as preocupações mais do que na solução inicial.
Laurent LA RIZZA
61

Eu diria que você está levando o SRP muito a sério. Se o seu código estiver organizado o suficiente para que o log seja a única "violação" do SRP, você estará se saindo melhor do que 99% de todos os outros programadores e deverá dar um tapinha nas costas.

O objetivo do SRP é evitar códigos espaguetes horríveis, onde códigos que fazem coisas diferentes são todos misturados. Misturar o registro com o código funcional não soa nenhum sinal de alarme para mim.

Grahamparks
fonte
19
@Itch: Suas opções são conectar o logon em sua classe, passar um identificador para um logger ou não registrar nada. Se você for extremamente rigoroso com o SRP às custas de todo o resto, eu recomendo nunca registrar nada. Tudo o que você precisa saber sobre o que seu software está fazendo pode ser descartado com um depurador. OP no SRP significa "princípio", não "lei física da natureza que nunca deve ser violada".
Blrfl
3
@Itch: Você deve conseguir rastrear o log na sua classe de volta a algum requisito, caso contrário você está violando o YAGNI. Se o log estiver na tabela, você fornecerá um identificador de log válido, exatamente como faria para qualquer outra coisa de que a classe precise, de preferência um de uma classe que já passou no teste. Se é aquele que produz entradas de log reais ou as despeja no bloco de bits, é a preocupação do que instancia a instância da sua classe; a classe em si não deveria se importar.
Blrfl
3
@Aitch Para responder sua pergunta sobre o teste de unidade:, Do you mock the logger?é PRECISAMENTE o que você faz. Você deve ter uma ILoggerinterface que defina O QUE o criador de logs faz. O código em teste é injetado com um ILoggerque você especificar. Para testar, você tem class TestLogger : ILogger. O melhor disso é que TestLoggerpode expor coisas como a última string ou erro registrado. Os testes podem verificar se o código em teste está registrado corretamente. Por exemplo, um teste pode ser o UserSignInTimeGetsLogged()local em que o teste verifica TestLoggero logado.
CurtisHx
5
99% parece um pouco baixo. Você provavelmente é melhor que 100% de todos os programadores.
Paul Draper
2
+1 por sanidade. Precisamos de mais desse tipo de pensamento: menos foco em palavras e princípios abstratos e mais foco em ter uma base de código sustentável .
jpmc26
15

Não, não é uma violação do SRP.

As mensagens enviadas para o log devem ser alteradas pelos mesmos motivos que o código circundante.

O que é uma violação do SRP está usando uma biblioteca específica para fazer logon diretamente no código. Se você decidir alterar a maneira de registrar, o SRP declara que isso não deve afetar seu código comercial.

Algum tipo de resumo Loggerdeve estar acessível ao seu código de implementação, e a única coisa que sua implementação deve dizer é "Enviar esta mensagem para o log", sem preocupações quanto ao modo como isso é feito. Decidir sobre a maneira exata de registrar-se (até mesmo no registro de data e hora) não é responsabilidade da sua implementação.

Sua implementação também não deve saber se o criador de logs para o qual está enviando mensagens é a NullLogger.

Dito isto.

Eu não descartaria o registro como uma preocupação transversal muito rápido . A emissão de logs para rastrear eventos específicos que ocorrem no seu código de implementação pertence ao código de implementação.

O que é uma preocupação transversal, OTOH, é o rastreamento de execução : o registro entra e sai em todo e qualquer método. AOP está em melhor posição para fazer isso.

Laurent LA RIZZA
fonte
Digamos que a mensagem do logger seja 'login user xyz', que é enviada para um logger que precede um carimbo de data / hora, etc. Você sabe o que 'login' significa para a implementação? Está iniciando uma sessão com um cookie ou qualquer outro mecanismo? Eu acho que existem muitas maneiras diferentes de implementar um login, portanto, alterar a implementação não tem nada a ver com o fato de o usuário efetuar login. Esse é outro ótimo exemplo de decoração de componentes diferentes (como OAuthLogin, SessionLogin, BasicAuthorizationLogin) fazendo a mesma coisa como uma Logininterface decorada com o mesmo logger.
Aitch
Depende do significado da mensagem "login user xyz". Se marcar o fato de um logon ser bem-sucedido, o envio da mensagem para o log pertencerá ao caso de uso de logon. A maneira específica de representar as informações de logon como uma sequência de caracteres (OAuth, Sessão, LDAP, NTLM, impressão digital, roda de hamster) pertence à classe específica que representa as credenciais ou a estratégia de logon. Não há necessidade de removê-lo. Este caso sepcífico não é uma preocupação transversal. É específico para o caso de uso de logon.
Laurent LA RIZZA
7

Como o log é geralmente considerado uma preocupação transversal, sugiro usar o AOP para separar o log da implementação.

Dependendo da linguagem, você usaria um interceptador ou alguma estrutura de AOP (por exemplo, AspectJ em Java) para fazer isso.

A questão é se isso realmente vale a pena. Observe que essa separação aumentará a complexidade do seu projeto, proporcionando muito pouco benefício.

Oliver Weiler
fonte
2
A maior parte do código AOP que vi foi sobre o registro de todas as etapas de entrada e saída de todos os métodos. Eu só quero registrar algumas partes da lógica de negócios. Talvez seja possível registrar apenas métodos anotados, mas o AOP só pode existir em linguagens de script e ambientes de máquinas virtuais, certo? Por exemplo, C ++ é impossível. Admito que não estou muito feliz com as abordagens da AOP, mas talvez não haja uma solução mais limpa.
Aitch
1
@Aitch. "C ++ é impossível." : Se você pesquisar no Google por "aop c ++", encontrará artigos sobre o assunto. "... o código AOP que vi foi sobre o registro de todas as etapas de entrada e saída de todos os métodos. Quero apenas registrar algumas partes da lógica de negócios." Aop permite definir padrões para encontrar os métodos a serem modificados. ou seja, todos os métodos do espaço de nome "my.busininess. *"
k3b
1
Geralmente, o log NÃO é uma preocupação transversal, especialmente quando você deseja que seu log contenha informações interessantes, ou seja, mais informações do que as contidas em um rastreamento de pilha de exceção.
Laurent LA RIZZA
5

Isso parece bom. Você está descrevendo um decorador de log bastante padrão. Você tem:

componente L (componente de log do sistema)

Isso tem uma responsabilidade: registrar as informações que são passadas para ele.

componente A implementa I

Isso tem uma responsabilidade: fornecer uma implementação da interface I (supondo que eu seja adequadamente compatível com SRP).

Esta é a parte crucial:

o componente D implementa I, decora / usa A, usa L para registrar

Quando afirmado dessa maneira, parece complexo, mas veja da seguinte maneira: o componente D faz uma coisa: reunir A e L.

  • O componente D não registra; delega isso para L
  • O componente D não implementa eu próprio; delega isso para A

A única responsabilidade que o componente D tem é garantir que L seja notificado quando A for usado. As implementações de A e L estão em outro lugar. Isso é totalmente compatível com SRP, além de ser um exemplo puro de OCP e um uso bastante comum de decoradores.

Uma ressalva importante: quando D usa seu componente de registro L, deve fazê-lo de uma maneira que permita alterar a forma como você está registrando. A maneira mais simples de fazer isso é ter uma interface IL implementada por L. Então:

  • O componente D usa uma IL para registrar; uma instância de L é fornecida
  • O componente D usa um I para fornecer funcionalidade; uma instância de A é fornecida
  • O componente B usa um I; uma instância de D é fornecida

Dessa forma, nada depende diretamente de qualquer outra coisa, facilitando a troca. Isso facilita a adaptação às mudanças e a simulação de partes do sistema para que você possa fazer o teste de unidade.

anaximander
fonte
Na verdade, eu só conheço C # que tem suporte a delegação nativa. Por isso escrevi D implements I. Obrigado pela sua resposta.
Aitch
1

Claro que é uma violação do SRP, pois você tem uma preocupação transversal. No entanto, você pode criar uma classe responsável por compor o log com a execução de qualquer ação.

exemplo:

class Logger {
   ActuallLogger logger;
   public Action ComposeLog(string msg, Action action) {
      return () => {
          logger.debug(msg);
          action();
      };
   }
}
Paul Nikonowicz
fonte
2
Votado. A exploração madeireira é realmente uma preocupação transversal. O mesmo acontece com as chamadas do método de seqüenciamento no seu código. Isso não é motivo suficiente para reivindicar uma violação do SRP. Registrar a ocorrência de um evento específico em seu aplicativo NÃO é uma preocupação transversal. A maneira como essas mensagens são transportadas para qualquer usuário interessado é realmente uma preocupação separada, e a descrição disso no código de implementação É uma violação do SRP.
Laurent LA RIZZA
"chamadas de método de sequenciamento" ou composição funcional não é uma preocupação transversal, mas um detalhe de implementação. A responsabilidade da função que criei é compor uma instrução de log, com uma ação. Não preciso usar a palavra "e" para descrever o que essa função faz.
Paul Nikonowicz 12/03/2015
Não é um detalhe de implementação. Isso tem um efeito profundo na forma do seu código.
Laurent LA RIZZA
Eu acho que estou olhando para SRP da perspectiva de "O que essa função faz", onde você está olhando para SRP da perspectiva de "COMO essa função faz isso".
Paul Nikonowicz 13/0315