2010-01-17 14 views
7

Tengo una aplicación muy centrada en los datos, escrita en Python/PyQt. Estoy planeando para hacer algunas refactorizaciones para separar realmente la IU del núcleo, principalmente porque todavía no existen pruebas reales, y eso claramente tiene que cambiar.Acerca de la refactorización

Ya hay algo de separación, y creo que he hecho bastantes cosas de la manera correcta, pero está lejos de ser perfecto. Dos ejemplos, para mostrar qué tipo de cosas me están molestando:

  • Cuando el usuario hace clic sobre la representación de un objeto de datos, el menú contextual que aparece es creado por el objeto de datos, aunque este objeto de datos (esencialmente la representación ORM de una fila de base de datos) claramente no debe tener nada que ver con la interfaz de usuario.

  • Cuando se escribe algo en la base de datos, pero la escritura falla (por ejemplo, porque el registro de la base de datos es bloqueado por un usuario diferente), se presenta al usuario el cuadro de mensaje clásico "reintentar/abortar". Este cuadro de diálogo es creado por el proveedor de datos *, aunque obviamente el proveedor no debería tener ninguna funcionalidad de UI. Claramente, el proveedor puede presentar una excepción o indicar una falla, y la IU puede detectar y actuar en consecuencia.

    * esa es la palabra que uso para el objeto que básicamente representa una tabla de base de datos y media entre entre sus objetos de datos y el motor de la base de datos; No estoy seguro de si eso es lo que suele llamarse un "proveedor"

no tengo experiencia con las pruebas, así que no fácilmente "sentir" los problemas de la capacidad de prueba o similares, pero antes Me meto en eso, hay que hacer una reorganización.

No hay una complicada lógica de negocios involucrada (principalmente es CRU D , sí, incluso sin la D), y esto sería mucho más reorganizador que reescritura, así que no estoy realmente preocupado por la introducción de errores de regresión como discutido en this question.

Mi plan es comenzar a refactorizar con la idea en mente de que la parte de la IU podría fácilmente ser arrancada para ser reemplazada por, digamos, una interfaz web o una interfaz basada en texto en lugar de la interfaz Qt. Por otro lado, Qt aún sería utilizado por el núcleo, porque el mecanismo de señal/ranura se utiliza en bastantes lugares, p. Ej. los objetos de datos emiten una señal changed para indicar, bueno, ya sabes qué.

Entonces, mi pregunta: ¿Es ese un enfoque factible para aumentar la capacidad de prueba y la mantenibilidad? ¿Alguna otra observación, especialmente con Python en mente?

Respuesta

1

He hecho la refactorización para el código heredado de gran tamaño con el objetivo de la separación UI/backend antes. Es divertido y gratificante

/alabanza;)

Cualquiera que sea el patrón que se llama o ya sea parte de MVC es muy valiosa para tener una muy clara capa API. Si es posible, puede enrutar todas las solicitudes de UI a través de un despachador que le ofrecería un mayor control sobre UI < -> Comunicación lógica, por ej. implementar el almacenamiento en caché, autenticación, etc.

Para visualizar:

[QT Frontend] 
[CLIs]    <=======> [Dispatcher] <=> [API] <==> [Core/Model] 
[SOAP/XMPRPC/Json] 
[API Test Suite] 

De esta manera

  • que es más fácil añadir banco de pruebas para probar sus APIs.
  • También hace que la adición de más IU sea uniforme y fácil.
  • API Documentación: Digamos que si desea documentar y exponer API a través de alguna interfaz RPC, es más fácil generar documentación API. Si alguien no está de acuerdo con la importancia de la documentación de la API, siempre puede ver la API de Twitter y su éxito.
  • puede importar rápidamente a nivel de API Python Shell y jugar con él

API diseño puede ocurrir mucho antes de empezar a programar para la capa API. Dependiendo de la aplicación, es posible que desee tomar la ayuda de paquetes como zinterfaces. Este es un enfoque general que tomo incluso cuando escribo aplicaciones muy pequeñas y nunca me ha fallado.

se fijan en

Una clara ventaja de este enfoque es después de tener a nivel de API y la nueva interfaz de usuario, ahora puede volver a código heredado y fijar/refactorearlo en quizás pasos más pequeños.

Otras sugerencias es que su suite de pruebas esté lista. Vea los consejos de la estrella al What are the first tasks for implementing Unit Testing in Brownfield Applications?.

+0

¡Esa es una idea interesante, me encanta! ¿No tiene alguna fuente o sugerencia sobre el despachador? Es decir. diferentes posibilidades de implementación, posibles advertencias, experiencias, etc.? – balpha

+0

Dispatcher pasaría todas las llamadas API a todos los subsistemas como administrador de caché, validador, correlacionador de URL ... Así que el despachador pasa la llamada API, contexto, argumentos a cada uno de los subsistemas (dependiendo de ciertas reglas) y finalmente invoca la API si necesario. Inicialmente, intentamos tener un diseño genérico de distribuidor para permitir el registro de subsistemas, pero más tarde se resolvió para el despachador que tenga conocimiento de cómo tratar con los subsistemas. Desafortunadamente, este marco para aplicaciones centradas en API es una fuente cerrada, pero el efecto es algo así como una pila de decoradores para cada API, pero de forma implícita. – Shekhar

+0

También se jugó un papel muy importante para determinar y pasar el contexto. – Shekhar

7

Si aún no lo ha hecho, lea "Trabajar eficazmente con código heredado" de Michael Feathers: se trata exactamente de este tipo de situaciones y ofrece una gran variedad de técnicas para manejarlo.

Un punto clave que él hace es tratar de realizar algunas pruebas antes de refactorizar. Dado que no es adecuado para pruebas unitarias, intente realizar algunas pruebas de extremo a extremo. Creo que Qt tiene su propio marco de prueba para manejar la GUI, así que agregue pruebas que manipulen la GUI contra una base de datos conocida y verifique el resultado. A medida que limpia el código, puede reemplazar o aumentar las pruebas de extremo a extremo con pruebas unitarias.

+1

No lo he leído todavía, pero lo he visto mencionado en bastantes lugares, así que definitivamente lo echaré un vistazo. Gracias. – balpha

2

Si desea extraer todas las partes de la GUI de la aplicación de todas las otras partes con el fin de todas las pruebas de la aplicación, se debe utilizar el Modelo-Vista-Presentador: Usted puede encontrar alguna explicación here y here.

Con este modelo, todos los servicios de su aplicación utilizan los presentadores mientras que solo el usuario puede interactuar directamente con las vistas (partes de la GUI). Los presentadores están administrando las vistas desde la aplicación. Tendrá una parte de GUI independiente de su aplicación en caso de que desee modificar el marco de la GUI. Lo único que tendrá que modificar son los presentadores y las vistas en sí.

Para las pruebas GUI que desee, solo tiene que escribir unit tests para presentadores. Si desea probar los usos de GUI, debe crear integration tests.

Espero que ayude!

+0

Sí, soy consciente de ese patrón y eso es más o menos a lo que me dirijo. En realidad, hasta cierto punto eso es lo que ya hice. Así que tomo su respuesta como una pista de que estoy tomando la dirección correcta :-) Gracias – balpha

+0

De nada, balpha. Creo que esta es la mejor manera para que tu aplicación sea más comprobable. –

1

Un punto, no se ha mencionado hasta ahora y realmente no responde la pregunta, pero muy importante: siempre que sea posible, debe poner la prueba ahora, antes de comenzar a refactorizar. El punto principal de la prueba es detectar si se rompe algo.

La refactorización es algo en lo que es realmente valioso ver exactamente dónde cambió el efecto de alguna operación y donde la misma llamada produce un resultado diferente. De eso se trata toda esta prueba: quieres ver si rompes algo, quieres ver todos los cambios involuntarios.

Por lo tanto, realice pruebas ahora para todas las piezas que aún deben producir los mismos resultados después de la refactorización. Las pruebas no son para el código perfecto que permanecerá igual para siempre, las pruebas son para el código que debe cambiar, el código que debe modificarse, el código que se refactorizará. Las pruebas están ahí para asegurarse de que su refactorización realmente haga lo que pretende que haga.

+0

Tienes razón, no se ha mencionado explícitamente, gracias. Es uno de los puntos clave de "Trabajar eficazmente con el código heredado" (que lo leí mientras tanto), y un enfoque interesante es la respuesta Stack Overflow a la que se vincula Shekhar en su respuesta. – balpha