2009-01-01 8 views
10

He sido utilizado para hacer algunas refactorizaciones introduciendo errores de compilación. Por ejemplo, si quiero eliminar un campo de mi clase y convertirlo en un parámetro para algunos métodos, generalmente elimino el campo primero, lo que causa un error de compilación para la clase. Luego, introduciría el parámetro en mis métodos, lo que rompería las llamadas. Y así. Esto usualmente me dio una sensación de seguridad. Todavía no he leído ningún libro (todavía) sobre refactorización, pero solía pensar que esta es una forma relativamente segura de hacerlo. Pero me pregunto, ¿es realmente seguro? ¿O es una mala forma de hacer las cosas?¿La refactorización por errores de compilación es mala?

+0

Lo hago todo el tiempo, y creo que es una buena manera de evitar la introducción de errores. Sin embargo, no elimino ningún código. Al principio, añado un _, 2, o lo que sea, para causar un error y luego reviso el código. Además, agregaré "estático" para causar errores y eliminar el código. Buscar todas las referencias es bueno, pero me gustaría poder "Buscar múltiples ..." – AMissico

+0

Lo bueno de esta técnica es que puedes usar la ventana de la caja de herramientas "Lista de errores" como una "lista de tareas" y una "lista de marcadores". – AMissico

Respuesta

9

Esta es una técnica común y útil para los lenguajes compilados estáticamente. La versión general de lo que está haciendo podría indicarse de la siguiente manera:

Cuando está realizando un cambio en un módulo que podría invalidar algunos usos en clientes de ese módulo, realice el cambio inicial de tal manera que causa un error en tiempo de compilación

Hay una variedad de corolarios:

  • Si el significado de un método, función o procedimiento cambia, y el tipo no lo hace también cambian, a continuación, cambiar el nombre. (Cuando haya analizado y solucionado todos los usos, probablemente cambie el nombre.)

  • Si agrega un nuevo caso a un tipo de datos o un nuevo literal a una enumeración, cambie los nombres de todos los tipos de datos existentes constructores o literales de enumeración. (O, si se tiene la suerte de tener un compilador que comprueba si el análisis de casos es exhaustiva, hay maneras más fáciles.)

  • Si está trabajando en un idioma con sobrecarga, no lo hacen simplemente cambiar una variante o agregar una nueva variante. Te arriesgas a que la sobrecarga se resuelva en silencio de una manera diferente. Si usa la sobrecarga, es bastante difícil hacer que el compilador trabaje para usted de la manera que espera. La única forma que conozco para manejar la sobrecarga es razonar globalmente sobre todos los usos. Si su IDE no lo ayuda, debe cambiar los nombres de todas las variantes sobrecargadas. Desagradable.

Lo que realmente está haciendo es usar el compilador para ayudarlo a examinar todos los lugares en el código que podría necesitar cambiar.

+0

Sin embargo, la respuesta a la pregunta "¿es realmente seguro?" Sería "No", ver respuestas de Vilx- y Shy. – Constantin

2

Creo que es una manera bastante común, ya que encuentra todas las referencias a esa cosa específica. Sin embargo, los IDEs modernos como Visual Studio tienen la función Buscar todas las referencias que lo hace innecesario.

Sin embargo, este enfoque tiene algunas desventajas. Para proyectos grandes, puede llevar mucho tiempo compilar la aplicación. Además, no haga esto durante mucho tiempo (quiero decir, vuelva a poner las cosas en funcionamiento lo antes posible) y no haga esto para más de una cosa a la vez, ya que puede olvidar la forma correcta en que parcheó las cosas en la primera vez.

5

No veo ningún problema con él. Es seguro, y siempre y cuando no esté cometiendo cambios antes de compilar, no tiene un efecto a largo plazo. Además, Resharper y VS tienen herramientas que hacen que el proceso sea un poco más fácil para usted.

Utiliza un proceso similar en la otra dirección en TDD: escribe código que puede no tener los métodos definidos, lo que hace que no se compile, luego escribe suficiente código para compilar (luego pasa pruebas, etc.). ..)

1

se trata de un enfoque común, pero los resultados pueden variar dependiendo de si el lenguaje es estática o dinámica.

En un lenguaje de tipos estáticos este enfoque tiene sentido ya que cualquier discrepancia que usted introduce serán capturados en tiempo de compilación. Sin embargo, los lenguajes dinámicos a menudo solo encontrarán estos problemas en el tiempo de ejecución. Estos problemas no serían captados por el compilador, sino más bien por su suite de pruebas; suponiendo que hayas escrito uno.

Me da la impresión de que está trabajando con un lenguaje estático como C# o Java, por lo que continúan con este enfoque hasta que se encuentra con algún tipo de problema principal que dice que usted debe hacer lo contrario.

1

Hago refactorizaciones habituales, pero todavía hago refactorizaciones introduciendo errores de compilación. Los hago generalmente cuando los cambios no son tan simples y cuando esta refactorización no es real refactorización (estoy cambiando la funcionalidad). Esos errores del compilador me están dando puntos que necesito echar un vistazo y complicar un poco más el cambio que el cambio de nombre o parámetro.

10

Nunca me baso en simple compilación al refactorizar, el código se puede compilar, pero podría haber sido introducido errores.

Creo que solo escribir algunas pruebas unitarias para los métodos o clases que desea refactorizar será lo mejor, luego, al ejecutar la prueba después de la refactorización, estará seguro de que no se presentaron errores.

no digo que vaya a Test Driven Development, acaba de escribir las pruebas unitarias para obtener la confianza necesaria que necesita para refactorizar.

5

Cuando esté listo para leer libros sobre el tema, recomiendo Michael pluma de "Working Effectively with Legacy Code". (Agregado por el no autor: también el libro clásico de Fowler "Refactoring" - y el sitio web Refactoring puede ser útil.)

Habla de identificar las características del código que está trabajando antes de realizar un cambio y hacer lo que él llama refactorización de scratch. Eso es refectoring para encontrar las características del código y luego arrojar los resultados de distancia.

Lo que está haciendo es utilizando el compilador como un auto-test. Probará que su código compila, pero no si el comportamiento ha cambiado debido a su refactorización o si hubo algún efecto secundario.

consideran este

class myClass { 
    void megaMethod() 
    { 
     int x,y,z; 
     //lots of lines of code 
     z = mysideEffect(x)+y; 
     //lots more lines of code 
     a = b + c; 
    } 
} 

usted podría refactorizar el addtion

class myClass { 
    void megaMethod() 
    { 
     int a,b,c,x,y,z; 
     //lots of lines of code 
     z = addition(x,y); 
     //lots more lines of code 
     a = addition(b,c); 
    } 

    int addition(int a, b) 
    { 
      return mysideaffect(a)+b; 
    } 
} 

y esto iba a funcionar, pero el segundo additon sería un error, ya que invoca el método. Se necesitarán más pruebas que no sean solo compilación.

2

Es una manera y no puede haber ninguna afirmación definitiva acerca de si es seguro o inseguro, sin saber lo que el código se reestructuran Aspecto del producto y las decisiones que toma.

Si funciona para usted, entonces no hay razón para cambiar solo por cambiar, pero cuando tenga tiempo de leer, los recursos aquí pueden darle nuevas ideas que puede explorar con el tiempo.

http://www.refactoring.com/

4

Es bastante fácil pensar en ejemplos en los que una refactorización por los errores de compilación error de forma silenciosa y produce resultados no deseados.
Unos pocos casos que vienen a la mente: (Vamos a suponer que estamos hablando de C++)

  • Cambio de argumentos a una función donde existen otras sobrecargas con parámetros por defecto. Después de la refactorización, la mejor coincidencia para los argumentos puede no ser la esperada.
  • Las clases que han emitido los operadores o los constructores de argumentos individuales explícitas no. Cambiar, agregar, quitar o cambiar argumentos a cualquiera de estos puede cambiar las mejores coincidencias a las que se llama, dependiendo de la constelación involucrada.
  • Cambiar una función virtual y sin cambiar la clase base (o cambiar la clase base de manera diferente) dará lugar a que las llamadas se dirijan a la clase base.

confiando en los errores del compilador se deben usar solo si usted está absolutamente seguro de que el compilador captará cada cambio que se necesita realizar. Casi siempre tiendo a sospechar de esto.

+0

Estos son realmente los puntos que podría haber perdido. Quiero dar este +5, pero solo puedo dar +1 :) –

1

Esto suena similar a un método absolutamente estándar usado en desarrollo controlado por prueba: escriba la prueba referente a una clase inexistente, de modo que el primer paso para hacer la prueba sea agregar la clase, luego los métodos, y así en. Ver Beck's book para ejemplos exhaustivos de Java.

Su método de refacturación parece peligroso porque no tiene ninguna prueba de seguridad (o al menos no menciona que tiene alguna). Puede crear código de compilación que realmente no hace lo que desea, o rompe otras partes de su aplicación.

Le sugiero que agregue una regla simple a su consulta: realice cambios que no se compliquen solo en el código de prueba de unidad.De esta forma, está seguro de tener al menos una prueba local para cada modificación, y está registrando la intención de la modificación en su prueba antes de realizarla.

Por cierto, Eclipse hace que este método "fail, stub, write" sea absurdamente fácil en Java: cada objeto inexistente está marcado para ti, y Ctrl-1 más una opción de menú le dice a Eclipse que escriba un código (compilable) ¡tú! Me interesaría saber si otros idiomas y IDEs ofrecen un soporte similar.

1

Es "seguro" en el sentido de que en un lenguaje suficientemente en tiempo de compilación a cuadros, que le obliga a actualizar todas las referencias en directo a lo que ha cambiado.

Todavía puede ir mal si ha condicionalmente código compilado, por ejemplo, si usted ha utilizado el/C preprocesador C++. Por lo tanto, asegúrese de reconstruir en todas las configuraciones posibles y en todas las plataformas, si corresponde.

No elimina la necesidad de probar los cambios. Si ha agregado un parámetro a una función, entonces el compilador no puede decirle si el valor correcto fue el valor que proporcionó cuando actualizó cada sitio de llamada para esa función. Si ha eliminado un parámetro, todavía se pueden cometer errores, por ejemplo, el cambio:

void foo(int a, int b); 

a

void foo(int a); 

a continuación, cambiar la llamada a partir de:

foo(1,2); 

a:

foo(2); 

que compila bien, pero está mal

Personalmente, hago uso de la compilación (y enlace) fracasos como una manera de buscar código para referencias en directo a la función que estoy cambiando. Pero debe tener en cuenta que esto es solo un dispositivo que ahorra trabajo. No garantiza que el código resultante sea correcto.

2

Otra opción que podría considerar, si se está utilizando uno de los lanuages ​​dotnet, es para marcar el método de "viejo" con el atributo obsoleto, que introducirá todas las advertencias del compilador, pero aún dejan el código exigible debería allí ser código más allá de tu control (si estás escribiendo una API, o si no usas la opción strict en VB.Net, por ejemplo). Puedes refactorizar alegremente, teniendo la versión obsoleta llamar a la nueva; como ejemplo:

public string Username 
    { 
     get 
     { 
      return this.userField; 
     } 
     set 
     { 
      this.userField = value; 
     } 
    } 

    public int Login() 
    { 
     /* do stuff */ 
    } 

se convierte en:

[ObsoleteAttribute()] 
    public string Username 
    { 
     get 
     { 
      return this.userField; 
     } 
     set 
     { 
      this.userField = value; 
     } 
    } 

    [ObsoleteAttribute("Replaced by Login(username, password)")] 
    public int Login() 
    { 
     Login(Username, Pasword); 
    } 

    public int Login(string username, string password) 
    { 
     /* do stuff */ 
    } 

Así es como me tiendo a hacerlo, de todos modos ...

3

Sólo me gustaría añadir a toda la sabiduría que por aquí hay un caso más donde esto podría no ser seguro. Reflexión. Esto afecta a entornos como .NET y Java (y también a otros, por supuesto). Su código se compilaría, pero todavía habría errores de tiempo de ejecución cuando Reflection intente acceder a variables que no existen. Por ejemplo, esto podría ser bastante común si usa un ORM como Hibernate y olvida actualizar sus archivos XML de mapeo.

Puede ser que sea un poco más seguro para buscar el conjunto de archivos de código para el nombre específico de la variable/método. Por supuesto, podría generar muchos falsos positivos, por lo que no es una solución universal; y también podría usar la concatenación de cadenas en su reflejo, lo que también lo haría inútil. Pero al menos está un paso más cerca de la seguridad.

Sin embargo, no creo que haya un método 100% infalible, además de pasar por todo el código manualmente.

Cuestiones relacionadas