2009-04-23 18 views
18

Me disculpo por la subjetividad de esta pregunta, pero estoy un poco atascado y agradecería alguna orientación y asesoramiento de cualquier persona que ha tenido que hacer frente a este problema antes:Cómo aplicar principios sólidos en un proyecto existente

Tengo (lo que se ha convertido) un gran proyecto RESTful API escrito en C# 2.0 y algunas de mis clases se han vuelto monstruosas. Mi principal clase de API es un ejemplo de esto, con varias docenas de miembros y métodos (probablemente acercándose a cientos). Como se puede imaginar, se está convirtiendo en una pequeña pesadilla, no solo para mantener este código sino que solo navegando el código se ha convertido en una tarea ardua.

Soy bastante nuevo a los principios sólidos, y yo soy gran fan de patrones de diseño (pero todavía estoy en esa etapa donde puedo aplicar ellos, pero no lo suficiente para saber cuándo uso ellos - en situaciones donde no es tan obvio).

Necesito dividir mis clases en tamaño, pero no sé cómo hacerlo. ¿Pueden mis compañeros de StackOverflow'ers sugerir maneras en que han tomado monolitos de código existentes y los han reducido a su tamaño?

Respuesta

23

Principio de responsabilidad única - Una clase solo debe tener un motivo para cambiar. Si tienes una clase monolítica, probablemente tenga más de un motivo para cambiar. Simplemente defina su único motivo para cambiar, y sea tan granular como razonable. Sugeriría comenzar "grande". Refactorizar un tercio del código en otra clase. Una vez que tenga eso, vuelva a comenzar con su nueva clase. Pasar directamente de una clase a 20 es demasiado desalentador.

Principio abierto/cerrado - Una clase debe estar abierta para extensión, pero cerrada para cambios. Donde sea razonable, marque sus miembros y métodos como virtuales o abstractos. Cada ítem debe ser relativamente pequeño por naturaleza y darle una funcionalidad básica o una definición de comportamiento. Sin embargo, si necesita cambiar la funcionalidad más tarde, podrá agregar el código, en lugar de cambiar el código para introducir funcionalidades nuevas/diferentes.

Liskov Substitution Principio - Una clase debe ser sustituible por su clase base. La clave aquí, en mi opinión, es hacer la herencia correctamente. Si tiene una gran declaración de caso, o dos páginas de declaraciones if que verifican el tipo derivado del objeto, entonces está violando este principio y necesita replantearse su enfoque.

Interface Segregation Principle - En mi opinión, este principio se parece mucho al principio de responsabilidad única. Solo se aplica específicamente a una clase/interfaz de alto nivel (o madura). Una forma de utilizar este principio en una clase grande es hacer que su clase implemente una interfaz vacía. Luego, cambie todos los tipos que usan su clase para que sean del tipo de la interfaz. Esto romperá tu código. Sin embargo, señalará exactamente cómo está consumiendo su clase. Si tiene tres instancias en las que cada una usa su propio subconjunto de métodos y propiedades, entonces ahora sabe que necesita tres interfaces diferentes. Cada interfaz representa un conjunto colectivo de funcionalidades y una razón para cambiar.

Dependency Inversion Principio - La alegoría de padres/hijos me hizo entender esto. Piensa en una clase para padres. Define el comportamiento, pero no se preocupa por los detalles sucios. Es confiable. Sin embargo, una clase para niños tiene que ver con los detalles y no se puede depender de ella porque cambia con frecuencia. Siempre quiere depender de los padres, las clases responsables y nunca a la inversa. Si tienes una clase para padres dependiendo de una clase para niños, obtendrás un comportamiento inesperado cuando cambies algo. En mi opinión, esta es la misma mentalidad de SOA. Un contrato de servicio define entradas, salidas y comportamiento, sin detalles.

Por supuesto, mis opiniones y entendimientos pueden ser incompletos o incorrectos. Sugeriría aprender de personas que dominan estos principios, como el tío Bob. Un buen punto de partida para mí fue su libro, Agile Principles, Patterns, and Practices in C#.Otro buen recurso fue Uncle Bob on Hanselminutes. Por ejemplo, como Joel and Jeff pointed out, estos son principios, no reglas. Deben ser herramientas para guiarte, no la ley de la tierra.

EDIT:

Acabo de encontrar estas SOLID screencasts la que se ven realmente interesante. Cada uno tiene aproximadamente 10-15 minutos de duración.

3

Será un proceso lento. Necesita leer el código e identificar las partes que no cumplen con los principios SOLIDOS y refactorizarlas en nuevas clases. El uso de un complemento VS como Resharper (http://www.jetbrains.com) lo ayudará con el proceso de refactorización.

Lo ideal es que tenga una buena cobertura de pruebas unitarias automatizadas para que pueda asegurarse de que sus cambios no introducen problemas con el código.

Más información

En la clase principal de la API, es necesario identificar los métodos que se relacionan entre sí y crear una clase que representa más específicamente las acciones que realiza el método.

p. Ej.

Digamos que tengo una clase de dirección con variables separadas que contienen número de calle, nombre, etc. Esta clase es responsable de insertar, actualizar, eliminar, etc. Si también necesitaba formatear una dirección de una manera específica para una dirección postal , Podría tener un método llamado GetFormattedPostalAddress() que devolviera la dirección formateada.

Alternativamente, podría refactorizar este método en una clase llamada AddressFormatter que tome una dirección en el constructor y tenga una propiedad Get llamada PostalAddress que devuelva la dirección formateada.

La idea es separar las diferentes responsabilidades en clases separadas.

+0

Soy un gran admirador de Resharper, y lo he usado durante mucho tiempo, y tengo cierta cobertura de prueba de unidad, pero no lo suficiente. ¿Hay algo más específico para pensar al hacer el refactor? – Ash

2

Lo que he hecho cuando se me presenta este tipo de cosas (y voy a admitir que no he usado principios SÓLIDOS antes, pero por lo poco que sé de ellas, suenan bien) es mirar la base de código existente desde un punto de vista de conectividad. Esencialmente, al observar el sistema, debería ser capaz de encontrar algún subconjunto de funcionalidad internamente altamente acoplado (muchas interacciones frecuentes) pero externamente débilmente acoplado (pocas interacciones poco frecuentes). Usualmente, hay algunas de estas piezas en cualquier gran base de código; son candidatos para la escisión. Esencialmente, una vez que haya identificado a sus candidatos, debe enumerar los puntos en los que están acoplados externamente al sistema como un todo. Esto debería darte una buena idea del nivel de interdependencia involucrado. Usualmente hay un poco de interdependencia involucrada.Evaluar los subconjuntos y sus puntos de conexión para la refactorización; con frecuencia (pero no siempre) terminan existiendo un par de refactorizaciones estructurales claras que pueden aumentar el desacoplamiento. Con un ojo en esas refactorizaciones, utilice los acoplamientos existentes para definir la interfaz mínima requerida para permitir que el subsistema funcione con el resto del sistema. Busque coincidencias en esas interfaces (¡con frecuencia encuentra más de lo que espera!). Y finalmente, implemente estos cambios que ha identificado.

El proceso suena terrible, pero en la práctica, en realidad es bastante sencillo. Eso sí, esta no es una hoja de ruta para llegar a un sistema completamente perfectamente diseñado (para eso, tendrías que empezar de cero), pero sin duda disminuirá la complejidad del sistema en su conjunto y aumentará la comprensión del código.

4

Hay un libro clásico de Martin Fowler - Refactoring: Improving the Design of Existing Code.

Allí se proporciona un conjunto de técnicas de diseño y ejemplo de decisiones para hacer su base de código existente sea más manejable y fácil de mantener (y que lo que los directores de sólidos se trata). Aunque hay algunas rutinas estándar en la refactorización, se trata de un proceso muy personalizado y una solución no se puede aplicar a todos los proyectos.

La prueba de unidades es uno de los pilares de la esquina para que este proceso tenga éxito. Debe cubrir su base de código existente con suficiente cobertura de código para asegurarse de no romper nada mientras lo cambia. En realidad, utilizar un marco moderno de pruebas de unidades con soporte de burla lo animará a diseñar mejor.

Existen herramientas como ReSharper (mi favorito) y CodeRush para ayudar con los tediosos cambios de código. Pero esas suelen ser cosas mecánicas triviales, por lo que tomar decisiones de diseño es un proceso mucho más complejo y no hay mucho soporte de herramientas. Usar diagramas de clase y UML ayuda. De eso empezaría, en realidad. Intenta darle sentido a lo que ya está allí y dale algo de estructura. Luego, desde allí puede tomar decisiones sobre la descomposición y las relaciones entre los diferentes componentes y cambiar su código en consecuencia.

Espero que esto ayude y feliz refactorización!

Cuestiones relacionadas