Meojres practicas para los contribuyentes al motor

Introducción

Godot cuenta con una gran cantidad de usuarios que tienen la capacidad de contribuir, ya que el proyecto está dirigido principalmente a usuarios con habilidades de programación. A pesar de esto, no todos tienen el mismo nivel de experiencia trabajando en proyectos grandes o en ingeniería de software, lo que puede llevar a malentendidos comunes y malas prácticas durante el proceso de contribuir código al proyecto.

Lenguaje

El objetivo de este documento es proporcionar una lista de mejores prácticas para que los colaboradores las sigan, así como crear un lenguaje que puedan utilizar para referirse a situaciones comunes que surgen en el proceso de enviar sus contribuciones.

Si bien algunos podrían encontrar útil extender esto al desarrollo de software en general, nuestra intención es limitarlo a situaciones que son más comunes en nuestro proyecto.

Las contribuciones se categorizan la mayor parte del tiempo como correcciones de errores, mejoras o nuevas características. Para abstraer esta idea, las llamaremos Soluciones, porque siempre buscan resolver algo que puede describirse como un Problema.

Mejores Prácticas

#1: El problema siempre viene primero

Muchos contribuyentes son extremadamente creativos y disfrutan simplemente del proceso de diseñar estructuras de datos abstractas, crear interfaces de usuario atractivas o simplemente aman programar. Sea cual sea el caso, tienen ideas interesantes, que pueden o no estar resolviendo problemas reales.

../../_images/best_practices1.png

Estas situaciones suelen ser llamadas Soluciones en busca de un problema. En un mundo ideal, estas soluciones no serían perjudiciales, pero en la realidad, el código lleva tiempo escribirlo, ocupa espacio como código fuente y binario, y requiere mantenimiento una vez que existe. Evitar agregar elementos innecesarios siempre se considera una buena práctica en el desarrollo de software.

#2: Para resolver el problema, este debe existir en primer lugar

Esta es una variación de la práctica anterior. Agregar cualquier cosa innecesaria no es una buena idea, pero ¿qué constituye lo que es necesario y lo que no lo es?

../../_images/best_practices2.png

La respuesta a esta pregunta es que el problema debe existir antes de que pueda ser realmente resuelto. No debe ser especulación ni una creencia. El usuario debe estar utilizando el software según lo previsto para crear algo que necesitan. En este proceso, el usuario puede encontrarse con un problema que requiere una solución para avanzar o para lograr una mayor productividad. En este caso, se necesita una solución.

Creer que pueden surgir problemas en el futuro y que el software debe estar listo para resolverlos en el momento en que aparezcan se conoce como "Future proofing", y se caracteriza por líneas de pensamiento como:

  • Creo que sería útil para los usuarios...

  • Creo que los usuarios querrán eventualmente...

Este es generalmente considerado un mal hábito porque intentar resolver problemas que en realidad no existen en el presente a menudo lleva a código que se escribe pero nunca se usa, o que es considerablemente más complejo de usar y mantener de lo que debería ser.

#3: El problema debe ser complejo o frecuente

El software está diseñado para resolver problemas, pero no podemos esperar que resuelva cada problema que existe bajo el sol. Como motor de juegos, Godot resolverá problemas para ti y te ayudará a mejorar y acelerar el desarrollo de juegos, pero no creará el juego completo por ti. Debe haber un límite.

../../_images/best_practices3.png

La decisión de si un problema vale la pena resolver se determina por la dificultad que tiene el usuario para sortearlo. Esta dificultad puede expresarse como:

  • La complejidad del problema

  • La frecuencia del problema

Si el problema es demasiado complejo para que la mayoría de los usuarios lo resuelva, el software debe ofrecer una solución lista para ello. Del mismo modo, si el problema es fácil de sortear para el usuario, ofrecer una solución de este tipo es innecesario y depende del usuario hacerlo.

La excepción, sin embargo, ocurre cuando el usuario se encuentra con este problema con suficiente frecuencia que tener que hacer la solución simple cada vez se vuelve molesto. En este caso, el software debe ofrecer una solución para simplificar este caso de uso.

En nuestra experiencia, en la mayoría de los casos suele ser obvio determinar si un problema es complejo o frecuente, pero pueden surgir situaciones en las que trazar esta línea sea difícil. Por eso siempre se recomienda discutir con otros desarrolladores (punto siguiente).

#4: La solución debe discutirse con otros

A menudo sucede que cuando los usuarios se encuentran con problemas, están completamente inmersos en su proyecto y naturalmente intentan resolver el problema desde su perspectiva, pensando únicamente en su caso de uso.

Debido a esto, las soluciones propuestas por los usuarios no siempre contemplan otros casos de uso que los desarrolladores suelen conocer, por lo que a menudo están sesgadas hacia sus propios requisitos.

../../_images/best_practices4.png

Para los desarrolladores, la perspectiva es diferente. Pueden considerar que el problema del usuario es demasiado único como para justificar una solución completa (en lugar de una solución provisional del usuario). También es posible que sugieran una solución parcial (generalmente más simple o a un nivel más bajo) que se aplique a una amplia gama de problemas conocidos y dejen el resto de la solución al usuario.

En cualquier caso, antes de intentar una contribución, es importante discutir los problemas reales con otros desarrolladores o colaboradores para llegar a un mejor acuerdo sobre la implementación.

La única excepción, en este caso, es cuando un área de código tiene un propietario claro (acordado por otros colaboradores), que se comunica directamente con los usuarios y tiene el conocimiento necesario para implementar una solución directamente.

Además, la filosofía de Godot es favorecer la facilidad de uso y el mantenimiento sobre el rendimiento absoluto. Las optimizaciones de rendimiento serán consideradas, pero es posible que no sean aceptadas si dificultan demasiado el uso o si agregan demasiada complejidad al código.

#5: A cada problema, su propia solución

Para los programadores, siempre es un desafío muy gratificante encontrar las soluciones más óptimas para los problemas. Sin embargo, en ocasiones las cosas pueden salirse de control y los programadores intentarán crear soluciones que resuelvan la mayor cantidad de problemas posible.

La situación a menudo empeorará cuando, para hacer que esta solución parezca aún más fantástica y flexible, también aparezcan los problemas basados en especulaciones puras (como se describe en el punto #2).

../../_images/best_practices5.png

El problema principal es que, en realidad, rara vez funciona de esa manera. La mayoría de las veces, escribir una solución individual para cada problema resulta en un código más simple y más fácil de mantener.

Además, las soluciones que se centran en problemas individuales son mejores para los usuarios, ya que encuentran algo que hace exactamente lo que necesitan, sin tener que aprender y recordar un sistema más complejo que solo necesitarán para tareas sencillas.

Las soluciones grandes y flexibles también tienen una desventaja adicional, y es que, con el tiempo, rara vez son lo suficientemente flexibles para todos los usuarios, quienes siguen solicitando más funciones adicionales (lo que hace que la API y el código sean cada vez más complejos).

#6: Satisfacer los casos de uso comunes, dejar la puerta abierta para los casos raros

Esto es una continuación del punto anterior, que explica aún más por qué esta forma de pensar y diseñar software es preferible.

Como se mencionó antes (en el punto #2), es muy difícil para nosotros (como seres humanos que diseñan software) comprender realmente todas las necesidades futuras de los usuarios. Intentar escribir estructuras muy flexibles que satisfagan muchas situaciones de uso a la vez suele ser un error.

Podemos crear algo que creemos que es brillante, pero cuando realmente se utiliza, nos daremos cuenta de que los usuarios ni siquiera utilizarán la mitad de ello, o que requerirán funciones que no se ajustan completamente a nuestro diseño original, lo que nos obliga a descartarlo o hacerlo aún más complejo.

La pregunta entonces es, ¿cómo diseñar software que brinde a los usuarios lo que sabemos que necesitan, pero que sea lo suficientemente flexible como para permitirles hacer lo que no sabemos que podrían necesitar en el futuro?

../../_images/best_practices6.png

La respuesta a esta pregunta es que, para asegurar que los usuarios aún puedan hacer lo que desean, necesitamos brindarles acceso a una API de bajo nivel que puedan utilizar para lograr lo que desean, incluso si requiere más trabajo por parte de ellos porque significa reimplementar alguna lógica que ya existe.

En escenarios del mundo real, estos casos de uso serán como mucho raros e infrecuentes, por lo que tiene sentido que se necesite escribir una solución personalizada. Es por eso que es importante seguir proporcionando a los usuarios los bloques de construcción básicos para hacerlo.

#7: Prefiere soluciones locales

Cuando buscas una solución para un problema, ya sea implementar una nueva característica o corregir un error, a veces el camino más fácil es agregar datos o una nueva función en las capas centrales del código.

El principal problema aquí es que agregar algo a las capas centrales que solo se usará desde un lugar lejano no solo hará que el código sea más difícil de seguir (dividido en dos), sino que también hará que la API central sea más grande, más compleja y más difícil de entender en general.

Esto es malo porque la legibilidad y limpieza de las API centrales siempre son de suma importancia, dado que mucho código depende de ellas, y también es clave para los nuevos colaboradores como punto de partida para aprender el código base.

../../_images/best_practices7.png

El razonamiento común para querer hacer esto es que generalmente es menos código simplemente agregar un parche en las capas centrales.

A pesar de esto, no se recomienda esta práctica. Generalmente, el código de una solución debe estar más cerca de donde se origina el problema, incluso si implica más código, duplicados, más complejidad o es menos eficiente. Puede requerir más creatividad, pero siempre es el camino recomendado.

#8: No utilices soluciones complejas para problemas simples

No todos los problemas tienen una solución simple y, muchas veces, la opción correcta es utilizar una biblioteca de terceros para resolver el problema.

Como Godot debe ser distribuido en una gran cantidad de plataformas, no podemos enlazar bibliotecas dinámicamente. En su lugar, las incluimos en nuestro árbol de código fuente.

../../_images/best_practices8.png

Como resultado, somos muy selectivos con lo que incluimos, y tendemos a preferir bibliotecas más pequeñas (de hecho, las que consisten en un solo archivo de cabecera son nuestras favoritas). Solo en casos en los que no hay otra opción terminamos incluyendo algo más grande.

Además, las bibliotecas deben utilizar una licencia lo suficientemente permisiva como para ser incluidas en Godot. Algunos ejemplos de licencias aceptables son Apache 2.0, BSD, MIT, ISC y MPL 2.0. En particular, no podemos aceptar bibliotecas con licencias GPL o LGPL, ya que estas licencias efectivamente prohíben la vinculación estática en software propietario (que es cómo Godot se distribuye en la mayoría de los proyectos exportados). Este requisito también se aplica al editor, ya que es posible que deseemos ejecutarlo en iOS a largo plazo. Dado que iOS no admite la vinculación dinámica, la vinculación estática es la única opción en esa plataforma.