2008-10-14 14 views
38

¿Cómo haría para convertir una base de código C razonablemente grande (> 300 K) y bastante madura en C++? El tipo de IC que tenemos en mente se divide en archivos que corresponden aproximadamente a módulos (es decir, menos granulares que una descomposición típica basada datos. Las variables globales se utilizan ampliamente para la comunicación entre los módulos. Existe un paquete de prueba de integración muy extenso disponible, pero no hay pruebas de nivel de unidad (es decir, un módulo).Conversión de fuente C a C++

que tienen en mente una estrategia general:

  1. compilar todo en el subgrupo C++ C 's y conseguir que el trabajo.
  2. Convierta los módulos en grandes clases, de modo que todas las referencias cruzadas estén delimitadas por un nombre de clase, pero dejando todas las funciones y datos como miembros estáticos, y funcione.
  3. Convierta clases enormes en instancias con constructores apropiados y referencias cruzadas inicializadas; reemplazar los accesos a miembros estáticos con accesos indirectos según corresponda; y haz que funcione
  4. Ahora, acérquese al proyecto como una aplicación OO mal factorizada, y escriba pruebas unitarias donde las dependencias son tratables, y descompóngase en clases separadas donde no lo están; el objetivo aquí sería pasar de un programa de trabajo a otro en cada transformación.

Obviamente, esto sería un poco de trabajo. ¿Hay estudios de caso/historias de guerra en este tipo de traducción? Estrategias alternativas? ¿Otro consejo útil?

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

Nota 2: la fuente tiene casi 20 años y quizás tenga un 30% de abandono del código (líneas modificadas + líneas totales añadidas/anteriores) por año. En otras palabras, es muy mantenido y extendido. Por lo tanto, uno de los objetivos sería aumentar la capacidad de mantenimiento.

[Por el bien de la cuestión, se supone que la traducción a C++ es obligatoria, y que dejándolo en C es no una opción. El punto de agregar esta condición es eliminar las respuestas "déjalo en C".]

+0

¿Cuál es el marco de tiempo para la migración obligatoria? – paxos1977

+0

¿Qué tan bien conoce la base de código C? ¿De adentro hacia afuera? – paxos1977

+0

La traducción no es obligatoria, solo por el bien de la pregunta (eliminar las respuestas "no traducir"). El tiempo puede ser de 1 a 10 años (es un programa de larga duración). –

Respuesta

15

Habiendo acaba de comenzar en más o menos lo mismo hace unos meses (en un proyecto comercial de diez años de edad, originalmente escrito con la "C++ es más que C con inteligente struct s" filosofía), que sería sugiera usar la misma estrategia que usaría para comerse un elefante: tómalo de un bocado a la vez. :-)

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

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

El cambio aún no está completo: nos hemos detenido dos veces para las versiones intermedias (nuestro objetivo es un punto de publicación cada pocas semanas), pero está en camino y ningún cliente se ha quejado de ningún problemas. Nuestra gente de QA solo ha encontrado un problema que yo también recuerdo. :-)

+0

suena aterrador ... Debería escribir un artículo más detallado sobre este procedimiento, apuesto a que sería bien leído. –

+0

He escrito un par de artículos de blog sobre partes específicas de la conversión, http://geekblog.oakcircle.com/2008/07/19/ascii-unicode-and-windows/ y http: //geekblog.oakcircle. com/2009/03/15/superbug /. Sin embargo, no soy un escritor suficientemente entretenido para que todo resulte interesante. –

5

Escribiría clases C++ en la interfaz C. Si no se toca el código C, disminuirá la probabilidad de que se estropee y acelere el proceso de manera significativa.

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

+3

La "interfaz C" comienza y finaliza con "main()".Creo que puede haber dejado algunos pasos ... :) –

11

¿Qué hay de:

  1. Compilar todo en subconjunto C de C++ 's y conseguir que el trabajo, y
  2. La implementación de un conjunto de facades dejando inalterado el código C?

¿Por qué es "traducción obligatoria a C++"? Puede envolver el código C sin el dolor de convertirlo en grandes clases y así sucesivamente.

+0

Uno de los puntos para hacer que el código sea más modular, convertirlo a C++ y agregar pruebas unitarias, es hacerlo más fácil de mantener. Solo poner una fachada sobre el frente simplemente no funcionará. –

+0

La "traducción obligatoria a C++" es eliminar las respuestas que dicen "dejar la C inalterada". –

+0

Además, quizás el código C no es puro Ansi C, sino que es un antiguo dialecto de C, que no era puro ANSI. :-) –

1

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

Si tiene una aplicación en el mundo real, le sugiero que compile como C++ (que generalmente significa principalmente la reparación de 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 necesita ser estructurado OO para ser un código C++ aceptable. Haría una conversión pieza por pieza, reescribiendo y refactorizando como lo necesitaras (para funcionalidad o para incorporar pruebas unitarias).

3

Su lista se ve bien, excepto que le sugiero que revise primero el conjunto de pruebas y trate de hacerlo lo más ajustado posible antes de hacer cualquier codificación.

+1

El conjunto de pruebas es bastante ajustado, créanme. 20 años de QA con decenas de miles de errores registrados con casos de prueba escritos por QA tienden a hacer eso. –

3

Vamos a lanzar otra idea estúpida:

  1. compilar todo en el subgrupo C++ C 's y conseguir que el trabajo.
  2. Comience con un módulo, conviértalo en una clase enorme, luego en una instancia y construya una interfaz C (idéntica a la que comenzó) fuera de esa instancia. Deja que el código C restante funcione con esa interfaz C.
  3. Refactorifique según sea necesario, haciendo crecer el subsistema OO desde el código C un módulo a la vez, y elimine partes de la interfaz C cuando se vuelvan inútiles.
+1

Sí, eso es más o menos las partes 1 y 2 de mi plan, desglosadas con más detalle. –

3

Probablemente dos cosas a tener en cuenta, además de la forma en que desea iniciar se encuentran en lo que quiere enfoque, y en la que desea parada.

Usted indica que hay un gran cambio de código, esto puede ser una clave para focus 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 tal como están, excepto, probablemente, para algunas cortinas con fachadas, etc.

El lugar donde desea detenerse depende de la razón por la que desea convertir a C++. Esto difícilmente puede ser un objetivo en sí mismo. Si se debe a alguna dependencia de terceros, centre sus esfuerzos en la interfaz de ese componente.

El software en el que trabajo es una gran base de código 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 parece un programa C con clases. Rompiendo las dependencias causadas por miembros de datos públicos, y refactorización las grandes clases con métodos monstruo de procedimiento en los métodos y las clases más pequeñas que nunca ha tenido tanto éxito, creo que por las siguientes razones:

  1. No hay necesidad de cambiar el código que está funcionando y eso no necesita ser mejorado. Al hacerlo, introduce nuevos errores sin agregar funcionalidad, y los usuarios finales no lo aprecian;
  2. Es muy, muy difícil hacer refactor de manera confiable. Muchos pedazos de código son tan grandes y tan vitales que la gente casi no se atreve a tocarlo. Tenemos un conjunto bastante extenso de pruebas funcionales, pero es difícil obtener suficiente información de cobertura del código. Como resultado, es difícil establecer si ya existen suficientes pruebas para detectar problemas durante la refactorización;
  3. El ROI es difícil de establecer. El usuario final no se beneficiará de la refactorización, por lo que debe ser con un costo de mantenimiento reducido, que aumentará inicialmente porque mediante la refactorización se introducen nuevos errores en código maduro, es decir, bastante libre de errores. Y la refacturación en sí misma también será costosa ...

NB. Supongo que conoce el libro "Working effective with Legacy code"?

+1

Sí, tengo el libro. Desafortunadamente, es casi solo aplicable al código de prueba unitaria. La sugerencia principal, apenas más que un párrafo que recuerdo, para las personas que usan código no OO era usar una variante OO. –

+1

La información sobre la mayoría sigue pareciendo C, puedo vivir con ella. Hay un abandono significativo, como dije, por lo que poder usar C++ para las piezas reescritas en movimiento seguiría siendo una victoria en términos de modularidad. –

+0

No existen realmente partes "estables", per se, aparte del administrador de memoria. Los objetivos principales serían poder aumentar el nivel de abstracción del código fuente recién escrito al usar cuidadosamente plantillas, clases y reducir las dependencias cruzadas, particularmente las causadas por variables globales. –

1

Esto es lo que yo haría:

  • Dado que el código es de 20 años de edad, la chatarra por el analizador analizador/sintaxis y reemplazarlo con uno de los más nuevos de la lex/yacc/bisontes (o algo similar), etc. código basado en C++, mucho más fácil de mantener y comprender. Más rápido para desarrollar también si tienes un BNF a mano.
  • Una vez que esto se haya adaptado al código anterior, comience a envolver los módulos en clases. Reemplazar variables globales/compartidas con interfaces.
  • Ahora lo que tienes será un compilador en C++ (no del todo bien).
  • Dibuja un diagrama de clases de todas las clases de tu sistema y observa cómo se están comunicando.
  • Dibuja otra utilizando las mismas clases y observa cómo deben comunicarse.
  • Refactorice el código para transformar el primer diagrama en el segundo. (Esto puede ser complicado y complicado)
  • Recuerde utilizar el 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 estándar STL o Boost.
+1

No creo que aprecien todas las sutilezas de los compiladores. Los compiladores comerciales usan lexers escritos a mano y analizadores sintácticos por muchas razones, siendo el rendimiento solo uno. En segundo lugar, no te vuelvas demasiado adicto a las clases. El despacho múltiple al estilo CLOS en las funciones sería más útil que los métodos virtuales. –

+0

Por ejemplo, ¿cómo se cambia la clase de una instancia sobre la marcha? ¿Cómo se crean nuevas clases en tiempo de ejecución? Terminas agregando niveles de direccionamiento indirecto y perdiendo gran parte del beneficio de OO habitual. En realidad, la coincidencia de patrones, no solo la coincidencia de tipos, en el despacho múltiple sería aún mejor. –

+0

Re analizador: el lenguaje no es LL (1), ni es LALR (1), es sensible al contexto en formas que los predicados semánticos y sintácticos ad-hoc pueden resolver. Este es el precio de la extensión flexible del lenguaje a lo largo de los años. –

7

Su aplicación tiene mucha gente trabajando en ello, y una necesidad de no estar roto. Si se toma en serio la conversión a gran escala a un estilo OO, lo que necesita es herramientas de transformación masiva para automatizar el trabajo.

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

Usted puede hacer una preanalítica automatizado para formar grupos estadísticos para obtener algunas ideas, pero No obstante, deberá ingeniero conscientes applicaiton para decidir qué elementos de datos deben agruparse.

Una herramienta que es capaz de hacer esta tarea es nuestra DMS Software Reengineering Toolkit. DMS tiene analizadores C fuertes para leer su código, captura el código C como árboles de sintaxis abstracta del compilador (ya diferencia de un compilador convencional) puede calcular los análisis de flujo en todo su SLOC de 300K. DMS tiene una interfaz de C++ que se puede usar como extremo "posterior"; se escriben transformaciones que mapean sintaxis C a sintaxis C++.

Una importante tarea de reingeniería de C++ en un gran sistema de aviónica da a una idea de cómo es el uso de DMS para este tipo de actividad. Ver documentos técnicos en www.semdesigns.com/Products/DMS/DMSToolkit.html, específicamente Re-ingeniería de componentes C++ Modelos Via automática de programas de transformación

Este proceso no es para los débiles de corazón. Pero cualquiera que considere la refacturación manual de una aplicación grande ya no le teme al trabajo duro.

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

+2

buena publicación, pero es posible que desee agregar que está afiliado con el producto y la compañía mencionados, de lo contrario la gente comenzará a señalar esto llamando a su publicación un anuncio encubierto ;-) – none

+0

Esto suena como el camino a seguir ... y tener un código de aviónica significa que tiene que estar absolutamente seguro de que funciona. –

+0

@none - Conozco a Ira de comp.compilers. Esta conversión probablemente no esté en nuestras manos, no solo por el costo/riesgo, sino porque estamos explorando otras vías. Sin embargo, la respuesta es útil para otras personas con problemas similares ... –

4

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

Para las últimas partes, una vez que realmente tiene todo compilando con un compilador C++, me centraría en reemplazar las cosas que tienen contrapartes idiomáticas de C++. Por ejemplo, si usa listas, mapas, conjuntos, bitvectores, tablas hash, etc., que se definen con macros C, es probable que gane mucho moviéndolos a C++. Del mismo modo con OO, es probable que encuentre beneficios en los que ya esté utilizando una expresión idiomática C OO (como struct inheritence), y donde C++ le proporcione mayor claridad y un mejor tipo de verificación de su código.

2

Menciona que su herramienta es un compilador, y que: "En realidad, la coincidencia de patrones, no solo la coincidencia de tipos, en el despacho 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 a partir de una gramática abstracta, y visitantes, transformadores, etc.