miércoles, 14 de marzo de 2018

Organización de los archivos de un proyecto (parte 1)

Casi todas las plantillas de proyecto de ZinjaI crean una única carpeta en la que los fuentes, el archivo de proyecto y eventuales extras van juntos, y solo se separan en una subcarpeta (debug/release) los archivos generados en la compilación. Esto es lo más directo para empezar, y funciona bien cuando es un proyecto pequeño.

Pero cuando los binarios empiezan a requerir archivos de datos/recursos, el proyecto crece y tenemos varias decenas de fuentes, la arquitectura se complejiza y empezamos a dividir en módulos o bibliotecas, etc; entonces esa organización se vuelve engorrosa, la carpeta del proyecto, una ensalada; y hay que hacer algo al respecto.

La mayoría de los alumnos no considera esto hasta que es demasiado tarde, simplemente por falta de experiencia (o al menos eso me pasó a mí y lo aprendí por las malas).


ZinjaI no promueve ninguna organización desde el vamos por varios motivos. Por ej, porque la gran mayoría de los proyectos no van a crecer tanto, o porque la estructura que le gusta o sirve a cada uno no es siempre la misma. Pero el usuario puede reorganizar las cosas como quiera. Esta organización no debe depender del IDE. Hay que definirla de acuerdo al proyecto, y hacer luego que el IDE la respete.


Veamos a continuación algunas subcarpetas que podríamos tener en nuestra carpeta de proyecto para un caso más o menos usual:
  • src: aquí iría el código fuente. Todos los .cpp y (si el proyecto no es una biblioteca) los .h/.hpp; y cualquier otra cosa que se considere parte del código fuente (como por ejemplo el .zpr, o un manifest o un .rc en Windows). 
  • temp/build: así se denomina usualmente a la carpeta a la que van a parar los temporales que genera la compilación (los .o, y tal vez el ejecutable). Dentro, por razones espero que obvias, debería haber una subcarpeta para cada perfil de compilación. 
  • bin: esta carpeta es la que usualmente contendrá al ejecutable y a todos los demás archivos que este necesita para ejecutarse (recursos o datos que cargue en su ejecución, dlls de los que dependa, etc). Respecto al ejecutable, suelo poner aquí solo el de release, que es el que se distribuye, y dejar el de debug en temp.
  • include: si el proyecto es una biblioteca en lugar de una app, usualmente movemos los headers (.h/.hpp) a esta carpeta. El motivo es que para distribuir la biblioteca a un usuario final, hay que darle los headers y los binarios, pero no los .cpp. Entonces, separando include de src separamos la parte "pública" de la "privada" de nuestro código fuente.
  • doc: esta es opcional y su finalidad es obvia: contener la documentación del proyecto. De acuerdo al tipo de proyecto podrá ser sobre el código, sobre el software resultante, o ambas; y manual o autogenerada (ej, con Doxygen). Allí adentro dividan las cosas como quieran.
  • data/examples: algunos proyectos incluyen datos de prueba, casos ejemplo, etc. Estos archivos no son indispensables para el ejecutable, por eso no los pongo en bin, pero sí tienen que ver con la ejecución, con algún caso de uso o ejemplo en particular. Por ejemplo, cuando trabajo en algoritmos de generación/edición de mallas, aquí tengo las mallas de ejemplo.
  • tests: esta también será opcional (aunque muy muy recomendable), y como su nombre lo indica, tendrá lo necesario para correr las pruebas/tests que validen el funcionamiento de nuestro proyecto. Por ejemplo, en PSeInt, esta carpeta tiene montones de pequeños pseudocódigos, cada uno con la salida que debería generar, y un script de bash que los pasa a todos por el intérprete y verifica si la salida es la correcta.

Con una organización así es fácil distinguir muchas cosas. Por ejemplo:
  • ¿Qué debe registrar git? todo src, todo include, todo tests, tal vez todo examples, y las partes no autogeneradas de bin y docs si las hay.
  • ¿Qué debo distribuir al usuario final? Si es una aplicación, bin, examples y tal vez parte de docs. Si es una biblioteca, hay que agregar include.
  • ¿Qué se puede borrar sin perder nada? temp o build, ya que todo lo que está allí se regenera compilando; y tal vez parte de docs.


En un proyecto complejo, a esto puede necesitar ciertos matices adicionales. Por ejemplo, la arquitectura de nuestro sistema puede dictaminar la división en módulos/partes/bibliotecas/como-lo-llamen. Y aquí cada uno verá si replica esta estructura para cada módulo como si fueran varios proyectos, o si solo en src divide las cosas en subcarpetas. En MotoGT, por ejemplo, utilizo la segunda opción (porque las partes van a parar a un mismo ejecutable); mientras que PSeInt se acerca más a la primera (porque cada modulo es un ejecutable diferente). Otros ejemplos de casos a analizar individualmente podrían los códigos autogenerados (por herramientas como moc de qt).

Y hasta aquí la parte 1, con la "teoría". En la próxima parte, algunos tips para aplicar esto en ZinjaI,

No hay comentarios:

Publicar un comentario