¿El registro junto a una implementación es una violación de SRP?

19

Cuando pienso en el desarrollo ágil de software y todos los principios (SRP, OCP, ...) me pregunto cómo tratar el registro.

¿El registro junto a una implementación es una violación de SRP?

Yo diría yesque la implementación también debería poder ejecutarse sin iniciar sesión. Entonces, ¿cómo puedo implementar el registro de una mejor manera? Revisé algunos patrones y llegué a la conclusión de que la mejor manera de no violar los principios de una manera definida por el usuario, sino usar cualquier patrón que se sepa que viola un principio es usar un patrón decorador.

Digamos que tenemos un montón de componentes completamente sin violación de SRP y luego queremos agregar el registro.

  • componente A
  • el componente B usa A

Queremos iniciar sesión para A, por lo que creamos otro componente D decorado con A, ambos implementando una interfaz I.

  • interfaz I
  • componente L (componente de registro del sistema)
  • el componente A implementa I
  • el componente D implementa I, decora / usa A, usa L para iniciar sesión
  • el componente B usa una I

Ventajas: - Puedo usar A sin iniciar sesión - probar A significa que no necesito ningún simulacro de registro - las pruebas son más simples

Desventaja: - más componentes y más pruebas

Sé que esta parece ser otra pregunta de discusión abierta, pero en realidad quiero saber si alguien usa mejores estrategias de registro que un decorador o una violación de SRP. ¿Qué pasa con el registrador estático singleton que son NullLogger predeterminados y si se desea el registro de syslog, uno cambia el objeto de implementación en tiempo de ejecución?

Aitch
fuente
posible duplicado de patrones
mosquito
Ya lo he leído y la respuesta no es satisfactoria, lo siento.
Aitch
@ MarkRogers gracias por compartir ese interesante artículo. El tío Bob dice en 'Código limpio', que un buen componente SRP está tratando con otros componentes en el mismo nivel de abstracción. Para mí, esa explicación es más fácil de entender ya que el contexto también puede ser demasiado grande. Pero no puedo responder la pregunta, porque ¿cuál es el contexto o nivel de abstracción de un registrador?
Aitch
3
"no es una respuesta para mí" o "la respuesta no es satisfactoria" es un poco desdeñoso. Puede reflexionar sobre lo que específicamente no es satisfactorio (¿qué requisito tiene que no se cumplió con esa respuesta? ¿Qué es específicamente único sobre su pregunta?), Luego edite su pregunta para asegurarse de que este requisito / aspecto único se explique claramente. El propósito es lograr que edite su pregunta para mejorarla para que sea más clara y más enfocada, no para pedir una referencia que afirme que su pregunta es diferente / no debe cerrarse sin justificación por qué. (También puede comentar la otra respuesta).
DW

Respuestas:

-1

Sí, es una violación de SRP ya que el registro es una preocupación transversal.

La forma correcta es delegar el registro a una clase de registrador (Intercepción) cuyo único propósito es iniciar sesión, respetando el SRP.

Consulte este enlace para ver un buen ejemplo: https://msdn.microsoft.com/en-us/library/dn178467%28v=pandp.30%29.aspx

Aquí hay un breve ejemplo :

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);
    }
}

Beneficios incluidos

  • Puede probar esto sin iniciar sesión: prueba de unidad real
  • puede activar / desactivar fácilmente el inicio de sesión, incluso en tiempo de ejecución
  • puede sustituir el registro por otras formas de registro, sin tener que cambiar el archivo TenantStore.
z0mbi3
fuente
Gracias por el buen enlace. La Figura 1 en esa página es en realidad lo que yo llamaría mi solución favorita. La lista de preocupaciones transversales (registro, almacenamiento en caché, etc.) y un patrón de decorador es la solución más genérica y estoy feliz de no estar completamente equivocado con mis pensamientos, aunque a la comunidad en general le gustaría abandonar esa abstracción y el registro en línea .
Aitch
2
No te veo asignando la variable _logger en ningún lado. ¿Estaba planeando usar la inyección del constructor y simplemente se olvidó? Si es así, probablemente recibirás una advertencia del compilador.
user2023861
27
En lugar de que TenantStore se sumerja con un Logger de propósito general, que requiere clases N + 1 (cuando agrega un LandlordStore, un FooStore, un BarStore, etc.) tiene un TenantStoreLogger que se sumerge con un TenantStore, un FooStoreLogger se sumerge con un FooStore , etc ... que requieren 2N clases. Por lo que puedo decir, para beneficio cero. Cuando desee hacer una prueba de unidad sin registro, necesitará reajustar las clases N, en lugar de simplemente configurar un NullLogger. OMI, este es un enfoque muy pobre.
user949300
66
Hacer esto para cada clase que necesita un registro aumenta drásticamente la complejidad de su base de código (a menos que tan pocas clases tengan un registro que ya ni siquiera lo llamaría una preocupación transversal). En última instancia, hace que el código sea menos mantenible simplemente debido a la gran cantidad de interfaces que se deben mantener, lo que va en contra de todo para lo que se creó el Principio de Responsabilidad Única.
jpmc26
99
Voto negativo Eliminó la preocupación de registro de la clase Inquilino, pero ahora TenantStoreLoggercambiará cada vez que TenantStorecambie. No está separando las preocupaciones más que en la solución inicial.
Laurent LA RIZZA
61

Yo diría que te estás tomando SRP demasiado en serio. Si su código es lo suficientemente ordenado como para que el registro sea la única "violación" de SRP, le está yendo mejor que al 99% de todos los demás programadores, y debería felicitarse.

El objetivo de SRP es evitar el espantoso código de espagueti donde el código que hace cosas diferentes está todo mezclado. Mezclar el registro con el código funcional no me suena ninguna alarma.

grahamparks
fuente
19
@Aitch: sus opciones son conectar el registro a su clase, pasar un identificador a un registrador o no registrar nada en absoluto. Si va a ser extremadamente estricto con el SRP a expensas de todo lo demás, le recomiendo no registrar nada, nunca. Cualquier cosa que necesite saber sobre lo que está haciendo su software se puede extraer con un depurador. La P en SRP significa "principio", no "ley física de la naturaleza que nunca debe romperse".
Blrfl
3
@Aitch: Debería poder rastrear el inicio de sesión en su clase hasta algún requisito, de lo contrario, está violando YAGNI. Si el registro está sobre la mesa, debe proporcionar un identificador de registrador válido como lo haría para cualquier otra cosa que la clase necesite, preferiblemente una de una clase que ya haya pasado la prueba. Ya sea que produzca entradas de registro reales o las descargue en el depósito de bits es la preocupación de qué instancia la instancia de su clase; la clase en sí no debería importarle.
Blrfl
3
@Aitch Para responder a su pregunta sobre las pruebas unitarias: Do you mock the logger?eso es PRECISAMENTE lo que hace. Debe tener una ILoggerinterfaz que defina QUÉ hace el registrador. El código bajo prueba se inyecta con uno ILoggerque especifique. Para probar, tienes class TestLogger : ILogger. Lo mejor de esto es que TestLoggerpuede exponer cosas como la última cadena o error registrado. Las pruebas pueden verificar que el código bajo prueba se registre correctamente. Por ejemplo, una prueba podría ser UserSignInTimeGetsLogged(), donde la prueba verifica TestLoggerlo registrado.
CurtisHx
55
99% parece un poco bajo. Probablemente eres mejor que el 100% de todos los programadores.
Paul Draper
2
+1 por cordura. Necesitamos más de este tipo de pensamiento: menos enfoque en palabras y principios abstractos y más enfoque en tener una base de código mantenible .
jpmc26
15

No, no es una violación de SRP.

Los mensajes que envíe al registro deberían cambiar por los mismos motivos que el código circundante.

Lo que ES una violación de SRP es usar una biblioteca específica para iniciar sesión directamente en el código. Si decide cambiar la forma de iniciar sesión, SRP declara que no debería afectar su código comercial.

LoggerSu código de implementación debe tener acceso a algún tipo de resumen , y lo único que debe decir su implementación es "Enviar este mensaje al registro", sin preocuparse por cómo se hace. Decidir sobre la forma exacta de iniciar sesión (incluso la marca de tiempo) no es responsabilidad de su implementación.

Su implementación tampoco debería saber si el registrador al que está enviando mensajes es a NullLogger.

Eso dicho

No eliminaría la tala como una preocupación transversal demasiado rápido . La emisión de registros para rastrear eventos específicos que ocurren en su código de implementación pertenece al código de implementación.

Lo que es una preocupación transversal, OTOH, es el rastreo de ejecución : el registro entra y sale en todos y cada uno de los métodos. AOP está mejor ubicado para hacer esto.

Laurent LA RIZZA
fuente
Digamos que el mensaje del registrador es 'usuario de inicio de sesión xyz', que se envía a un registrador que antepone una marca de tiempo, etc. ¿Sabe qué significa 'inicio de sesión' para la implementación? ¿Está comenzando una sesión con una cookie o algún otro mecanismo? Creo que hay muchas formas diferentes de implementar un inicio de sesión, por lo que cambiar la implementación lógicamente no tiene nada que ver con el hecho de que un usuario inicie sesión. Ese es otro gran ejemplo de decorar diferentes componentes (por ejemplo, OAuthLogin, SessionLogin, BasicAuthorizationLogin) haciendo lo mismo como una Logininterfaz decorada con el mismo registrador.
Aitch
Depende del significado del mensaje "usuario de inicio de sesión xyz". Si marca el hecho de que un inicio de sesión es exitoso, el envío del mensaje al registro pertenece en el caso de uso de inicio de sesión. La forma específica de representar la información de inicio de sesión como una cadena (OAuth, Session, LDAP, NTLM, huella digital, rueda de hámster) pertenece a la clase específica que representa las credenciales o la estrategia de inicio de sesión. No hay una necesidad imperiosa de eliminarlo. Este caso específico no es una preocupación transversal. Es específico para el caso de uso de inicio de sesión.
Laurent LA RIZZA
7

Como el registro a menudo se considera una preocupación transversal, sugeriría usar AOP para separar el registro de la implementación.

Dependiendo del idioma, usaría un interceptor o algún marco AOP (por ejemplo, AspectJ en Java) para realizar esto.

La pregunta es si esto realmente vale la pena. Tenga en cuenta que esta separación aumentará la complejidad de su proyecto mientras proporciona muy pocos beneficios.

Oliver Weiler
fuente
2
La mayor parte del código AOP que vi fue sobre el registro de cada paso de entrada y salida de cada método. Solo quiero registrar algunas partes de lógica de negocios. Entonces, tal vez sea posible registrar solo métodos anotados, pero AOP solo puede existir en lenguajes de script y entornos de máquinas virtuales, ¿verdad? En, por ejemplo, C ++ es imposible. Admito que no estoy muy contento con los enfoques de AOP, pero tal vez no haya una solución más limpia.
Aitch
1
@Aitch. "C ++ es imposible". : Si buscas en Google "aop c ++" encontrarás artículos al respecto. "... el código AOP que vi fue sobre el registro de cada paso de entrada y salida de cada método. Solo quiero registrar algunas partes de lógica de negocios". Aop le permite definir patrones para encontrar los métodos para modificar. es decir, todos los métodos del espacio de nombres "my.busininess. *"
k3b
1
El registro a menudo NO es una preocupación transversal, especialmente cuando desea que su registro contenga información interesante, es decir, más información que la contenida en un seguimiento de pila de excepción.
Laurent LA RIZZA
5

Esto suena bien Estás describiendo un decorador de registro bastante estándar. Tienes:

componente L (componente de registro del sistema)

Esto tiene una responsabilidad: registrar la información que se le pasa.

el componente A implementa I

Esto tiene una responsabilidad: proporcionar una implementación de la interfaz I (suponiendo que cumpla correctamente con SRP, es decir).

Esta es la parte crucial:

el componente D implementa I, decora / usa A, usa L para iniciar sesión

Cuando se dice de esa manera, suena complejo, pero mírelo de esta manera: el componente D hace una cosa: unir A y L.

  • El componente D no se registra; delega eso a L
  • El componente D no implementa I mismo; delega eso a A

La única responsabilidad que tiene el componente D es asegurarse de que L sea notificado cuando se usa A. Las implementaciones de A y L están ambas en otra parte. Esto es completamente compatible con SRP, además de ser un buen ejemplo de OCP y un uso bastante común de decoradores.

Una advertencia importante: cuando D usa su componente de registro L, debe hacerlo de una manera que le permita cambiar la forma en que está registrando. La forma más sencilla de hacer esto es tener una interfaz IL implementada por L. Luego:

  • El componente D usa un IL para iniciar sesión; se proporciona una instancia de L
  • El componente D usa una I para proporcionar funcionalidad; se proporciona una instancia de A
  • El componente B usa una I; se proporciona una instancia de D

De esa manera, nada depende directamente de nada más, lo que facilita su intercambio. Esto hace que sea fácil adaptarse a los cambios y burlarse de partes del sistema para que pueda realizar pruebas unitarias.

anaximandro
fuente
En realidad, solo conozco C # que tiene soporte de delegación nativa. Por eso escribí D implements I. Gracias por su respuesta.
Aitch
1

Por supuesto, es una violación de SRP ya que tiene una preocupación transversal. Sin embargo, puede crear una clase responsable de componer el registro con la ejecución de cualquier acción.

ejemplo:

class Logger {
   ActuallLogger logger;
   public Action ComposeLog(string msg, Action action) {
      return () => {
          logger.debug(msg);
          action();
      };
   }
}
Paul Nikonowicz
fuente
2
Voto negativo La tala es una preocupación transversal de hecho. Lo mismo ocurre con las llamadas al método de secuenciación en su código. Esa no es razón suficiente para reclamar una violación de SRP. Registrar la ocurrencia de un evento específico en su aplicación NO es una preocupación transversal. La FORMA en que estos mensajes se llevan a cualquier usuario interesado es, de hecho, una preocupación separada, y describir esto en el código de implementación ES una violación de SRP.
Laurent LA RIZZA
"llamadas de método de secuenciación" o composición funcional no es una preocupación transversal sino más bien un detalle de implementación. La responsabilidad de la función que creé es componer una declaración de registro, con una acción. No necesito usar la palabra "y" para describir lo que hace esta función.
Paul Nikonowicz
No es un detalle de implementación. Tiene un profundo efecto en la forma de su código.
Laurent LA RIZZA
Creo que estoy mirando a SRP desde la perspectiva de "QUÉ hace esta función", mientras que usted está mirando a SRP desde la perspectiva de "CÓMO lo hace esta función".
Paul Nikonowicz