Pregunta

¿Cómo haría para convertir una base de código C razonablemente grande (> 300K) y bastante madura a C ++?

El tipo de CI que se tiene en mente se divide en archivos que corresponden aproximadamente a los módulos (es decir, menos granulares que una descomposición típica basada en la clase OO), utilizando enlaces internos en lugar de funciones y datos privados, y enlaces externos para funciones públicas y datos . Las variables globales se utilizan ampliamente para la comunicación entre los módulos. Hay un conjunto de pruebas de integración muy extenso disponible, pero no hay pruebas de nivel de unidad (es decir, módulo).

Tengo en mente una estrategia general:

  1. Compila todo en el subconjunto C de C ++ y haz que funcione.
  2. Convierta los módulos en clases enormes, de modo que todas las referencias cruzadas estén definidas por un nombre de clase, pero dejando todas las funciones y datos como miembros estáticos, y haga que funcione.
  3. Convierta clases enormes en instancias con constructores apropiados y referencias cruzadas inicializadas; reemplazar los accesos estáticos de miembros con accesos indirectos según corresponda; y que funcione.
  4. Ahora, aborde el proyecto como una aplicación OO sin factor, y escriba pruebas unitarias donde las dependencias sean manejables, y descomponga en clases separadas donde no lo sean; el objetivo aquí sería pasar de un programa de trabajo a otro en cada transformación.

Obviamente, esto sería bastante trabajo. ¿Hay algún caso de estudio / historia de guerra sobre este tipo de traducción? Estrategias alternativas? ¿Otro consejo útil?

Nota 1: el programa es un compilador, y probablemente millones de otros programas confían en que su comportamiento no cambia, por lo que la reescritura general no es una opción.

Nota 2: la fuente tiene casi 20 años y tiene quizás un 30% de abandono de código (líneas modificadas + agregadas / líneas totales anteriores) por año. Es fuertemente mantenido y extendido, en otras palabras. Por lo tanto, uno de los objetivos sería aumentar la mantenibilidad.

[En aras de la pregunta, suponga que la traducción a C ++ es obligatoria, y que dejarla en C es no una opción. El punto de agregar esta condición es eliminar el " dejarlo en C " respuestas.]

¿Fue útil?

Solución

Después de haber comenzado casi lo mismo hace unos meses (en un proyecto comercial de diez años de antigüedad, originalmente escrito con & "; C ++ no es más que C con struct s " inteligente; filosofía), sugeriría usar la misma estrategia que usarías para comer un elefante: dale un mordisco a la vez. :-)

Tanto como sea posible, divídalo en etapas que se puedan hacer con efectos mínimos en otras partes. Construir un sistema de fachada, como Federico Ramponi es un buen comienzo. Una vez que todo tiene una fachada C ++ y se está comunicando a través de ella, puede cambiar las partes internas de los módulos con la certeza de que no pueden afectar nada fuera de ellos.

Ya teníamos un sistema de interfaz C ++ parcial (debido a esfuerzos de refactorización anteriores más pequeños), por lo que este enfoque no fue difícil en nuestro caso. Una vez que todo se comunicaba como objetos C ++ (que tardó algunas semanas, trabajando en una rama de código fuente completamente separada e integrando todos los cambios en la rama principal tal como fueron aprobados), era muy raro que no pudiéramos compilar totalmente versión de trabajo antes de partir para el día.

El cambio aún no se ha completado: hemos hecho una pausa dos veces para los lanzamientos provisionales (nuestro objetivo es un lanzamiento puntual cada pocas semanas), pero está en camino y ningún cliente se ha quejado de ningún problema . Nuestro personal de control de calidad solo ha encontrado un problema que también recuerdo. :-)

Otros consejos

¿Qué pasa con:

  1. Compilar todo en el subconjunto C de C ++ y hacer que funcione, y
  2. Implementando un conjunto de fachadas dejando el código C inalterado?

¿Por qué es obligatoria la traducción a C ++? Puede ajustar el código C sin la molestia de convertirlo en grandes clases, etc.

Su aplicación tiene mucha gente trabajando en ella, y la necesidad de que no se rompa. Si te tomas en serio la conversión a gran escala a un estilo OO, ¿qué lo que necesita son herramientas de transformación masiva para automatizar el trabajo.

La idea básica es designar grupos de datos como clases, y luego obtener la herramienta para refactorizar el código para mover esos datos a clases, mover funciones solo de esos datos a esas clases, y revise todos los accesos a esos datos a las llamadas en las clases.

Puede realizar un preanálisis automatizado para formar grupos estadísticos para obtener algunas ideas, pero aún necesitará un ingeniero consciente de la aplicación para decidir qué los elementos de datos deben estar agrupados.

Una herramienta que es capaz de realizar esta tarea es nuestra DMS Software Reengineering Kit de herramientas . DMS tiene fuertes analizadores C para leer su código, captura el código C como árboles de sintaxis abstracta del compilador (y a diferencia de un compilador convencional) puede calcular análisis de flujo en todo su SLK de 300K. DMS tiene una interfaz de C ++ que se puede usar como '' atrás '' final; uno escribe transformaciones que asignan la sintaxis de C a la sintaxis de C ++.

Una importante tarea de reingeniería de C ++ en un gran sistema de aviónica da alguna idea de cómo es usar DMS para este tipo de actividad. Ver documentos técnicos en www.semdesigns.com/Products/DMS/DMSToolkit.html, específicamente Reingeniería de modelos de componentes C ++ a través de la transformación automática de programas

Este proceso no es para los débiles de corazón. Pero que nadie eso consideraría la refactorización manual de una aplicación grande ya no le teme al trabajo duro.

Sí, estoy asociado con la empresa, siendo su arquitecto principal.

Escribiría clases de C ++ sobre la interfaz de C. No tocar el código C disminuirá la posibilidad de equivocarse y acelerará significativamente el proceso.

Una vez que tenga su interfaz C ++ activada; entonces es una tarea trivial copiar + pegar el código en sus clases. Como mencionó, durante este paso es vital realizar pruebas unitarias.

GCC se encuentra actualmente en la transición intermedia a C ++ desde C. Comenzaron moviendo todo al subconjunto común de C y C ++, obviamente. Al hacerlo, agregaron advertencias a GCC para todo lo que encontraron, que se encuentra en -Wc ++ - compat . Eso debería llevarte a la primera parte de tu viaje.

Para las últimas partes, una vez que tenga todo compilado con un compilador de C ++, me enfocaría en reemplazar las cosas que tienen contrapartes idiomáticas de C ++. Por ejemplo, si usa listas, mapas, conjuntos, vectores de bits, tablas hash, etc., que se definen usando macros C, es probable que gane mucho moviéndolos a C ++. Del mismo modo, con OO, es probable que encuentre beneficios donde ya está utilizando un lenguaje C OO (como la herencia de estructura), y donde C ++ le brindará una mayor claridad y una mejor verificación de tipo en su código.

Su lista se ve bien, excepto que sugeriría revisar primero el conjunto de pruebas y tratar de ajustarlo lo más posible antes de realizar cualquier codificación.

Vamos a lanzar otra idea estúpida:

  1. Compila todo en el subconjunto C de C ++ y haz que funcione.
  2. Comience con un módulo, conviértalo en una clase enorme, luego en una instancia, y cree una interfaz C (idéntica a la que comenzó) a partir de esa instancia. Deje que el código C restante funcione con esa interfaz C.
  3. Refactorice según sea necesario, haciendo crecer el subsistema OO fuera del código C módulo a módulo, y suelte partes de la interfaz C cuando se vuelvan inútiles.

Probablemente, dos cosas a tener en cuenta además de cómo quiere comenzar son en qué quiere enfocar y dónde quiere detenerse .

Usted declara que hay una gran rotación de código, esto puede ser una clave para enfocar sus esfuerzos. Le sugiero que elija las partes de su código donde se necesita mucho mantenimiento, las partes maduras / estables aparentemente funcionan lo suficientemente bien, por lo que es mejor dejarlas como están, excepto probablemente para algunos escaparates con fachadas, etc. / p>

El lugar donde desea detenerse depende de cuál sea la razón para querer convertir a C ++. Esto difícilmente puede ser un objetivo en sí mismo. Si se debe a una dependencia de terceros, centre sus esfuerzos en la interfaz de ese componente.

El software en el que trabajo es una base de código enorme y antigua que se ha "convertido" de C a C ++ hace años. Creo que fue porque la GUI se convirtió a Qt. Incluso ahora todavía se ve principalmente como un programa en C con clases. Romper las dependencias causadas por los miembros de los datos públicos y refactorizar las clases enormes con métodos de procedimientos de monstruos en métodos y clases más pequeños nunca ha despegado realmente, creo que por las siguientes razones:

  1. No es necesario cambiar el código que funciona y que no es necesario mejorarlo. Al hacerlo, se introducen nuevos errores sin agregar funcionalidad, y los usuarios finales no lo aprecian;
  2. Es muy, muy difícil hacer una refactorización confiable. Muchas piezas de código son tan grandes y también tan vitales que las personas apenas se atreven a tocarlo. Tenemos un conjunto bastante extenso de pruebas funcionales, pero es difícil obtener suficiente información de cobertura de código. Como resultado, es difícil establecer si ya hay suficientes pruebas para detectar problemas durante la refactorización;
  3. El retorno de la inversión es difícil de establecer. El usuario final no se beneficiará de la refactorización, por lo que debe tener un costo de mantenimiento reducido, lo que aumentará inicialmente porque al refactorizar introduce nuevos errores en código maduro, es decir, bastante libre de errores. Y la refactorización en sí será costosa también ...

NB. Supongo que conoce el "Trabajar eficazmente con código heredado" libro?

Usted menciona que su herramienta es un compilador y que: "En realidad, la coincidencia de patrones, no solo la coincidencia de tipos, en el envío múltiple sería aún mejor".

Es posible que desee echar un vistazo a maketea . Proporciona coincidencia de patrones para AST, así como la definición de AST de una gramática abstracta y visitantes, transformadores, etc.

Si tiene un proyecto pequeño o académico (digamos, menos de 10,000 líneas), una reescritura es probablemente su mejor opción. Puede factorizarlo como desee, y no tomará demasiado tiempo.

Si tiene una aplicación del mundo real, le sugiero que se compile como C ++ (lo que generalmente significa principalmente reparar prototipos de funciones y similares), luego trabaje en la refactorización y el ajuste OO. Por supuesto, no me suscribo a la filosofía de que el código debe estar estructurado OO para que sea un código C ++ aceptable. Haría una conversión pieza por pieza, reescritura y refactorización según sea necesario (para funcionalidad o para incorporar pruebas unitarias).

Esto es lo que haría:

  • Dado que el código tiene 20 años, deseche el analizador de analizador sintáctico / sintáctico y reemplácelo con uno de los códigos C ++ más nuevos lex / yacc / bison (o algo similar), etc., mucho más fácil de mantener y de entender. También es más rápido de desarrollar si tiene un BNF a mano.
  • Una vez que esto se haya adaptado al código anterior, comience a ajustar los módulos en clases. Reemplace las variables globales / compartidas con interfaces.
  • Ahora lo que tienes será un compilador en C ++ (aunque no del todo).
  • Dibuje un diagrama de clase de todas las clases en su sistema y vea cómo se comunican.
  • Dibuje otro usando las mismas clases y vea cómo deben comunicarse.
  • Refactorice el código para transformar el primer diagrama en el segundo. (esto puede ser complicado y complicado)
  • Recuerde usar código C ++ para todos los nuevos códigos agregados.
  • Si le queda algo de tiempo, intente reemplazar las estructuras de datos una por una para usar el STL o Boost más estandarizado.
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top