with switch introduce guru else if-statement nested refactoring anti-patterns

if-statement - switch - replace if else with polymorphism java



Necesita ideas de refactorizaciĆ³n para Arrow Anti-Pattern (7)

A juzgar por la descripción, una máquina de estado podría ser la mejor manera de enfrentarlo. Tener una variable enum para almacenar el estado actual, e implementar el procesamiento como un bucle sobre los registros, con un interruptor o declaraciones if para seleccionar la acción a tomar en función del estado actual y los datos de entrada. También puede enviar fácilmente el trabajo para separar funciones basadas en el estado usando punteros a funciones, también, si se está volviendo demasiado voluminoso.

Heredé un monstruo

Se está haciendo pasar por una aplicación .NET 1.1 que procesa archivos de texto que cumplen con los estándares de Pago de reclamo de atención médica (ANSI 835), pero es un monstruo. La información que se procesa se relaciona con reclamos de atención médica, EOB y reembolsos. Estos archivos consisten en registros que tienen un identificador en las primeras posiciones y campos de datos formateados según las especificaciones para ese tipo de registro. Algunos identificadores de registros son identificadores del segmento de control, que delimitan grupos de registros relacionados con un tipo particular de transacción.

Para procesar un archivo, mi pequeño monstruo lee el primer registro, determina el tipo de transacción que está a punto de llevarse a cabo y luego comienza a procesar otros registros en función del tipo de transacción que está procesando actualmente. Para hacer esto, usa un if anidado. Dado que hay una cantidad de tipos de registros, hay varias decisiones que deben tomarse. Cada decisión implica algún procesamiento y otras 2 o 3 decisiones que deben tomarse en base a decisiones anteriores. Eso significa que el anidado tiene muchos nidos. Ahí es donde radica mi problema.

Este anidado si tiene 715 líneas de largo. Si, eso es correcto Líneas de doscientos cincuenta y cinco adolescentes. No soy un experto en análisis de código, así que descargué un par de herramientas de análisis gratuitas y obtuve una clasificación de complejidad ciclomática de McCabe de 49. Me dicen que es un número bastante alto. Alto como en el recuento de polen en el área de Atlanta donde 100 es el estándar de alta y la noticia dice "El recuento de polen de hoy es de 1.523". Este es uno de los mejores ejemplos de Arrow Anti-Pattern que alguna vez tuve el privilegio de ver. En su punto más alto, la sangría tiene 15 pestañas de profundidad.

Mi pregunta es, ¿qué métodos sugerirías para refactorizar o reestructurar tal cosa?

He pasado un tiempo buscando ideas, pero nada me ha dado un buen punto de apoyo. Por ejemplo, sustituir un estado de guarda por un nivel es un método. Solo tengo uno de esos. Un nido abajo, catorce para ir.

Tal vez hay un patrón de diseño que podría ser útil. ¿La Cadena de mando sería una forma de abordar esto? Tenga en cuenta que debe permanecer en .NET 1.1.

Gracias por todas y cada una de las ideas.


A veces combino el patrón de estado con una pila.

Funciona bien para estructuras jerárquicas; un elemento padre sabe qué estado insertar en la pila para manejar un elemento hijo, pero un niño no tiene que saber nada sobre su elemento principal. En otras palabras, el niño no sabe cuál es el siguiente estado, simplemente señala que está "completo" y se quita de la pila. Esto ayuda a desacoplar los estados entre sí manteniendo las dependencias unidireccionales.

Funciona muy bien para el procesamiento de XML con un analizador SAX (el controlador de contenido simplemente empuja y muestra estados para cambiar su comportamiento a medida que se ingresan y salen los elementos). EDI debería prestarse a este enfoque también.


Una máquina de estado parece ser el lugar lógico para comenzar, y usar WF si puedes balancearla (parece que no puedes).

Aún puede implementar uno sin WF, solo tiene que hacerlo usted mismo. Sin embargo, pensar en ello como una máquina de estado desde el principio probablemente le dará una mejor implementación y luego creará un monstruo de procedimientos que verifica el estado interno en cada acción.

Diagrama tus estados, lo que causa una transición. El código real para procesar un registro se debe tener en cuenta y llamar cuando se ejecuta el estado (si ese estado en particular lo requiere).

Entonces, la ejecución de State1 llama a su "leer un registro", y luego, basándose en ese registro, pasa a otro estado.

El siguiente estado puede leer múltiples registros y llamar a las instrucciones de procesamiento de registros, luego la transición vuelve al estado1.


Una cosa que hago en estos casos es usar el patrón ''Método compuesto''. Ver la publicación del blog de Jeremy Miller sobre este tema. La idea básica es usar las herramientas de refactorización en su IDE para extraer pequeños métodos significativos. Una vez que hayas hecho eso, podrás refactorizar más y extraer clases significativas.



Acabo de tener algún código heredado en el trabajo esta semana que fue similar (aunque no tan grave) como lo que está describiendo.

No hay una sola cosa que te saque de esto. La máquina de estado puede ser la forma final que tome su código, pero eso no lo ayudará a llegar allí, ni debería decidir sobre esa solución antes de desenredar el desastre que ya tiene.

El primer paso que tomaría es escribir una prueba para el código existente. Esta prueba no es para mostrar que el código es correcto sino para asegurarse de que no haya roto algo cuando comience a refactorizar. Obtenga una gran cantidad de datos para procesar, aliméntelos al monstruo y obtenga los resultados. Esa es tu prueba de fuego. Si puede hacer esto con una herramienta de cobertura de código, verá que lo que prueba no cubre. Si puedes, construye algunos registros artificiales que también ejercerán este código, y repite. Una vez que sienta que ha hecho lo que puede con esta tarea, los datos de salida se convertirán en el resultado esperado para su prueba.

La refacturación no debe cambiar el comportamiento del código. Recuerda eso. Esta es la razón por la cual usted tiene información conocida y conjuntos de datos de salida conocidos para validar que no va a romper cosas. Esta es tu red de seguridad.

¡Ahora Refactor!

Un par de cosas que hice que encontré útiles:

Invertir declaraciones if

Un gran problema que tuve fue simplemente leer el código cuando no pude encontrar la declaración else correspondiente, noté que muchos bloques se veían así

if (someCondition) { 100+ lines of code { ... } } else { simple statement here }

Invirtiendo el if pudiera ver el caso simple y luego pasar al bloque más complejo sabiendo lo que el otro ya hizo. no es un gran cambio, pero me ayudó a comprenderlo.

Método de extracto

Lo usé mucho. Tome algún bloque complejo de múltiples líneas, atrápelo y déjelo a un lado en su propio método. esto me permitió ver más fácilmente dónde había duplicación de código.

Ahora, con suerte, no ha roto su código (la prueba todavía pasa, ¿verdad?), Y tiene un código de procedimiento más legible y mejor entendido. ¡Mira que ya está mejorado! Pero la prueba que escribiste antes no es lo suficientemente buena ... solo te dice que estás duplicando la funcionalidad (errores y todo) del código original, y esa es solo la línea en la que tenías cobertura, estoy seguro de que encontraría bloques de código que no puedes descifrar cómo golpear o que simplemente no pueden golpear (he visto ambos en mi trabajo).

Ahora, los grandes cambios en los que entran en juego todos los patrones de los grandes nombres es cuando comienzas a ver cómo puedes refactorizar esto de la manera correcta de OO. Hay más de una manera de despellejar a este gato, y esto involucrará múltiples patrones. Sin conocer los detalles sobre el formato de estos archivos que está analizando, solo puedo arrojar algunas sugerencias útiles que pueden ser o no las mejores soluciones.

Refactoring to Patterns es un gran libro para ayudar a explicar los patrones que son útiles en estas situaciones.

Estás tratando de comer un elefante, y no hay otra manera de hacerlo que un bocado a la vez. Buena suerte.


Comenzaría con el uso desinhibido del método de extracto. Si no lo tiene en su IDE de Visual Studio actual, puede obtener un complemento de terceros o cargar su proyecto en un VS más nuevo. (Intentará actualizar su proyecto, pero ignorará cuidadosamente esos cambios en lugar de registrarlos).

Dijiste que tienes un código con sangría de 15 niveles. Comience aproximadamente 1/2-salida, y Extraiga el Método. Si puede encontrar un buen nombre, úselo, pero si no puede, extraiga de todos modos. Partir por la mitad otra vez. No vas por la estructura ideal aquí; estás tratando de dividir el código en pedazos que encajarán en tu cerebro. Mi cerebro no es muy grande, así que seguiré rompiendo y rompiendo hasta que ya no duela.

A medida que avanza, busque nuevos métodos largos que parezcan ser diferentes al resto; hacer estos en nuevas clases. Solo usa una clase simple que solo tiene un método por ahora. Diablos, hacer que el método estético está bien. No porque pienses que son buenas clases, sino porque estás tan desesperado por alguna organización.

Regístrese a menudo sobre la marcha, para que pueda controlar su trabajo, comprender la historia más adelante, estar preparado para hacer un "trabajo real" sin necesidad de fusionarse y salvar a sus compañeros de equipo de la complicada fusión.

Eventualmente tendrá que regresar y asegurarse de que los nombres de los métodos sean buenos, que el conjunto de métodos que creó tenga sentido, limpie las nuevas clases, etc.

Si tiene una herramienta de método de extracción altamente confiable, puede escaparse sin buenas pruebas automatizadas. (Confío en VS en esto, por ejemplo.) De lo contrario, asegúrese de no estar rompiendo cosas, o terminará peor de lo que comenzó: con un programa que no funciona en absoluto.

Un compañero de pareja sería útil aquí.