Nota: El código de muestra está escrito en C #, pero eso no debería importar. Puse c # como etiqueta porque no puedo encontrar una más adecuada. Esto se trata de la estructura del código.
Estoy leyendo Clean Code e intento convertirme en un mejor programador.
A menudo me encuentro luchando por seguir el Principio de Responsabilidad Única (las clases y las funciones deberían hacer solo una cosa), especialmente en las funciones. Quizás mi problema es que "una cosa" no está bien definida, pero aún así ...
Un ejemplo: tengo una lista de Fluffies en una base de datos. No nos importa lo que sea un Fluffy. Quiero una clase para recuperar pelusas. Sin embargo, las pelusas pueden cambiar según alguna lógica. Dependiendo de alguna lógica, esta clase devolverá los datos del caché u obtendrá lo último de la base de datos. Podríamos decir que maneja pelusas, y eso es una cosa. Para simplificarlo, supongamos que los datos cargados son válidos durante una hora y luego deben volver a cargarse.
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()
me parece bien El usuario solicita algunas pelusas, nosotros se las proporcionamos. Ir a recuperarlos de la base de datos si es necesario, pero eso podría considerarse parte de obtener las esponjosas (por supuesto, eso es algo subjetivo).
NeedsReload()
Parece correcto también. Comprueba si necesitamos recargar las pelusas. UpdateNextLoad está bien. Actualiza el tiempo para la próxima recarga. Eso es definitivamente una sola cosa.
Sin embargo, siento que LoadFluffies()
no se puede describir como una sola cosa. Está obteniendo los datos de la base de datos, y está programando la próxima recarga. Es difícil argumentar que calcular el tiempo para la próxima recarga es parte de obtener los datos. Sin embargo, no puedo encontrar una mejor manera de hacerlo (cambiar el nombre de la función LoadFluffiesAndScheduleNextLoad
puede ser mejor, pero solo hace que el problema sea más obvio).
¿Existe una solución elegante para escribir realmente esta clase de acuerdo con el SRP? ¿Estoy siendo demasiado pedante?
¿O tal vez mi clase no está haciendo solo una cosa?
DateTime.UtcNow
para evitar cambios de horario de verano o incluso un cambio en la zona horaria actual.Respuestas:
Si esta clase fuera realmente tan trivial como parece ser, entonces no habría necesidad de preocuparse por violar el SRP. Entonces, ¿qué pasa si una función de 3 líneas tiene 2 líneas haciendo una cosa y otra 1 línea haciendo otra cosa? Sí, esta función trivial viola el SRP, ¿y qué? ¿A quien le importa? La violación del SRP comienza a convertirse en un problema cuando las cosas se vuelven más complicadas.
Su problema en este caso particular probablemente se deba al hecho de que la clase es más complicada que las pocas líneas que nos ha mostrado.
Específicamente, el problema probablemente radica en el hecho de que esta clase no solo administra el caché, sino que también contiene la implementación del
GetFluffiesFromDb()
método. Entonces, la violación del SRP está en la clase, no en los pocos métodos triviales que se muestran en el código que publicó.Entonces, aquí hay una sugerencia sobre cómo manejar todo tipo de casos que caen dentro de esta categoría general, con la ayuda del Patrón Decorador .
y se usa de la siguiente manera:
Tenga en cuenta que
CachingFluffiesProvider.GetFluffies()
no tiene miedo de contener el código que verifica y actualiza el tiempo, porque eso es algo trivial. Lo que hace este mecanismo es abordar y manejar el SRP a nivel de diseño del sistema, donde importa, no a nivel de pequeños métodos individuales, donde de todos modos no importa.fuente
Tu clase en sí me parece bien, pero tienes razón en eso
LoadFluffies()
no es exactamente lo que anuncia el nombre. Una solución simple sería cambiar el nombre y mover la recarga explícita de GetFluffies a una función con una descripción adecuada. Algo comoA mí me parece limpio (también porque, como dice Patrick: está compuesto de otras pequeñas funciones obedientes a SRP), y especialmente claro, que a veces es tan importante.
fuente
Creo que tu clase está haciendo una cosa; Es un caché de datos con un tiempo de espera. LoadFluffies parece una abstracción inútil a menos que lo llame desde varios lugares. Creo que sería mejor tomar las dos líneas de LoadFluffies y ponerlas en el condicional NeedsReload en GetFluffies. Esto haría que la implementación de GetFluffies sea mucho más obvia y siga siendo un código limpio, ya que está componiendo subrutinas de responsabilidad única para lograr un solo objetivo, una recuperación en caché de datos de la base de datos. A continuación se muestra el método actualizado get fluffies.
fuente
Tus instintos son correctos. Tu clase, por pequeña que sea, está haciendo demasiado. Debe separar la lógica de almacenamiento en caché de actualización temporizada en una clase completamente genérica. Luego cree una instancia específica de esa clase para administrar Fluffies, algo como esto (no compilado, el código de trabajo se deja como ejercicio para el lector):
Una ventaja adicional es que ahora es muy fácil probar TimedRefreshCache.
fuente
Su clase está bien, SRP se trata de una clase, no de una función, toda la clase es responsable de proporcionar los "Fluffies" de la "Fuente de datos" para que tenga una implementación interna libre.
Si desea expandir el mecanismo de cahing, puede crear una clase respirable para ver la fuente de datos
fuente