2012-04-11 10 views
5

Estoy en el proceso de refactorizar una gran cantidad de código, principalmente C++, para eliminar un número de comprobaciones de configuración temporales que se han establecido de forma permanente en valores determinados. Así, por ejemplo, tendría el siguiente código:Refactorización en C++: expansión condicional y eliminación de bloque

#include <value1.h> 
#include <value2.h> 
#include <value3.h> 

... 

if (value1()) 
{ 
    // do something 
} 

bool b = value2(); 

if (b && anotherCondition) 
{ 
    // do more stuff 
} 

if (value3() < 10) 
{ 
    // more stuff again 
} 

donde las llamadas a un valor de retorno sea bool o un int. Como sé que los valores que estas llamadas siempre regresan, he hecho alguna sustitución de expresiones regulares para expandir las llamadas a sus valores normales:

// where: 
// value1() == true 
// value2() == false 
// value3() == 4 

// TODO: Remove expanded config (value1) 
if (true) 
{ 
    // do something 
} 

// TODO: Remove expanded config (value2) 
bool b = false; 

if (b && anotherCondition) 
{ 
    // do more stuff 
} 

// TODO: Remove expanded config (value3) 
if (4 < 10) 
{ 
    // more stuff again 
} 

en cuenta que aunque los valores son fijos, no se establecen en tiempo de compilación, pero se leen desde la memoria compartida por lo que el compilador no está optimizando nada detrás de escena.

Aunque el código resultante parece un poco tonto, este enfoque de expresión regular logra mucho de lo que quiero ya que es fácil de aplicar y elimina la dependencia de las llamadas, sin cambiar el comportamiento del código y también es probable que el compilador luego puede optimizar una gran cantidad sabiendo que nunca se puede llamar a un bloque o que un cheque siempre será verdadero. También hace que sea razonablemente fácil (sobre todo cuando se diferencian contra el control de versiones) para ver lo que ha cambiado y dar el paso final de la limpieza para arriba por lo que el código anterior código de tiempo se ve de la siguiente manera:

// do something 

// DONT do more stuff (b being false always prevented this) 

// more stuff again 

El problema es que yo tiene que hacer cientos (posiblemente miles) de cambios para pasar de la segunda etapa, correcta pero tonta, para llegar al código final limpio.

Me preguntaba si alguien sabía de una herramienta de refactorización que pudiera manejar esta o cualquier otra técnica que pudiera aplicar. El problema principal es que la sintaxis de C++ hace que la expansión o eliminación completa sea bastante difícil de lograr y hay muchas permutaciones al código anterior. Siento que casi necesito un compilador para manejar la variación de la sintaxis que necesitaría cubrir.

Sé que ha habido preguntas similares pero no encuentro ningún requisito como este y también me pregunto si surgieron herramientas o procedimientos desde que se solicitaron.

Respuesta

5

Parece que usted tiene lo que yo llamo "código zombi" ... muertos en la práctica, pero todavía vivo en lo que se refiere al compilador.Este es un problema bastante común con la mayoría de los sistemas de variables de configuración de tiempo de ejecución organizadas: eventualmente algunas variables de configuración llegan a un estado fijo permanente, pero se vuelven a evaluar en tiempo de ejecución repetidamente.

La cura no es regex, como usted ha notado, porque regex no analiza confiablemente el código C++. Lo que necesita es un program transformation system. Esta es una herramienta que realmente analiza el código fuente, y puede aplicar un conjunto de reglas de reescritura de código a código en el árbol de análisis sintáctico, y puede regenerar el texto fuente del árbol modificado.

Entiendo que Clang tiene alguna capacidad aquí; puede analizar C++ y construir un árbol, pero no tiene la capacidad de transformación de fuente a fuente. Puede simular esa capacidad escribiendo transformaciones AST a AST, pero eso es mucho más inconveniente en mi humilde opinión. Creo que puede regenerar código C++, pero no sé si conservará los comentarios o las directivas de preprocesador.

Nuestro DMS Software Reengineering Toolkit con su C++(11) front end puede (y se ha usado) realizar transformaciones masivas en el código fuente de C++, y tiene transformaciones de fuente a fuente. AFAIK, es la única herramienta de producción que puede hacer esto. Lo que necesita es un conjunto de transformaciones que representan su conocimiento del estado final de las variables de configuración de interés, y algunas reglas sencillas de simplificación de código. Las siguientes reglas son DMS cerca de lo que es probable que desee:

rule fix_value1():expression->expression 
    "value1()" -> "true"; 
    rule fix_value2():expression->expression 
    "value2()" -> "false"; 
    rule fix_value3():expression->expression 
    "value3()" -> "4"; 

    rule simplify_boolean_and_true(r:relation):condition->condition 
    "r && true" -> "r". 
    rule simplify_boolean_or_ture(r:relation):condition->condition 
    "r || true" -> "true". 
    rule simplify_boolean_and_false(r:relation):condition->condition 
    "r && false" -> "false". 
    ... 
    rule simplify_boolean_not_true(r:relation):condition->condition 
    "!true" -> "false". 
    ... 

    rule simplify_if_then_false(s:statement): statement->statement 
     " if (false) \s" -> ";"; 
    rule simplify_if_then_true(s:statement): statement->statement 
     " if (true) \s" -> "\s"; 
    rule simplify_if_then_else_false(s1:statement, s2:statement): statement->statement 
     " if (false) \s1 else \s2" -> "\s2"; 
    rule simplify_if_then_else_true(s1:statement, s2: statement): statement->statement 
     " if (true) \s1 else \s2" -> "\s2"; 

también necesita reglas para simplificar ("doblar") expresiones constantes que implican la aritmética, y las reglas para manejar interruptor sobre las expresiones que ahora son constantes. Para ver las reglas de DMS para el plegado constante de números enteros, consulte Algebra as a DMS domain.

A diferencia de las expresiones regulares, las reglas de reescritura de DMS no pueden "desajustar" el código; representan los AST correspondientes y es que los AST coinciden. Debido a que es una coincidencia AST, no tienen problemas con espacios en blanco, saltos de línea o comentarios. Podría pensar que podrían tener problemas con el orden de los operandos ('¿Qué pasa si' falso & & x '' se encuentra? '); no lo hacen, como las reglas de gramática para & & y || están marcados en el analizador DMS C++ como asociativos y conmutativos y el proceso de coincidencia lo tiene en cuenta automáticamente.

Lo que estas reglas no pueden hacer por sí solas es el valor (en su caso, constante) de propagación a través de las asignaciones. Para esto necesita análisis de flujo para que pueda rastrear tales asignaciones ("llegar a definiciones"). Obviamente, si no tiene tales asignaciones o muy pocas, puede parchearlas a mano. Si lo hace, necesitará el análisis de flujo; por desgracia, el frente C++ de DMS no está del todo allí, pero estamos trabajando en ello; tenemos control de flujo de análisis en su lugar. (La parte frontal C de DMS tiene un análisis de flujo completo).

(EDITAR Febrero de 2015: Ahora realiza C++ 14 completo; análisis de flujo dentro de funciones/métodos).

En realidad aplicamos esta técnica a la aplicación 1.5M SLOC de código mixto C y C++ de IBM Tivoli hace casi una década con excelente éxito; no necesitábamos el análisis de flujo: -}

+0

Sí, esto es exactamente lo que quiero. Los regex me pueden ayudar un poco, pero son imprecisos y, como dices, lo más importante es que no "conocen" el idioma. Muchas gracias. (Por cierto, su enlace "DMS Software Reengineering Toolkit" está roto) –

+0

Fundamentalmente, las expresiones regulares no pueden ocuparse de nada relacionado con la anidación. Las expresiones (booleanas) implican anidamiento. QED: las expresiones regulares no pueden funcionar por sí mismas en las expresiones. PD: enlace fijo. –

1

Usted dice:

en cuenta que aunque los valores son razonablemente fijos, no se establecen en tiempo de compilación, pero se leen desde la memoria compartida por lo que el compilador no está optimizando cualquier cosa lejos detrás de las escenas.

Constante-plegado de los valores a mano no tiene mucho sentido a menos que estén completamente fijo. Si su compilador proporciona constexpr usted podría utilizar eso, o se puede sustituir en las macros del preprocesador de esta manera:

#define value1() true 
#define value2() false 
#define value3() 4 

El optimizador se haría cargo de que a partir de ahí. Sin ver ejemplos de exactamente lo que hay en sus encabezados <valueX.h> o saber cómo funciona el proceso de obtener estos valores de la memoria compartida, simplemente descartaré que podría ser útil cambiar el nombre de las funciones valueX() existentes y hacer una verificación en tiempo de ejecución caso de que cambien de nuevo en el futuro:

// call this at startup to make sure our agreed on values haven't changed 
void check_values() { 
    assert(value1() == get_value1_from_shared_memory()); 
    assert(value2() == get_value2_from_shared_memory()); 
    assert(value3() == get_value3_from_shared_memory()); 
} 
+0

Gracias, mi redacción era mala: están corregidas y no cambiarán, pero no en tiempo de compilación. Me gusta la idea de sustituir para que el compilador pueda optimizar y ya lo logré automáticamente con la sustitución de expresiones regulares. Sin embargo, uno de los objetivos es eliminar el código 'cruft': hay muchos bloques de código, algunos bastante grandes, que serán completamente redundantes una vez que se haya fijado el valor y sería bueno si se eliminase automáticamente de la fuente también. –

+0

Si usted está teniendo dificultades para evaluar expresiones en su cabeza, prueba de un hombre pobre para la búsqueda de código inalcanzable sería seguir mi sugerencia y luego usar algo como advertencia -Wunreachable-code' de gcc '(que es por lo general fuera, incluso con '-Wall'). Mi suposición es que cualquier herramienta de reescritura de código para C++ que haga esto por ti probablemente sea más complicada de configurar que hacerlo tú mismo. Si le preocupa que pueda cometer errores, no los sustituya de una sola vez ... levante las expresiones y afirme su igualdad a sus simplificaciones. – HostileFork

Cuestiones relacionadas