Como mejorar el tiempo de compilación en C++

 Mejorar el tiempo de compilación es la base para poder mejorar la calidad de todo nuestro código. En muchas ocasiones, la velocidad de compilación no recibe la atención que se merece dado que o bien se buscan soluciones "hardware" como mejores servidores de compilación o compilación distribuida o bien se acepta que a medida que el proyecto crece el tiempo de compilación también debe crecer, incluso cuando este incremento es desorbitado. 

El gran problema es que un proyecto con un tiempo de compilación excesivo es desmoralizante para el programador, afecta claramente a la calidad y velocidad de implementación de código e impide realizar técnicas de programación como TDD o refactor continuo. Tener un proyecto con un tiempo de compilación excesivo hace que tendamos a ser conservadores con el número de cambios y a reducir las pruebas, es decir, reduce drásticamente nuestra capacidad para mejorar la calidad de nuestro código.

Por si fuera poco, tambien reduce nuestra flexibilidad para adaptarnos a nuestros clientes. Imaginas ir a casa del cliente, hacer los cambios que el pida y tener que esperar 4 horas a que se compile el proyecto? Imaginas hacer un spike para hacer una demo sencilla y tener que esperar 1 hora?. Un tiempo excesivo de compilación nos resta agilidad y reduce la versatilidad de nuestro producto.

Ahora bien, como podemos mejorar nuestro tiempo de compilación en C++? Básicamente solo hay una manera, reduciendo el número de includes que el compilador tenga que procesar. Sencillo de decir, pero difícil de plasmar en el código, no? Por suerte, hay un conjunto de pasos que si los sigues te permitirán disminuir drásticamente el tiempo de compilación:


1. Los includes han de ir en el .cpp, no en el .h 

El motivo de esto es bastante sencillo. Muchas veces hay la tendencia inconsciente que como los .cpp de una clase hacen el include del .h correspondiente , no necesitamos poner includes en el .cpp y con que todo vaya en el .h ya tenemos suficiente. CRASO ERROR. Los .h incluyen otros .h, cuando un proyecto crece en tamaño el número de .h implícitos crece exponencialmente. Dado que es extremadamente raro que alguien haga un include de un .cpp, podemos asumir que todo include que podamos mover directamente del .h al .cpp es un include que solo se hará una vez en toda la compilación.


2. Se han de usar las predefiniciones en los .h

Una vez ya hemos movido los includes obvios a los .cpp, ahora tendremos que mover aquellos que no son tan sencillos de ver. Generalmente, cuando hacemos un include en un .h lo hacemos básicamente para poder tener las definiciones de las clases y tipos usados en las interficies de entrada/salida de nuestro .h. Dado que no necesitamos saber lo que hay dentro de las clases, sino poderle explicar al compilador que va a recibir o devolver una cierta clase o enum, podemos predefinir la clase y pasar el include al .cpp ( dado que en el .cpp si que usaremos la clase y necesitaremos tenerla incluida). 

Los enums nos obligaran a hacer include si o si. El truco es convertir los enums a enum class permitiéndonos esto hacer la predeclaración del enum como si fuera una clase. Puede implicar hacer un poco de refactor en el código, pero vale la pena.

Usando las predefiniciones de clases, gran parte de los includes ya se habrán movido a los .cpp y se habrá reducido en gran cantidad el volumen de includes del arbol de compilación.


3. Se han de eliminar todos los includes redundantes.

Uno de los grandes problemas de los includes es que una vez se han puesto da pereza miedo quitarlos. Los includes se van acumulando incluso cuando ya no son necesarios y en muchas ocasiones hacemos includes de .h que incluyen todo un grupo de funcionalidades extensas por que nos da bastante pereza buscar exactamente cual sería el .h que deberíamos incluir porque "total, así ya funciona". Deberemos solventar esto, revisando uno a uno todos los includes, tirando de lógica , intuición o inspiración divina.  

Un segundo punto a revisar es la necesidad de ciertos includes debido al árbol de includes que tengamos. Es decir, no necesitamos incluir un fichero que ya teníamos incluido de forma implícita en otro include. 

A veces, la mejor forma de mejorar estos puntos es comentar el include que creemos que no es necesario y compilar. Si se queja la compilación es que seguramente era necesario, sino se queja la compilación casi seguro que puedes eliminarlo. Es un proceso manual y lento pero que da sus frutos a largo plazo. Cada include que quitamos es un pequeño paso que hacemos para reducir el tiempo de compilación.


4. Se ha de refactorizar la estructura de includes.

Una vez ya hemos reducido "mecánicamente" el número de includes ahora toca una pensar un poco. Realmente es necesario hacer un include de un .h con 1000 funciones para usar solamente 1? Sería posible mover esa función a otro fichero? Realmente haría falta usar esa función? Hay otra manera de hacerlo? ...

Este punto no es fácil de implementar pero si tu objetivo es mejorar la calidad de código de tu proyecto es claramente una de las más importantes. Poner en duda todo, refactorizar y buscar maneras mejores y más organizadas de implementar el código es el camino para que nuestro código pueda seguir mejorando de una forma sostenible.


VALE, BIEN, PERO TRABAJO CON UN PROYECTO VIEJO Y GRANDE, QUE HAGO?

Exactamente lo mismo que acabo de explicar. Si tienes experiencia trabajando en proyectos grandes sabes que el problema de la compilación puede ser muy serio, y la solución parece imposible debido al volumen de ficheros. Bien, paciencia, tranquilidad y perseverancia, no te puedo decir mucho más, pero los únicos pasos que puedes dar son los que he explicado. La ventaja con los proyectos grandes es que aunque dé MUCHO miedo empezar a tocar includes , si eres mínimamente hábil escogiendo por donde empezar, los resultados se pueden ver en muy poco tiempo. Recuerda la ley de Pareto, con el 20% del esfuerzo obtendrás el 80% de los beneficios, es decir, con pocos includes que toques verás como el tiempo de compilación empieza a reducirse de forma drástica. 

Con constancia y paciencia conseguirás reducir los tiempos de compilación a unos niveles aceptables en muy poco tiempo.

No dudes en hacerme llegar tus preguntas sobre el tema

Nos vemos



Como mantener código antiguo en C++

Si has llegado a este artículo es que te preocupa la calidad de tu código y el mantenimiento de tus proyectos en C++ se está haciendo cada vez más costoso ( o ya directamente imposible )

Perfecto! Es buena señal que te preocupes por la calidad de tu trabajo, ahora bien, ¿ realmente estas enfocando correctamente tus esfuerzos?
En muchas ocasiones esforzarse en el mantenimiento de un proyecto C++ se centra en preocuparse por el código. Desgraciadamente esto solo es el 25% de lo que debes hacer para conseguir mantener tu código en óptimas condiciones:

1. Actitud
  Personalmente lo que creo que más limita el mantenimiento del código en proyectos antiguos es la actitud de los programadores de un equipo. Es muy fácil caer en el pesimismo y en la "dinamica de la pereza". Si realmente quieres que el proyecto tire adelante has de cuidar tu actitud hacía el proyecto: has de ser positivo pero realista, valiente pero no suicida. Sin la actitud adecuada ninguno de los puntos que vienen después tienen el más mínimo sentido

2. Estrategia
 Cuando hablamos de estrategia en el mantenimiento de código podríamos hablar de la filosofía con la que nos enfrentaremos a los problemas. La filosofía adecuada para hacer un mantenimiento óptimo de proyectos en C++ se podría resumir en que la calidad ha de estar por encima de todo. No deberíamos contraer deuda técnica ( o pagarla cuanto antes sea posible ), deberíamos ser conscientes que el objetivo principal del código es ser entendible por otro programador (el hecho que lo compilemos para generar un producto es circunstancial), deberíamos tener muy claro que cada vez que toquemos código ha de ser para dejarlo un poco mejor que como estaba. Sin estos conceptos claros ya podemos intentar hacer el mejor código del mundo que no lo conseguiremos.

3. Código
 Evidentemente, "tocar" el código, aunque no sea lo más importante, es un factor a tener en cuenta en el mantenimiento de un proyecto en C++. Todo lo que se puede decir sobre el código legible es que al final hay unas "recetas de cocina" la mar de bonitas que en cuanto las controlas tienes gran parte del trabajo hecho. El resumen seria eliminar todas las duplicaciones de código que fuera posible y que la nomenclatura de clases, variables y funciones sea coherente.

4. Herramientas
Aunque no sea el factor más determinante, tener un buen conjunto de herramientas de desarrollo ayudan , y mucho, a tener una buena calidad de código. Disponer de un sistema de versionado, un profiler, un debuger y un detector de memleaks, parece algo evidente, pero en muchas ocasiones es algo de lo que no se dispone. Si no lo tienes, consiguelo, te quitará de muchos problemas en el mantenimiento del código.
Aparte de estas herramientas básicas, un buen analizador estático de código y un sistema integrado de tests unitarios (o TDD) aporta un plus nada despreciable.

Espero que disfrutes de este blog tanto como yo disfruto al escribirlo Cualquier duda que tengas sobre mantenimiento de proyectos en C++ no vaciles en hacermela llegar en los comentarios.