Nota: O exemplo de código é escrito em c #, mas isso não deve importar. Coloquei c # como uma tag porque não consigo encontrar uma mais apropriada. Isso é sobre a estrutura do código.
Estou lendo o Código Limpo e tentando me tornar um programador melhor.
Muitas vezes me vejo lutando para seguir o Princípio da Responsabilidade Única (classes e funções devem fazer apenas uma coisa), especialmente em funções. Talvez o meu problema seja que "uma coisa" não esteja bem definida, mas ainda assim ...
Um exemplo: eu tenho uma lista de Fluffies em um banco de dados. Não nos importamos com o que é fofo. Eu quero uma aula para recuperar fluffies. No entanto, os fluffies podem mudar de acordo com alguma lógica. Dependendo de alguma lógica, essa classe retornará os dados do cache ou obterá as informações mais recentes do banco de dados. Poderíamos dizer que gerencia fluffies, e isso é uma coisa. Para simplificar, digamos que os dados carregados sejam válidos por uma hora e depois sejam recarregados.
class FluffiesManager
{
private Fluffies m_Cache;
private DateTime m_NextReload = DateTime.MinValue;
// ...
public Fluffies GetFluffies()
{
if (NeedsReload())
LoadFluffies();
return m_Cache;
}
private NeedsReload()
{
return (m_NextReload < DateTime.Now);
}
private void LoadFluffies()
{
GetFluffiesFromDb();
UpdateNextLoad();
}
private void UpdateNextLoad()
{
m_NextReload = DatTime.Now + TimeSpan.FromHours(1);
}
// ...
}
GetFluffies()
parece-me bem. O usuário pede alguns fluffies, nós os fornecemos. Indo recuperá-los do banco de dados, se necessário, mas isso pode ser considerado parte da obtenção dos fluffies (é claro, isso é um pouco subjetivo).
NeedsReload()
parece certo também. Verifica se precisamos recarregar os fluffies. UpdateNextLoad está bom. Atualiza o horário da próxima recarga. essa é definitivamente uma coisa.
No entanto, sinto que o que LoadFluffies()
fazer não pode ser descrito como uma única coisa. Ele está obtendo os dados do banco de dados e agendando a próxima recarga. É difícil argumentar que calcular o tempo para a próxima recarga faz parte da obtenção dos dados. No entanto, não consigo encontrar uma maneira melhor de fazê-lo (renomear a função LoadFluffiesAndScheduleNextLoad
pode ser melhor, mas isso apenas torna o problema mais óbvio).
Existe uma solução elegante para realmente escrever esta classe de acordo com o SRP? Estou sendo muito pedante?
Ou talvez minha turma não esteja realmente fazendo apenas uma coisa?
DateTime.UtcNow
para evitar trocas de horário de verão ou mesmo uma alteração no fuso horário atual.Respostas:
Se essa classe realmente fosse tão trivial quanto parece, não haveria necessidade de se preocupar em violar o SRP. Então, e se uma função de 3 linhas tiver 2 linhas fazendo uma coisa e outra 1 linha fazendo outra coisa? Sim, essa função trivial viola o SRP, e daí? Quem se importa? A violação do SRP começa a se tornar um problema quando as coisas ficam mais complicadas.
Seu problema nesse caso em particular provavelmente decorre do fato de a classe ser mais complicada do que as poucas linhas que você nos mostrou.
Especificamente, o problema provavelmente reside no fato de que essa classe não apenas gerencia o cache, mas também provavelmente contém a implementação do
GetFluffiesFromDb()
método. Portanto, a violação do SRP está na classe, não nos poucos métodos triviais mostrados no código que você postou.Então, aqui está uma sugestão sobre como lidar com todos os tipos de casos que se enquadram nessa categoria geral, com a ajuda do Padrão Decorador .
e é usado da seguinte maneira:
Observe como
CachingFluffiesProvider.GetFluffies()
não tem medo de conter o código que verifica e atualiza o tempo, porque isso é trivial. O que esse mecanismo faz é abordar e manipular o SRP no nível de design do sistema, onde é importante, não no nível de pequenos métodos individuais, onde isso não importa.fonte
Sua aula em si parece boa para mim, mas você está certo
LoadFluffies()
, não exatamente o que o nome anuncia. Uma solução simples seria alterar o nome e mover a recarga explícita de GetFluffies para uma função com uma descrição apropriada. Algo comoparece limpo para mim (também porque, como Patrick diz: é composto de outras pequenas funções obedientes ao SRP), e especialmente claro, o que às vezes é igualmente importante.
fonte
Eu acredito que sua classe está fazendo uma coisa; é um cache de dados com um tempo limite. O LoadFluffies parece uma abstração inútil, a menos que você a chame de vários lugares. Eu acho que seria melhor pegar as duas linhas do LoadFluffies e colocá-las no condicional NeedsReload no GetFluffies. Isso tornaria a implementação do GetFluffies muito mais óbvia e ainda é um código limpo, pois você está compondo sub-rotinas de responsabilidade única para atingir um único objetivo, uma recuperação em cache de dados do banco de dados. Abaixo está o método get fluffies atualizado.
fonte
Seus instintos estão corretos. Sua turma, por menor que seja, está fazendo muito. Você deve separar a lógica de cache de atualização programada em uma classe completamente genérica. Em seguida, crie uma instância específica dessa classe para gerenciar Fluffies, algo como isto (não compilado, o código de trabalho é deixado como um exercício para o leitor):
Uma vantagem adicional é que agora é muito fácil testar o TimedRefreshCache.
fonte
Sua classe é boa, o SRP é sobre uma classe e não uma função, toda a classe é responsável por fornecer os "Fluffies" da "Fonte de Dados" para que você seja livre na implementação interna.
Se você deseja expandir o mecanismo de conversão, pode criar uma classe respeitável para assistir à fonte de dados
fonte