Red de conocimientos turísticos - Conocimientos sobre calendario chino - Descripción general de la refactorización de código

Descripción general de la refactorización de código

Refactoring () mejora la calidad y el rendimiento del software ajustando el código del programa, haciendo que el patrón de diseño y la arquitectura del programa sean más razonables y mejorando la escalabilidad y la mantenibilidad del software. Algunas personas pueden preguntar, ¿por qué no dedicar más tiempo al diseño al comienzo del proyecto, pero dedicar tiempo a la refactorización más adelante? Debes saber que no existe un diseño lo suficientemente perfecto como para prever cambios en el futuro, ni un diseño lo suficientemente flexible como para adaptarse a cualquier expansión. Los diseñadores de sistemas a menudo sólo pueden controlar los proyectos que están a punto de iniciar desde la dirección general, pero no pueden conocer cada detalle. La segunda cosa que nunca cambia es el cambio. Los usuarios que plantean requisitos a menudo comienzan a juzgar el diseño del sistema después de que se forma el software. Después de todo, el personal no es un dios profético y es inevitable que los cambios en las funciones conduzcan a ajustes de diseño. Por lo tanto, las pruebas son lo primero y cada vez más personas adoptan la refactorización continua como un buen hábito de desarrollo. Las pruebas y la refactorización, como la berma del río Amarillo, se han convertido en un arma mágica para garantizar la calidad del software. Cambiar la forma en que se implementa el sistema sin cambiar su funcionalidad. ¿Por qué hacer esto? ¿Es un desperdicio de la inversión del cliente invertir energía no para satisfacer las necesidades que les interesan, sino sólo para cambiar la forma en que se implementa el software?

La importancia de la refactorización comienza con el ciclo de vida del software. El software es diferente de los productos ordinarios. Es un producto intelectual y no tiene una forma física específica. Es imposible que un software sufra desgaste físico y los botones de la interfaz nunca sufrirán un mal contacto debido a demasiadas pulsaciones. Entonces, ¿por qué no se puede utilizar un software para siempre después de su producción?

Solo hay un factor que amenaza la vida del software: los cambios en los requisitos. Siempre se produce un software para resolver una necesidad específica. A medida que evolucionan los tiempos, el negocio del cliente también cambia. Algunas demandas son relativamente estables, otras cambian más drásticamente y algunas han desaparecido o se han transformado en otras demandas. En este caso, el software debe modificarse en consecuencia.

Teniendo en cuenta factores como el coste y el tiempo, por supuesto no todos los cambios de demanda deben implementarse en el sistema de software. Pero, en general, el software debe adaptarse a los cambios en la demanda para mantener su vitalidad.

Esto crea un fenómeno negativo: los productos de software se fabrican inicialmente con un diseño cuidadoso y una buena arquitectura. Sin embargo, con el paso del tiempo y los cambios en las necesidades, las funciones originales deben modificarse constantemente y agregarse nuevas funciones, e inevitablemente surgen algunos defectos que deben modificarse. Para implementar cambios, es inevitable violar la estructura de diseño original. Después de un período de tiempo, la arquitectura del software queda plagada de agujeros. Hay cada vez más errores, cada vez es más difícil de mantener y los nuevos requisitos son cada vez más difíciles de implementar. La arquitectura del software pierde gradualmente su capacidad para soportar nuevos requisitos y se convierte en una restricción. Al final, el costo de desarrollar nuevos requisitos excederá el costo de desarrollar un nuevo software. Aquí es cuando la vida útil de este sistema de software llega a su fin.

La refactorización puede evitar este fenómeno en la mayor medida posible. Una vez que el sistema se desarrolla hasta una determinada etapa, el método de reconstrucción se utiliza para reorganizar la estructura interna sin cambiar las funciones externas del sistema. A través de la reconstrucción, la estructura del sistema se ajusta constantemente para que el sistema siempre tenga una gran adaptabilidad a los cambios en los requisitos.

Los siguientes objetivos se pueden lograr mediante la refactorización:

·Corrección y mejora continua del diseño de software

La refactorización y el diseño son complementarios entre sí. Con la refactorización, todavía es necesario hacer un diseño previo, pero no es necesario que sea el diseño óptimo. Sin la refactorización, el diseño del programa se corromperá gradualmente y se desconectará cada vez más. Es un caballo salvaje que no se puede controlar. La refactorización consiste en realidad en organizar el código y devolver todo el código con tendencias divergentes a su lugar original.

·

Martin Flower tiene un dicho clásico en "Reconstrucción": Cualquier tonto puede escribir un programa que una computadora pueda entender, pero sólo escribiendo un programa que sea fácil de entender para los humanos. entender Es un excelente programador. El autor tiene sentimientos profundos al respecto. Algunos programadores siempre pueden escribir código ejecutable rápidamente, pero los nombres oscuros en el código marean a la gente y necesitan agarrarse de los reposabrazos de sus sillas. Él viene a hacerse cargo de ese código. ¿No quieres ser un desertor?

El ciclo de vida del software a menudo requiere varios lotes de programadores para mantenerlo y, a menudo, ignoramos a estos recién llegados.

Para que el código sea fácil de entender para otros, es necesario hacer muchas cosas adicionales al implementar funciones de software, como un diseño claro y comentarios concisos, de los cuales la denominación también es un aspecto importante. Una buena forma es utilizar nombres metafóricos, es decir, nombrar en función de las funciones implementadas por el objeto, utilizando un enfoque visual o antropomórfico. Una buena actitud es nombrar cada elemento del código como un recién nacido. Nombra la tendencia a la paranoia. Si te pueden poner este apodo, te sentirás profundamente honrado.

Para aquellos nombres que hacen que la gente se sienta confundida o incluso engañosa, es necesario reorganizarlos de manera decisiva y drástica, ¡y nunca mostrar misericordia!

·Ayuda a descubrir defectos ocultos en el código

Confucio dijo: Revisa el pasado y aprende lo nuevo. La refactorización del código te obliga a obtener una comprensión más profunda del código que escribiste originalmente. A menudo escribo un programa, pero luego no entiendo la lógica de mi programa. Me asusté, pero luego descubrí que este síntoma es en realidad un resfriado que muchos programadores padecen a menudo. Cuando esto le suceda, podrá profundizar su comprensión del diseño original refactorizando el código, descubriendo los problemas y peligros ocultos y creando un código mejor.

·A largo plazo, ayudará a mejorar la eficiencia de la programación

Cuando descubres que resolver un problema se vuelve extremadamente complicado, a menudo no es el problema en sí, sino el fallo que utilizas. Un diseño deficiente a menudo conduce a una codificación inflada.

Mejorar el diseño, aumentar la legibilidad y reducir los defectos son todos factores para estabilizar nuestra posición. Un buen diseño es la mitad de la batalla. Detener y mejorar el diseño mediante la reconstrucción puede ralentizar el proceso actual, pero no se puede subestimar la ventaja de avanzar tarde. El nuevo funcionario asumió el cargo con tres cosas en mente y comenzó una nueva era. Trabajó sin parar y trabajó horas extras. Un ejército masivo con miles de códigos envueltos en la pasión de los programadores y Ming Jin que hacía clic en el teclado luchaban por seguir adelante. Era como un bambú roto, atacando la ciudad. El suelo apunta directamente a la Mansión Huanglong.

El gerente de desarrollo es el comandante en jefe de este vasto equipo de codificadores. Él es responsable del destino de este equipo. Cuando el duque Huan de Qi se paró en la cima de la montaña y vio al equipo. entrenado por Guan Zhong avanzando uniformemente, suspiró. Si tengo un ejército así, ¿por qué debería tener miedo de no ganar? . Pero desafortunadamente, el equipo en tus manos era originalmente solo un rezagado. Recluta tropas y crece continuamente a medida que avanza, por lo que es inevitable que el equipo se deforme. Cuando el gerente de desarrollo nota que el equipo está deformado, puede ser el momento de resistir la tentación de conquistar la colina que se avecina y detenerse y reorganizar el equipo.

Kent Beck propuso el término mal olor a código, que tiene el mismo significado que la deformación del equipo que propusimos. ¿Cuáles son las señales de la deformación del equipo? Los siguientes síntomas del código son una fuerte señal de deformación del equipo:

·Códigos duplicados en el código

China tiene 118 fabricantes de vehículos, un número casi igual al de Estados Unidos, Japón , y El número de fabricantes de automóviles en Europa combinados, pero la producción anual del país es menor que la de una gran empresa automovilística extranjera. La construcción duplicada sólo conducirá a ineficiencia y desperdicio de recursos.

El código del programa no se puede construir repetidamente. Si hay el mismo bloque de código en la misma clase, refínelo en un método independiente de la clase. Si hay el mismo código en diferentes clases, sepárelo. . Destilar en una nueva clase y nunca duplicar código.

·Clases demasiado grandes y métodos demasiado largos

Las clases demasiado grandes son a menudo el resultado de una abstracción de clases irrazonable. Una abstracción de clases irrazonable reducirá la tasa de reutilización del código. El método consiste en formar estados vasallos en un reino similar. Si los estados vasallos se vuelven demasiado grandes, inevitablemente sacudirán el poder centralizado. Si un método demasiado largo contiene una lógica demasiado compleja, la probabilidad de error se disparará, la legibilidad caerá en picado y la solidez de la clase se verá afectada fácilmente. Cuando ve un método que es demasiado largo, necesita encontrar una manera de dividirlo en varios métodos más pequeños para poder dividir y conquistar.

·Un pequeño cambio requiere un conjunto completo de modificaciones

Cuando descubres que modificas una función pequeña o agregas una función pequeña, se desencadena un terremoto en el código, tal vez tu diseño. El concepto de abstracción no es lo suficientemente ideal y el código funcional está demasiado disperso.

·Se requiere comunicación excesiva entre clases

La clase A necesita llamar a demasiados métodos de la clase B para acceder a los datos internos de B. La relación entre estas dos clases parece un poco ambigua. estas dos categorías deben estar juntas y no separadas.

·Cadena de información sobreacoplada

La informática es tal que cree que cualquier problema puede resolverse añadiendo una capa intermedia, por lo que a menudo se añadirán demasiadas capas intermedias en el proceso.

Si ve en el código que necesita obtener cierta información, necesita un método de una clase para llamar a un método de otra clase, conectado capa por capa, como un oleoducto. Esto a menudo se debe a demasiadas capas de conexión. Es necesario verificar si hay una capa intermedia extraíble o si se puede proporcionar un método de llamada más directo.

·Revolución Geolishantougan

Si encuentra que hay dos clases o dos métodos con nombres diferentes pero funciones similares o idénticas, encontrará que a menudo se debe al desarrollo causado por Coordinación insuficiente del equipo. El autor una vez escribió una clase de procesamiento de cadenas bastante útil, pero como no notificó a otros miembros del equipo a tiempo, más tarde descubrió que en realidad había tres clases de procesamiento de cadenas en el proyecto. Los recursos revolucionarios son preciosos y no debemos luchar por la revolución de forma independiente.

·Diseño imperfecto

En un proyecto de alarma comparativo que el autor acaba de completar, le pedí a A'Zhu que desarrollara un módulo de alarma que envía mensajes a plataformas de SMS y plataformas de voz designadas. A través de Socket. y el complemento de alarma del cliente para enviar información de mensajes de alarma, A'Zhu completó esta tarea de manera brillante. Más tarde, los usuarios plantearon la necesidad de una comparación en tiempo real, es decir, requerir que el sistema de terceros envíe una solicitud al sistema de alarma de comparación en forma de mensaje, y el sistema de alarma de comparación recibe y responde a esta solicitud. Esto requiere el uso de comunicación de mensajes Socket. Dado que el diseño original no separaba el módulo de comunicación de mensajes, el código desarrollado por A'Zhu no se puede reutilizar. Más tarde, ajusté el diseño a tiempo y agregué un módulo de envío y recepción de mensajes, de modo que todas las comunicaciones externas del sistema reutilizaron este módulo y el diseño general del sistema se volvió más razonable.

Todos los sistemas tienen diseños más o menos imperfectos. Puede que al principio no los notes, pero poco a poco se irán haciendo evidentes. En este momento, la única salida es tener el coraje de cambiarlos.

·Falta de comentarios necesarios

Aunque muchos libros de ingeniería de software suelen recordar a los programadores que eviten comentarios excesivos, esta preocupación no parece necesaria. Los programadores suelen estar más interesados ​​en la implementación funcional que en los comentarios de código, porque lo primero puede aportar una mayor sensación de logro, por lo que los comentarios de código a menudo no son demasiados, sino muy pocos y demasiado simples. La pendiente de la curva de la memoria humana es terriblemente pronunciada. Cuando vuelves a agregar notas después de un período de tiempo, es fácil olvidar las palabras y dejar de hablar.

Una vez vi los comentarios del código de Microsoft en Internet. El nivel de detalle es asombroso y también me di cuenta de una experiencia del éxito de Microsoft. Cuando aprende una nueva tecnología que puede aumentar drásticamente su productividad, siempre es difícil ver dónde no funcionará. Normalmente lo aprendes en un contexto específico, que suele ser un proyecto. En este caso es difícil ver qué haría que esta nueva tecnología fuera ineficaz o incluso perjudicial. Hace diez años, lo mismo ocurría con la tecnología de objetos. En ese momento, si alguien me preguntara “cuándo no usar objetos”, me resultaría difícil responder. No es que piense que el objeto es perfecto y no tiene limitaciones (estoy totalmente en contra de esta actitud ciega), sino que, si bien conozco sus beneficios, realmente no sé cuáles son sus limitaciones.

Ahora, la misma situación existe para la reconstrucción. Conocemos los beneficios de la refactorización y sabemos que la refactorización puede traer cambios fáciles a nuestro trabajo. Pero todavía no hemos adquirido suficiente experiencia para ver sus limitaciones.

Esta sección es más corta de lo que me hubiera gustado. Dejémoslo así. A medida que más personas aprendan técnicas de refactorización, también aprenderemos sobre ellas. Debería intentar refactorizar para obtener los beneficios que proporciona, pero al mismo tiempo, también debe monitorear el proceso y buscar problemas que puedan surgir mediante la refactorización. Háganos saber qué problemas está experimentando. A medida que aprendamos más sobre la refactorización, encontraremos más soluciones y tendremos una idea más clara de qué problemas son realmente difíciles de resolver.

·Bases de datos

Un área donde la "reconstrucción" a menudo causa problemas es la base de datos. La gran mayoría de los programas comerciales están estrechamente vinculados con el esquema de la base de datos (estructura de la tabla de la base de datos), que es una de las razones por las que el esquema de la base de datos es tan difícil de modificar. Otra razón es la migración de datos.

Incluso si coloca su sistema en capas con mucho cuidado para minimizar las dependencias entre el esquema de la base de datos y el modelo de objetos, un cambio en el esquema de la base de datos aún lo obligará a migrar todos los datos, lo que puede ser un proceso largo y engorroso.

En las "bases de datos sin objetos", una forma de resolver este problema es insertar una capa de separación entre el modelo de objetos y la capa del modelo de base de datos, que puede aislar los cambios en los dos modelos. Al actualizar un modelo, no es necesario actualizar otro modelo al mismo tiempo. Solo necesita actualizar la capa de separación mencionada anteriormente. Una capa de separación de este tipo aumentará la complejidad del sistema, pero puede brindarle mucha flexibilidad. Esta capa de separación es importante incluso sin refactorizar si tiene varias bases de datos al mismo tiempo o si el modelo de la base de datos es complejo y difícil de controlar.

No es necesario insertar la capa separadora inicialmente; puede generarla cuando descubra que el modelo de objetos se ha vuelto inestable. De esta manera podrá encontrar el mejor apalancamiento para su cambio.

Para los desarrolladores, las bases de datos de objetos pueden ayudar y obstaculizar. Algunas bases de datos orientadas a objetos proporcionan migración automática entre diferentes versiones de objetos, lo que reduce la carga de trabajo durante la migración de datos, pero aún provocará una cierta pérdida de tiempo. Si la migración de datos entre bases de datos no es automática, debe completar la migración usted mismo, lo cual supone una enorme carga de trabajo. En este caso debes prestar más atención a los cambios en la estructura de datos dentro de las clases. Aún puedes transferir de forma segura el comportamiento de las clases, pero debes tener cuidado al transferir campos. Antes de transferir los datos, primero debe utilizar descriptores de acceso para crear la ilusión de que "los datos han sido transferidos". Una vez que sepas con certeza dónde deben estar tus datos, puedes moverlos allí de una sola vez. Lo único que es necesario modificar en este momento son los descriptores de acceso, lo que también reduce el riesgo de errores.

·Cambio de interfaces

Otra cosa importante acerca de los objetos es que te permiten modificar la implementación y la interfaz de un módulo de software por separado. Puede modificar de forma segura las partes internas de un objeto sin afectar a los demás, pero tenga especial cuidado con las interfaces: si se modifica una interfaz, puede pasar cualquier cosa.

Una cosa que siempre ha preocupado a la refactorización es que muchas técnicas de refactorización modifican la interfaz. Todo lo que hace una técnica de refactorización simple como Rename Method (273) es modificar la interfaz. ¿Qué impacto tendrá esto en el preciado concepto del embalaje?

Si todas las llamadas a una función están bajo tu control, no habrá ningún problema incluso si cambias el nombre de la función. Incluso si se enfrenta a una función pública, siempre que pueda obtener y modificar a todos los llamadores, puede cambiar el nombre de la función de forma segura. La modificación de la interfaz solo se convertirá en un problema cuando la interfaz que debe modificarse sea utilizada por un código que "no se puede encontrar y no se puede modificar incluso si se encuentra". Si ese fuera el caso, diría: esta interfaz es una "interfaz publicada", un paso más que una interfaz pública. Una vez que se lanza una interfaz, ya no puede modificarla de forma segura simplemente modificando a la persona que llama. Necesitas un programa un poco más complicado.

Esta idea cambió nuestro problema. La pregunta ahora es: ¿Cómo lidiar con las técnicas de refactorización que requieren modificación de la "interfaz publicada"?

En resumen, si la refactorización cambia la interfaz publicada, debe mantener tanto la interfaz antigua como la nueva hasta que todos sus usuarios tengan tiempo de reaccionar al cambio. Afortunadamente esto no es demasiado difícil. Generalmente puedes encontrar una manera de organizar las cosas para que la interfaz anterior siga funcionando. Intente hacer esto: deje que la interfaz anterior llame a la nueva interfaz. Cuando desee cambiar el nombre de una función, deje la función anterior y deje que llame a la nueva función.

Nunca copie el código de implementación de la función, ya que eso le hará caer en el atolladero del "código duplicado" y le resultará difícil salir. También debe utilizar la función de desuso proporcionada por Java para marcar la interfaz antigua como obsoleta. De esa manera las personas que llamen lo notarán.

Un buen ejemplo de este proceso son las clases contenedoras de Java (clases de colección). Los nuevos contenedores de Java 2 reemplazan algunos de los contenedores originales. Cuando se lanzaron los contenedores Java 2, JavaSoft puso mucho esfuerzo en brindar a los desarrolladores una ruta de migración fluida.

El enfoque de "mantener la interfaz antigua" normalmente funciona, pero es molesto. Al menos por un tiempo, debes crear y mantener algunas funciones adicionales. Complican la interfaz y dificultan su uso. Afortunadamente tenemos otra opción: no publicar la interfaz. Por supuesto, no estoy diciendo que lo prohíba por completo, porque obviamente hay que publicar algunas interfaces. Si está creando API para uso externo, como lo hace Sun, debe publicar la interfaz. La razón por la que digo que intente no publicar es porque a menudo veo que algunos equipos de desarrollo exponen demasiadas interfaces. Una vez vi un equipo de tres trabajando así: cada persona publicaba una interfaz públicamente para otras dos. Esto les obliga a ir y venir frecuentemente para mantener la interfaz. De hecho, podrían haber entrado directamente a la biblioteca y modificado la parte que gestionaban, lo que hubiera sido mucho más sencillo. Los equipos que enfatizan demasiado la "propiedad del código" a menudo cometen este error. Publicar interfaces es útil, pero tiene un costo. Así que no publiques interfaces a menos que sea realmente necesario. Esto puede significar cambiar su concepto de propiedad del código para que todos puedan modificar el código de otras personas para adaptarlo a los cambios de la interfaz. Generalmente es una buena idea hacer esto con la programación en pares.

No publique interfaces prematuramente. Revise su política de propiedad de código para que la refactorización sea más fluida.

También hay un problema especial acerca de "modificar la interfaz" en Java: agregar una excepción en la cláusula throws. Esta no es una modificación de la firma, por lo que no puedes ocultarla con delegación. Pero si el código de usuario no realiza los cambios correspondientes, el compilador no lo dejará pasar. Este problema es difícil de resolver. Puede elegir un nuevo nombre para esta función (excepción controlada) para convertirla en una excepción no marcada (excepción no marcada). También puede generar una excepción no verificada, pero luego perderá la capacidad de verificar. Si hace esto, puede advertir a la persona que llama que esta excepción no comprobada se convertirá más adelante en una excepción comprobada. Esto les da tiempo para agregar el manejo de esta excepción a su código. Por esta razón, siempre me gusta definir una excepción de superclase para todo el paquete (al igual que SQLException de java.sql) y asegurarme de que todas las funciones públicas solo declaren esta excepción en su propia cláusula throws. De esta manera, puedo definir excepciones de subclase como quiera sin afectar a la persona que llama, porque la persona que llama siempre solo conocerá la excepción de superclase más general.

·Cambios de diseño que son difíciles de lograr mediante la refactorización

¿Se pueden eliminar todos los errores de diseño mediante la refactorización? ¿Existen algunas decisiones de diseño centrales que no se pueden cambiar mediante la refactorización? Ésta es un área en la que nuestras estadísticas están incompletas. Por supuesto, hay situaciones en las que podemos refactorizar de manera muy eficaz, lo que a menudo nos sorprende, pero ciertamente hay lugares donde la refactorización es difícil. Por ejemplo, en un proyecto, es difícil (pero aún posible) reconstruir un "sistema construido sin requisitos de seguridad" en un "buen sistema de seguridad".

Mi enfoque en este caso es "imaginar primero la situación de reconstrucción". Al considerar candidatos de diseño, me pregunto: ¿Qué tan difícil sería refactorizar un diseño en otro? Si parece simple, no tengo que preocuparme demasiado por tomar la decisión correcta, así que elijo el diseño más simple, incluso si no cubre todas las necesidades potenciales.

Pero si no veo una refactorización fácil de antemano, me esforzaré más en el diseño. Pero he descubierto que esto rara vez sucede.

·¿Cuándo no refactorizar?

A veces no deberías refactorizar nada, como cuando deberías reescribir todo tu código. A veces el código existente es tan confuso que refactorizarlo es más fácil que escribirlo desde cero. Es una decisión difícil de tomar y admito que no tengo buenas pautas sobre cuándo abandonar la refactorización.

Una señal clara de reescribir (en lugar de refactorizar) es que el código existente simplemente no funciona correctamente. Puede intentar hacer algunas pruebas y luego descubrir que el código está lleno de errores y no funciona de manera estable. Recuerde, antes de refactorizar, el código debe al menos funcionar correctamente en la mayoría de las situaciones.

Un compromiso es refactorizar "software grande" en "componentes pequeños bien encapsulados". Luego puede tomar una decisión de "refactorizar o reconstruir" componente por componente. Este es un enfoque prometedor, pero todavía no tengo datos suficientes para escribir buenas directrices. Sin duda, esta sería una buena dirección para un importante sistema antiguo.

Además, debes evitar la refactorización si el proyecto se acerca a su fecha límite. En este momento, la productividad obtenida con el proceso de refactorización solo se obtendrá después de que haya pasado la fecha límite y, para entonces, el tiempo haya pasado. Ward Cunningham tiene una buena perspectiva al respecto. Calificó de "deuda" el trabajo de reestructuración inacabado. Muchas empresas necesitan deuda para funcionar de manera más eficiente. Pero hay que pagar intereses cuando se pide dinero prestado. Los "gastos generales adicionales de mantenimiento y expansión" causados ​​por un código demasiado complejo son los intereses. Puedes permitirte una cierta cantidad de interés, pero si es demasiado alto, quedarás aplastado. Es importante administrar bien su deuda y siempre debe reestructurarla para pagar una parte.

Si el proyecto está muy cerca de la fecha límite, no debes distraerte con la refactorización porque no hay tiempo. Sin embargo, la experiencia de múltiples proyectos muestra que la refactorización realmente puede mejorar la productividad. Si se le acaba el tiempo, normalmente significa que debería haber refactorizado antes. La refactorización tiene una misión especial: ella y el diseño se complementan. Cuando aprendí a programar por primera vez, simplemente me sumergí en la escritura de programas y los desarrollé de manera confusa. Sin embargo, pronto descubrí que el "diseño inicial" podría ayudarme a ahorrar el alto costo del retrabajo. Así que rápidamente reforcé este estilo "prediseñado". Mucha gente considera el diseño como un eslabón clave en el desarrollo de software y ve la programación como una simple mano de obra mecánica de bajo nivel. Piensan que el diseño es como dibujar dibujos de ingeniería y la codificación es como la construcción. Pero hay que saber que existe una gran diferencia entre el software y el equipo real. El software es más maleable y es enteramente un producto del pensamiento. Como dijo Alistair Cockburn: "Con el diseño puedo pensar más rápido, pero está lleno de pequeños agujeros". 』

Existe la opinión de que la refactorización puede convertirse en un sustituto del "prediseño". Esto significa que no tiene que hacer ningún diseño en absoluto, simplemente comienza a codificar de acuerdo con la idea original, hace que el código funcione de manera efectiva y luego lo refactoriza para darle forma. De hecho, este enfoque realmente funciona. De hecho, he visto a personas hacer esto y terminar con un software bien diseñado. Programación extrema (Programación extrema) Beck, los partidarios de XP abogan firmemente por este enfoque.

Aunque, como se mencionó anteriormente, solo el uso de la refactorización también puede lograr resultados, esta no es la forma más efectiva. Sí, incluso los entusiastas de la programación extrema hacen diseños previos. Usarán tarjetas CRC o algo similar para probar diferentes ideas antes de llegar a la primera solución aceptable, luego podrán comenzar a codificar y luego refactorizar. El punto clave es: la refactorización cambia el papel del "prediseñado". Sin refactorizar, debe asegurarse de que el "diseño previo" sea correcto, lo cual supone demasiada presión. Esto significa que cualquier cambio futuro en el diseño original será muy costoso. Por lo tanto, es necesario dedicar más tiempo y energía al diseño inicial para evitar revisiones posteriores.

Si eliges refactorizar, el enfoque del problema cambia. Todavía haces el diseño previo, pero no necesariamente tienes que encontrar la solución correcta. En este momento sólo necesita encontrar una solución suficientemente razonable.

Usted sabe con certeza que a medida que implemente esta solución inicial, su comprensión del problema se profundizará gradualmente y es posible que descubra que la solución óptima es diferente de lo que imaginó originalmente. Mientras tengas el arma de refactorización, no será un problema, porque la refactorización hace que futuras modificaciones ya no sean costosas.

Este cambio ha llevado a un resultado importante: el diseño de software ha dado un gran paso hacia la simplicidad. Cuando no he usado la refactorización en el pasado, siempre me esfuerzo por encontrar soluciones flexibles. Cada requisito me hace sospechar: ¿A qué cambios conducirá este requisito durante la vida del sistema? Dado que cambiar el diseño es muy costoso, quería crear una solución que fuera lo suficientemente flexible y robusta para soportar cualquier cambio en los requisitos que pudiera prever. El problema es que el coste de crear una solución flexible es difícil de estimar. Las soluciones flexibles son mucho más complejas que las soluciones simples, por lo que el software resultante suele ser más difícil de mantener; aunque va en la dirección que imaginé, también hay que entender cómo modificar el diseño. Si el cambio sólo ocurre en uno o dos lugares, no es un gran problema. Sin embargo, pueden ocurrir cambios en todo el sistema. Si se incorpora flexibilidad en todos los lugares posibles donde pueden ocurrir cambios, la complejidad y la dificultad de mantenimiento de todo el sistema aumentarán considerablemente. Por supuesto, el mayor fracaso sería si toda esta flexibilidad resultara innecesaria. Ya sabes, debe haber cierta flexibilidad que no se utilizará, pero no se puede predecir cuáles no se utilizarán. Para obtener la flexibilidad que desea, debe agregar más flexibilidad de la que realmente necesita.

Con la refactorización, tienes un enfoque diferente para lidiar con los riesgos del cambio. Aún es necesario pensar en cambios potenciales y considerar soluciones flexibles. Pero en lugar de implementar cada una de estas soluciones una por una, debería preguntarse: "¿Qué tan difícil es refactorizar una solución simple en esta solución flexible?" ' Si la respuesta es "bastante fácil" (que lo es la mayor parte del tiempo), entonces simplemente implemente la solución simple que tiene hoy.

La refactorización puede conducir a diseños más simples sin perder flexibilidad, lo que también reduce la dificultad del proceso de diseño y reduce la presión de diseño. Una vez que tenga una mejor idea de la simplicidad que aporta la refactorización, ni siquiera tendrá que pensar de antemano en la llamada solución flexible; siempre tendrá la confianza suficiente para refactorizar cuando la necesite. Sí, simplemente cree el sistema más simple que funcione en este momento. En cuanto a diseños flexibles y complejos, bueno, la mayoría de las veces no los necesitará.

Nada que ganar - Ron Jeffries

El proceso de pago de la Compensación Integral de Chrysler es demasiado lento. Aunque nuestro desarrollo aún no ha terminado, este problema ha comenzado a molestarnos porque ha ralentizado la velocidad de las pruebas.

Kent Beck, Martin Fowler y yo decidimos resolver este problema. Mientras esperaba que todos se reunieran, basándome en mi comprensión general del sistema, comencé a especular: ¿Qué está haciendo que el sistema se ralentice? Pensé en varias posibilidades y luego discutí varias modificaciones posibles con mis socios. Finalmente, se nos ocurrieron algunas ideas realmente buenas sobre cómo hacer que este sistema sea más rápido.

Luego, utilizamos las herramientas de medición de Kent para medir el rendimiento del sistema. Ninguna de las posibilidades en las que pensé inicialmente fue la causa del problema. Descubrimos que el sistema dedica la mitad del tiempo a crear la entidad (instancia) de "fecha". Lo que es aún más interesante es que todas estas entidades tienen el mismo valor.

Así que observamos la lógica de creación de fechas y encontramos una oportunidad para optimizarla. Las fechas se convierten originalmente a partir de cadenas, incluso sin entrada externa. La razón por la que se utiliza la conversión de cadenas es exclusivamente para facilitar la entrada del teclado. Bien, tal vez podamos optimizarlo.

Así observamos cómo las fechas son utilizadas por este programa. Descubrimos que muchos objetos de fecha se utilizan para generar entidades (instancias) de "intervalo de fechas". "Intervalo de fechas" es un objeto que consta de una fecha de inicio y una fecha de finalización. Después de un seguimiento cuidadoso, descubrimos que la mayoría de los rangos de fechas están vacíos.

Cuando trabajamos con rangos de fechas, seguimos esta regla: si la fecha de finalización es anterior a la fecha de inicio, el rango de fechas debe estar vacío. Esta es una buena regla y satisface plenamente las necesidades de esta clase.

Poco después de adoptar esta regla, nos dimos cuenta de que crear un rango de fechas con una fecha de inicio posterior a la fecha de finalización aún no era un código claro, por lo que refinamos el comportamiento en un método de fábrica (consulte "Patrones de diseño"), que crea específicamente ". rangos de fechas vacíos".

Hicimos las modificaciones anteriores para aclarar el código, pero nos llevamos una sorpresa. Creamos un objeto fijo de "intervalo de fechas vacío" y dejamos que el método de fábrica ajustado anteriormente devuelva el objeto cada vez en lugar de crear un objeto nuevo cada vez. Esta modificación casi duplicó la velocidad del sistema, lo que fue suficiente para que la velocidad de prueba fuera aceptable. Esto sólo nos llevó unos cinco minutos.

Los miembros de mi equipo y yo (Kent y Martin declinamos participar) especulamos cuidadosamente: ¿Qué errores podría haber en este programa que conocemos tan bien? Incluso hicimos algunos diseños mejorados de la nada sin medir primero las condiciones reales del sistema.

Estábamos completamente equivocados. No hicimos nada bueno excepto tener una conversación interesante.

Lección: Incluso si comprende completamente el sistema, mida realmente su rendimiento y no haga suposiciones. Adivinar te enseñará algo, pero nueve de cada diez veces te equivocarás.