Migliori pratiche per i contributori al motore

Introduzione

Godot ha un gran numero di utenti in grado di contribuire, poiché il progetto stesso si rivolge principalmente a utenti che sanno programmare. Detto questo, non tutti hanno lo stesso livello di esperienza in grandi progetti o nell'ingegneria informatica, il che può portare a malintesi e cattive pratiche durante il processo di contribuzione al progetto.

Lingua

The scope of this document is to be a list of best practices for contributors to follow, as well as to create a language they can use to refer to common situations that arise in the process of submitting their contributions.

Sebbene un elenco generalizzato delle migliori pratiche per lo sviluppo software possa essere utile, ci concentreremo sulle situazioni più comuni nel nostro progetto.

Contributions are most of the time categorized as bug fixes, enhancements or new features. To abstract this idea, we will call them Solutions, because they always seek to solve something that can be described as a Problem.

Migliori pratiche

#N°1: Il problema viene sempre prima

Molti collaboratori sono estremamente creativi e amano il processo di progettare strutture dati astratte, creare interfacce utente accattivanti o semplicemente programmare. In ogni caso, escogitano idee interessanti, che potrebbero o meno risolvere problemi reali.

../../_images/best_practices1.png

Queste sono solitamente chiamate soluzioni alla ricerca di un problema. In un mondo ideale, non sarebbero dannose, ma in realtà il codice richiede tempo per essere scritto, occupa spazio e richiede manutenzione una volta creato. Evitare di aggiungere qualsiasi cosa non necessaria è sempre considerata una buona pratica nello sviluppo software.

#N°2: Per risolvere il problema, deve innanzitutto esistere

Questa è una variazione della pratica precedente. Aggiungere qualcosa di superfluo non è una buona idea, ma cosa costituisce ciò che è necessario e ciò che non lo è?

../../_images/best_practices2.png

La risposta a questa domanda è che il problema deve esistere prima di poter essere effettivamente risolto. Non deve essere una speculazione o una convinzione. L'utente deve utilizzare il software come previsto per creare qualcosa di cui ha bisogno. In questo processo, l'utente potrebbe imbattersi in un problema che richiede una soluzione per procedere, o per ottenere una maggiore produttività. In questo caso, è necessaria una soluzione.

Credere che in futuro possano presentarsi dei problemi e che il software debba essere pronto a risolverli nel momento in cui si presentano è detto "Future proofing" ed è caratterizzato da linee di pensiero del tipo:

  • Penso che sarebbe utile per gli utenti a...

  • Penso che gli utenti alla fine avranno bisogno di...

Questa è generalmente considerata una cattiva abitudine perché cercare di risolvere problemi che non esistono nel presente spesso porterà a codice che sarà scritto ma mai utilizzato, o che è considerevolmente più complesso da usare e mantenere del necessario.

#N°3: Il problema deve essere complesso o frequente

Il software è progettato per risolvere problemi, ma non possiamo aspettarci che risolva ogni problema esistente sotto il sole. Come motore di gioco, Godot ti aiuterà a creare giochi migliori e più veloci, ma non creerà un gioco intero per te. Da qualche parte bisogna pur stabilire un limite.

../../_images/best_practices3.png

Se un problema valga la pena risolverlo è determinato dallo sforzo richiesto per risolverlo. Lo sforzo richiesto dipende da:

  • La complessità del problema

  • La frequenza del problema

Se il problema è troppo complesso da risolvere per la maggiorità degli utenti, il software dovrebbe offrire una soluzione pronta all'uso. Allo stesso modo, se il problema è facile da risolvere per l'utente, offrire una soluzione del genere è superfluo.

L'eccezione, tuttavia, occorre quando l'utente incontra un problema abbastanza frequentemente che dover ricorrere ogni volta alla soluzione più semplice diventa fastidioso. In questo caso, il software dovrebbe offrire una soluzione per semplificare il caso d'uso.

Di solito è facile capire se un problema è complesso o frequente, ma può essere difficile. Per questo motivo discuterne con altri sviluppatori (punto successivo) è sempre consigliabile.

#4: The solution must be discussed with others

Often, users will be immersed in their own projects when they stumble upon problems. These users will naturally try to solve the problem from their perspective, thinking only about their own use case. As a result, user proposed solutions don't always contemplate all use cases and are often biased towards the user's own requirements.

../../_images/best_practices4.png

For developers, the perspective is different. They may find the user's problem too unique to justify a solution (instead of a workaround), or they might suggest a partial (usually simpler or lower level) solution that applies to a wider range of known problems and leave the rest of the solution up to the user.

In any case, before attempting to contribute, it is important to discuss the actual problems with the other developers or contributors, so a better agreement on implementation can be reached.

The only exception is when an area of code has a clear agreed upon owner, who talks to users directly and has the most knowledge to implement a solution directly.

Also, Godot's philosophy is to favor ease of use and maintenance over absolute performance. Performance optimizations will be considered, but they may not be accepted if they make something too difficult to use or if they add too much complexity to the codebase.

#5: To each problem, its own solution

For programmers, it is always a most enjoyable challenge to find the most optimal solutions to problems. It is possible to go overboard, though. Sometimes, contributors will try to come up with solutions that solve as many problems as possible.

The situation will often take a turn for the worse when, in order to make this solution appear even more fantastic and flexible, the pure speculation-based problems (as described in #2) also make their appearance on stage.

../../_images/best_practices5.png

The main problem is that, in reality, it rarely works this way. Most of the time, writing an individual solution to each problem results in code that is simpler and more maintainable.

Additionally, solutions that target individual problems are better for the users. Targeted solutions allow users find something that does exactly what they need, without having to learn a more complex system they will only need for simple tasks.

Big and flexible solutions also have an additional drawback which is that, over time, they are rarely flexible enough for all users. Users end up requesting more and more functionality which ends up making the API and codebase more and more complex.

#6: Cater to common use cases, leave the door open for the rare ones

This is a continuation of the previous point, which further explains why this way of thinking and designing software is preferred.

As mentioned before (in point #2), it is very difficult for us (as human beings who design software) to actually understand all future user needs. Trying to write very flexible structures that cater to many use cases at once is often a mistake.

We may come up with something we believe is brilliant, but later find out that users will never even use half of it or that they require features that don't quite fit into our original design, forcing us to either throw it away or make it even more complex.

The question is then, how do we design software that both allows users to do what we know they need to do now and allows them to do what we don't yet know they'll need to do in the future?

../../_images/best_practices6.png

The answer to this question is that, to ensure users still can do what they want to do, we need to give them access to a low-level API that they can use to achieve what they want, even if it's more work for them because it means reimplementing some logic that already exists.

In real-life scenarios, these use cases will be at most rare and uncommon anyway, so it makes sense a custom solution needs to be written. This is why it's important to still provide users the basic building blocks to do it.

#7: Prefer local solutions

When looking for a solution to a problem, be it implementing a new feature or fixing a bug, sometimes the easiest path is to add data or a new function in the core layers of code.

The main problem here is, adding something to the core layers that will only be used from a single location far away will not only make the code more difficult to follow (split in two), but also make the core API larger, more complex, more difficult to understand in general.

This is bad, because readability and cleanness of core APIs is always of extreme importance given how much code relies on it, and because it's key for new contributors as a starting point to learning the codebase.

../../_images/best_practices7.png

A common reason for wanting to do this is that it's usually less code to simply add a hack in the core layers.

Non è consigliabile farlo. In genere, il codice per una soluzione dovrebbe essere più vicino all'origine del problema, anche se richiede codice aggiuntivo, duplicato, più complesso o meno efficiente. Potrebbe essere necessaria più creatività, ma questa è sempre la strada consigliata.

#8: Don't use complex canned solutions for simple problems

Non tutti i problemi hanno una soluzione semplice e, spesso, la scelta giusta è quella di utilizzare una libreria di terze parti per risolvere il problema.

Poiché Godot deve essere distribuito su un gran numero di piattaforme, non possiamo collegare le librerie dinamicamente. Invece, le includiamo nel nostro albero sorgente.

../../_images/best_practices8.png

Pertanto, siamo molto esigenti con ciò che inseriamo e tendiamo a preferire librerie più piccole (quelle a singolo header sono le nostre preferite). Includiamo qualcosa di più grande solamente se non abbiamo altra scelta.

Le librerie devono utilizzare una licenza sufficientemente permissiva per essere incluse in Godot. Alcuni esempi di licenze accettabili sono Apache 2.0, BSD, MIT, ISC e MPL 2.0. In particolare, non possiamo accettare librerie con licenza GPL o LGPL, poiché queste licenze di fatto impediscono il collegamento statico in software proprietario (ovvero il modo cui Godot viene distribuito, in gran parte dei progetti esportati). Questo requisito si applica anche all'editor, poiché potremmo volerlo eseguire su iOS a lungo termine. Poiché iOS non supporta il collegamento dinamico, il collegamento statico è l'unica opzione disponibile su tale piattaforma.