¿Cómo llamar la atención del programador en ciertas condiciones?

13

Comencemos con un ejemplo.

Digamos que tengo un método llamado exportque depende en gran medida del esquema DB. Y por "depende en gran medida" quiero decir que sé que agregar una nueva columna a una tabla determinada a menudo (muy a menudo) conduce al exportcambio de método correspondiente (por lo general, también debe agregar el nuevo campo a los datos de exportación).

Los programadores a menudo se olvidan de cambiar el exportmétodo, ya que no está muy claro que incluso debas mirar esto. Mi objetivo es obligar al programador a tomar una decisión explícita para determinar si olvidó mirar el exportmétodo o simplemente no quiere agregar un campo a los datos de exportación. Y estoy buscando la solución de diseño para este problema.

Tengo dos ideas, pero ambas tienen fallas.

Contenedor inteligente "Leer todo"

Puedo crear el contenedor inteligente que se asegura de que todos los datos se lean explícitamente.

Algo como esto:

def export():
    checker = AllReadChecker.new(table_row)

    name    = checker.get('name')
    surname = checker.get('surname')
              checker.ignore('age') # explicitly ignore the "age" field

    result = [name, surname] # or whatever

    checker.check_now() # check all is read

    return result

Entonces, checkerafirma si table_rowcontiene otros campos que no fueron leídos. Pero todo esto parece un poco pesado y (tal vez) afecta el rendimiento.

Prueba de unidad "Verificar ese método"

Solo puedo crear la prueba de unidad que recuerda el último esquema de tabla y falla cada vez que se cambia la tabla. En ese caso, el programador vería algo así como "no olvides revisar el exportmétodo". Para ocultar el programador de advertencia, (o no lo haría, eso es un problema) verifique exporty corrija manualmente la prueba (ese es otro problema) agregando nuevos campos.

Tengo algunas otras ideas, pero son demasiado problemáticas para implementar o demasiado difíciles de entender (y no quiero que el proyecto se convierta en un rompecabezas).


El problema anterior es solo un ejemplo de la clase más amplia de problemas que encuentro de vez en cuando. Quiero vincular algunas piezas de código y / o infraestructura, por lo que cambiar una de ellas alerta inmediatamente al programador para que compruebe otra. Por lo general, tiene algunas herramientas simples, como extraer una lógica común o escribir pruebas unitarias confiables, pero estoy buscando la herramienta para casos más complejos: quizás conozco algunos patrones de diseño.

Vadim Pushtaev
fuente
¿Puedes generar exportsegún el esquema?
coredump
No se puede generar de forma automática, por eso debería pedirle al programador que mire el código y tome una decisión.
Vadim Pushtaev
¿Sería una solución agregar dos listas de nombres de campo (exportar y no exportar) a la clase de exportación, y tener una prueba de unidad que verifique que esas dos listas juntas abarquen el conjunto completo de campos de la base de datos?
Sjoerd Job Postmus
¿Se puede generar automáticamente una prueba que compruebe si export tiene todo lo que realmente necesita?
biziclop
1
¿Un comentario en el código fuente es una solución demasiado simplista? Por lo general, las cosas se pierden porque no hay recordatorio, un comentario solucionaría eso.
gbjbaanb

Respuestas:

11

Está en el camino correcto con su idea de prueba unitaria, pero su implementación es incorrecta.

Si exportse relaciona con el esquema y el esquema cambió, hay dos casos posibles:

  • O exportbien, todavía funciona perfectamente bien, ya que no se vio afectado por un ligero cambio en el esquema,

  • O se rompe.

En ambos casos, el objetivo de la compilación es rastrear esta posible regresión. Un conjunto de pruebas, ya sean pruebas de integración, pruebas de sistema, pruebas funcionales u otra cosa, aseguran que su exportprocedimiento funcione con el esquema actual , independientemente del hecho de que haya cambiado o no desde la confirmación anterior. Si esas pruebas pasan, genial. Si fallan, esta es una señal para el desarrollador de que puede haberse perdido algo, y una clara indicación de dónde buscar.

¿Por qué está mal tu implementación? Bueno, por varias razones.

  1. No tiene nada que ver con las pruebas unitarias ...

  2. ... y, en realidad, ni siquiera es una prueba.

  3. La peor parte es que arreglar la "prueba" requiere, bueno, cambiar realmente la "prueba", es decir, hacer una operación que no tiene relación alguna con la export.

En cambio, al hacer pruebas reales para el exportprocedimiento, se asegura de que el desarrollador arregle el export.


En términos más generales, cuando se encuentra con una situación en la que un cambio en una clase siempre o por lo general requiere un cambio en una clase completamente diferente, esta es una buena señal de que hizo mal su diseño y está violando el Principio de Responsabilidad Única.

Si bien hablo específicamente sobre las clases, también se aplica más o menos a otras entidades. Por ejemplo, un cambio en el esquema de la base de datos debería reflejarse automáticamente en su código, por ejemplo a través de generadores de código utilizados por muchos ORM, o al menos debería localizarse fácilmente: si agrego Descriptioncolumna a la Producttabla y no uso ORM o generadores de código, Al menos espero hacer un solo cambio dentroData.Product clase del DAL, sin la necesidad de buscar en toda la base de código y encontrar algunas ocurrencias de Productclase en, digamos, la capa de presentación.

Si no puede restringir razonablemente el cambio a una ubicación (ya sea porque se encuentra en un caso en el que simplemente no funciona o porque requiere una gran cantidad de desarrollo), entonces crea un riesgo de regresiones . Cuando cambio de clase A, y la clase Ben algún lugar de la base del código deja de funcionar, es una regresión.

Las pruebas reducen el riesgo de regresiones y, lo que es mucho más importante, le muestran la ubicación de una regresión. Es por eso que cuando sabe que los cambios en una ubicación causan problemas en una parte completamente diferente de la base del código, asegúrese de tener suficientes pruebas que generen alarmas tan pronto como aparezca una regresión en este nivel.

En todos los casos, evite confiar en tales casos solo en los comentarios. Algo como:

// If you change the following line, make sure you also change the corresponding
// `measure` value in `Scaffolding.Builder`.

nunca funciona No solo los desarrolladores no lo leerán en la mayoría de los casos, sino que a menudo terminará eliminado o alejándose de la línea en cuestión y será imposible de entender.

Arseni Mourzenko
fuente
Sí, esa "prueba" no es una prueba, es una especie de trampa, algo If you change the following line...que funciona automáticamente y no se puede ignorar. Nunca he visto a alguien usar esas trampas, de ahí la duda.
Vadim Pushtaev
1
El problema es que la clase Bno deja de funcionar, tal vez comienza a funcionar incorrectamente. Pero no puedo predecir el futuro y no puedo escribir pruebas que pronostiquen el futuro, así que trato de recordarle al desarrollador que escriba esa nueva prueba.
Vadim Pushtaev
@VadimPushtaev: cuando se trata de pruebas, "tal vez comienza a funcionar incorrectamente" y "deja de funcionar" es exactamente lo mismo. No puede predecir el futuro, pero debe poder conocer exactamente los requisitos y probar que su producto real cumple esos requisitos.
Arseni Mourzenko
Si, eso es una cosa. De hecho, quiero recordarle al desarrollador que piense en los nuevos requisitos . Solo quiero agitar una mano: “Hola, ¿estás seguro de que no te olvidas de la exportación? Pregúntale al gerente o algo, es un problema común ”.
Vadim Pushtaev
Gracias de todos modos, su respuesta me ayudó a organizar mis pensamientos y ahora tengo un cierto plan.
Vadim Pushtaev
3

Me parece que sus cambios no están especificados. Supongamos que vive en un lugar que no tiene códigos postales, por lo que no tiene una columna de código postal en la tabla de direcciones. Luego se introducen los códigos postales, o comienza a tratar con clientes que viven donde hay códigos postales, y debe agregar esta columna a la tabla.

Si el elemento de trabajo solo dice "agregar columna de código postal a la tabla de direcciones", entonces sí, la exportación se interrumpirá, o al menos no exportará códigos postales. Pero, ¿qué pasa con la pantalla de entrada que se utiliza para ingresar códigos postales? ¿El informe que enumera todos los clientes y sus direcciones? Hay toneladas de cosas que deben cambiar cuando agrega esta columna. El trabajo de recordar esas cosas es importante: no debe contar con artefactos de código aleatorio para "atrapar" a los desarrolladores para que recuerden.

Cuando se toma la decisión de agregar una columna significativa (es decir, no solo una búsqueda en caché total o desnormalizada u otro valor que no pertenece a una exportación o informe o en una pantalla de entrada), los elementos de trabajo creados deben incluir TODOS los cambios necesario: agregar la columna, actualizar el script de relleno, actualizar las pruebas, actualizar la exportación, los informes, las pantallas de entrada, etc. Es posible que no todos sean asignados (o recogidos) por la misma persona, pero todos deben hacerse.

A veces, los desarrolladores eligen agregar columnas ellos mismos como parte de la implementación de un cambio mayor. Por ejemplo, alguien puede haber escrito un elemento de trabajo para agregar algo a una pantalla de entrada y un informe, sin pensar en cómo se implementa. Si esto sucede mucho, deberá decidir si su sumador de elementos de trabajo necesita conocer los detalles de implementación (para poder agregar todos los elementos de trabajo correctos) o si los desarrolladores deben saber que el elemento de trabajo- La víbora a veces deja las cosas fuera. Si es lo último, entonces necesita una cultura de "no solo cambie el esquema; deténgase y piense qué más afecta".

Si hubo muchos desarrolladores y esto sucedió más de una vez, configuraría una alerta de registro para que el líder del equipo u otra persona de alto nivel recibiera alertas sobre los cambios de esquema. Esa persona podría buscar elementos de trabajo relacionados para lidiar con las consecuencias del cambio de esquema, y ​​si los elementos de trabajo faltaban, no solo podría agregarlos sino educar a quienes los dejaron fuera del plan.

Kate Gregory
fuente
2

Casi siempre, al crear una exportación, también creo una importación correspondiente. Como tengo otras pruebas que pueblan completamente la estructura de datos que se exporta, puedo crear una prueba de unidad de ida y vuelta que compara un original completamente poblado con una copia exportada y luego importada. Si son iguales, entonces la exportación / importación está completa; si no son iguales, la prueba de la unidad falla y sé que el mecanismo de exportación necesita actualizarse.

Pete Kirkham
fuente
esto puede verificar que cada objeto se guarde y se vuelva a cargar desde db. No cubre el caso de que un campo db existente no tenga un campo de objeto correspondiente.
k3b
@ k3b cierto. Al menos en mis sistemas, eso generalmente ocurre si algo ya no se usa pero no se ha eliminado del esquema, que está fuera del alcance del mecanismo de exportación: las pruebas unitarias para la persistencia de objetos verifican si cada campo de un objeto es persistió, pero no pruebo las columnas no utilizadas ya que eso no tendría un efecto observable en la función del sistema.
Pete Kirkham