La Belleza Excepcional del Código Fuente de Doom 3’s


doom3s

Por Shawn McGrath

Esta es una historia acerca del código fuente de Doom 3‘s y su belleza. Si, belleza. Permítanme explicar.

Después de publicar mi video juego Dyad me tomé un pequeño descanso. Leí algunos libros y miré algunas películas que había postergado por mucho tiempo. Estaba trabajando en la versión Europea de Dyad, pero durante ese tiempo estaba más que todo esperando por la retroalimentación del equipo de aseguramiento de calidad de Sony, así que tenía mucho tiempo libre. Luego de vagar alrededor de más o menos un mes, empecé a considerar seriamente que iba a hacer en seguida.

Deseaba extraer las partes reusables del motor-y de Dyad para un nuevo proyecto.

Cuando inicié a trabajar originalmente en Dyad era un juego muy limpio, un motor para juegos muy funcional que creé de una acumulación de años de trabajo en otros proyectos. Al finalizar Dyad tenía un desorden espantoso.

Al final de seis semanas de desarrollo de Dyad agregué alrededor de 13K líneas de código. MainMenu.cc se aumentó a 24,501 líneas. El que una vez fue un lindo código fuente se convirtió en un desorden acribillado con #ifdefs, punteros gratuitos en funciones, horrible SIMD en línea y código asm, hasta aprendí un nuevo término: “entropía de código.” Busqué por internet otros proyectos que pudiera usar para aprender como organizar cientos de miles de líneas de código. Después de mirar bastantes extensos motores de juegos estaba muy desalentado; el código fuente de Dyad en realidad no estaba tan mal comparado a todo lo que había allí afuera!

Insatisfecho, continué mirando y encontré un muy agradable análisis del código fuente de Doom 3 por el experto en computación Fabien Sanglard.

Gasté algunos días mirando el código de Doom 3 y leyendo el excelente artículo de Fabien, entonces triné:

Hay un estándar de codificación idTech 4 (.doc) que creo que vale la pena leer. Yo sigo la mayoría de esos estándares que trataré de explicar porque son buenos y porque específicamente hacen que el código de Doom 3 sea muy bonito.

Parseo Unificado y Análisis Léxico

Una de las cosas más inteligentes que vi en Doom es el uso genérico de su parser analizador léxico. Todos los archivos de recursos son ascii con sintaxis unificada que incluyen: scripts, archivos de animación, archivos de configuración, etc. todo es lo mismo. Esto permite que todos los archivos se lean y procesen por un pedacito de código. El parseador si es particularmente robusto, soportando la mayoría de los subconjuntos de C++. Al pegarse a un programa de análisis léxico unificado, todos los demás componentes del motor no necesitan preocuparse de la serialización de datos ya que existe código para eso. Esto hace todos los demás aspectos del código más limpio.

Constantes y Parámetros Rígidos

El código de Doom es bastante rígido, aunque no lo suficiente en mi opinión con respecto a constantes. Las constantes sirven para diversos propósitos los cuales creo que muchos programadores ignoran. Mi regla es “todo debería ser siempre contanste a menos que no lo pueda ser”. Me gustaría que todas las variables en C++ fueran constantes por defecto. Doom casi siempre conserva una política de parámetros de “no in-out”, esto significa que todo parámetro en una función o son de entrada o de salida, pero nunca ambos. Esto hace mucho más fácil de entender que pasa con una variable que pasas a una función. Por ejemplo:

figure1-sharpen

La definición de esta función me hace feliz!

Sólo por unas constantes ya sé muchas cosas:

  • El idPlane que se pasa como argumento no será modificado por esta función. Puedo usar de forma segura el “plane” después que se ejecute esta función sin chequear modificaciones en idPlane.
  • Sé que épsilon no será cambiado en la función, (aunque podría ser fácilmente copiada a otro valor y escalada por ejemplo, pero sería contraproducente)
  • front, back, frontOnPlaneEdges and backOnPlaceEdges son variables de salida. En estas se escribirá.
  • la constante final después del parámetro lista es mi favorita. Le indica idSurface::Split() no modificará a “surface” ella misma. Esta es una característica de mis favoritas de C++ que no existe en otros lenguajes. Me permite hacer algo como esto: void f(const idSurface &s) { s.Split(….); }   si Split no fue definida como constante Split(….) este código no compilará. Ahora sé que dondequiera que se llame f() no modificará “surface”, incluso si f() pasa a surface a otra función o llama algún Surface::method(). Las constantes me dicen mucho acerca de la función y además sugieren un gran diseño de sistema. Simplemente por leer la declaración de la función se que surface se puede dividir por un plane dinámicamente. En lugar de modificar surface, retorna nuevas surface, front y back, y opcionalmente frontOnPlaneEdges y backOnPlaneEdges.

Las constantes mandan y parámetros que no sean input/output es probablemente la cosa más sencilla a mi forma de ver que separan buen código de código bonito. Eso hace que todo un sistema sea más fácil de entender y más fácil de editar o mejorar.

Comentarios Mínimos

Este es una cuestión de estética, pero una cosa que hace del código de Doom una belleza es que no está sobre-comentado. He visto mucho código como este:

figure2-cropped

Encuentro esto extremadamente irritante. Puedo decir que hace este método por su nombre. Si la función no se puede inferir desde su nombre, su nombre debe ser cambiado. Si su nombre la describe demasiado, disminúyalo. Si no se puede realmente refactorizar y renombrar para describir su simple propósito sólo entonces está bien comentar. Creo que los programadores aprendieron en la escuela que los comentarios son buenos, pues no, no lo son. Los comentarios son malos a menos que sean totalmente necesarios y normalmente no lo son. Doom hace un excelente trabajo manteniendo los comentarios al mínimo. Usando el ejemplo idSurface::Split(), miremos como se comentaría:

//divide el surface en surface frontal y trasera, el surface como tal mantiene intacto

//frontOnPlaneEdges y backOnPlaneEdges opcionalmente almacenan los índices de los bordes en el plane dividido

//retorna un SIDE_?

La primera línea es completamente innecesaria. Sabemos eso de toda la información de la definición de la función. La segunda y la tercera pueden valer algo. Podemos inferir las propiedades de la segunda línea pero el comentario elimina ambigüedad potencial.

El código de Doom es en su mayor parte magistral con sus comentarios los cuales lo hacen mucho más fácil de leer. Se que esto puede ser muy simple para algunas personas, pero definitivamente creo que hay un limpio y buen camino para hacer esto. Por ejemplo, que puede pasar si alguien cambia la función y elimina la constante del final? Entonces el surface *PODRIA* ser cambiado desde la función y ahora el comentario no concuerda con el código. Comentarios extraños afectan la exactitud de la lectura del código y lo hacen más feo.

Espaciado

Doom no pierde espacio vertical:

Este es un ejemplo de t_stencilShadow::R_ChopWinding():

figure3-cropped

Puedo leer el algoritmo entero en 1/4 de mi pantalla, dejando los otros 3/4s para entender donde ese bloque de código encaja relativamente con el código que lo rodea. He visto mucho código como este:

figure4-cropped

Este es otro punto que falla en “estilo”. Programé por más de 10 años con este último estilo, pero me forcé a cambiarme a la forma más estricta mientras trabajaba en un proyecto hace seis años. Estoy contento de haber cambiado.

Las últimas son 18 líneas comparadas a las 11 anteriores. Eso es casi el doble número de líneas para una funcionalidad casi “EXACTA”. Eso significa que el siguiente pedazo de código no encaja en la pantalla para mi. Que es el siguiente pedazo?

figure5-cropped

Ese código no tiene sentido sin el pedazo anterior for loop. Si no respeta los espacios verticales el código será mucho más duro de leer, más duro de escribir, más duro de mantener y será menos bonito.

Otra cosa que creo que no es solo estético en el código id es que *SIEMPRE* usan llaves aunque sea opcional. Creo que es un crimen saltarse las llaves. He visto mucho código como este:

figure6-cropped

Este es código feo, es peor que poner { } en cada línea. No pude encontrar en el código de id algún ejemplo donde se saltaran los { }. Omitir los opcionales { } hace que este ciclo while() consuma más tiempo del que necesita realmente. También esto hace la edición un dolor, que pasa si necesito insertar un if nuevo en else if(c>d)?

Plantillas al mínimo

id hizo un grandioso trabajo en el mundo C++. Ellos reescribieron todas las funciones STL requeridas. Yo personalmente tengo una relación de odio-amor con las STL. En Dyad las usé en debug de código para administrar recursos dinámicos. En release reuní a todos los recursos así se podían cargar más rápidamente y no usé ninguna funcionalidad STL. El STL es agradable porque proporciona estructuras genéricas rápidas, pero está mal porque usándolo muy a menudo es propenso al código feo y con errores. Por ejemplo, miremos esta clase std::vector<T>. Digamos que quiero pasar sobre cada elemento:

original

Esto se puede simplificar con C++11:

original2

A mi personalmente no me gusta el uso de auto, creo que hace el código más fácil de escribir pero más difícil de leer. De pronto usaré el auto en los próximos años,  pero por ahora lo veo mal. Ni siquiera voy a mencionar las ridiculeces de algunos algoritmos STL como std:for_each o std::remove_if.

Eliminar un valor de un std::vector es una bobada también:

original3

Carambolas, eso va a ser digitado correctamente por cada programador cada vez!

id elimina toda ambigüedad: Ellos hicieron sus propios contenedores genéricos, clase string, etc. Los escribieron mucho menos genéricos que las clases STL, muy seguramente para hacerlos más fácil de entender. Usan muy pocas plantillas y específicos asignadores de memoria. El código STL está tan lleno de plantillas sin sentido que es imposible de leer.

El código C++ puede convertirse rápidamente en una revoltura y fealdad sin un orden de parte de los programadores. Para ver que tan malas se pueden poner las cosas, mire nada más el código fuente del STL. Las implementaciones Microsoft y GCC STL son probablemente el código más horrible que he visto. Inclusive cuando los programadores tienen cuidado extremo haciendo su código plantilla lo más leíble posible, es todavía un completo desastre. Dele una mirada a  Andrei Alexandrescu’s Loki library o a boost libraries, estos libros fueron escritos por algunos de los mejores programadores C++ en el mundo y tuvieron mucho cuidado de hacerlo tan bonito como es posible y aún así todavía es código feo y básicamente ilegible.

id arregla este problema simplemente sin sobrepasarse en las cosas genéricas. Ella tiene un HashTable<V> y una clase HashIndex. La HashTable fuerza la llave para ser un carácter constante (const char *) y el HashIndex es un int->int pair.

Pueden ver el resto del artículo original en http://kotaku.com/5975610/the-exceptional-beauty-of-doom-3s-source-code

Gracias a Shawn McGrath  por permitirme traducir su artículo.

One thought on “La Belleza Excepcional del Código Fuente de Doom 3’s”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s