Skip to content

Instantly share code, notes, and snippets.

@Marsgames
Last active February 15, 2024 09:06
Show Gist options
  • Save Marsgames/c9c20d50d2a0e3c81f369a4bcc6b53b3 to your computer and use it in GitHub Desktop.
Save Marsgames/c9c20d50d2a0e3c81f369a4bcc6b53b3 to your computer and use it in GitHub Desktop.
Ce document présente les good practices que nous respectons au sein de Headcrab.

Programmation Guideline

Ce document présente les good practices que nous respectons au sein de Headcrab.

Quand vous clonez un projet Unity OU que vous créez un nouveau projet Unity, vérifiez que le fichier setup.py est présent et exécutez le avant toute chose.

General programming

  • Les noms des classes sont en PascalCase --> class MyClass
  • Les classes de base servant à l'héritage doivent avoir comme suffixe Base --> class PlayerBase
  • Les variables membres sont en camelCase --> private var myPrivateVariable
  • Les variables public sont en PascalCase --> public var MyPublicVariable
  • Les constantes commencent par K_ puis sont en UPPER_SNAKE_CASE --> static K_MY_CONSTANT
  • Les interfaces commencent par un i majuscule puis sont en PascalCase --> interface ICanDoSomething
  • Les enumérations commencent par un e majuscule puis sont en PascalCase --> enum EType
  • Les valeurs des enum sont en PascalCase
  • Les lists, array, map... qui contiennent plusieurs éléments, sont au pluriel --> List scores; / int[,] positions;

Inheritance describes an is-a relationship.
Implementing an interface describes a can-do relationship.


Les accolades commencent sur la ligne du dessous.
(Meilleur lisibilité, on peut facilement voir où commence et se termine un bloc de code)

private void MyFunction()
{
    if (myVariable)
    {
        // Some instructions  
    }
    else
    {
        // One unique instruction
    }
}

Les accolades sont obligatoires MÊME s'il n'y a qu'une seule instruction dans le if.
(Lisibilité + Plus facile pour insérer du code)


Évitez les if imbriqués s'ils ne sont pas nécessaire.
On préfère sortir de la condition si elle n'est pas remplie.
(Sur des grosses fonctions c'est plus lisible + il est plus facile de voir qu'on sort directement de la fonction si la condition n'est pas remplie, plutôt que devoir scroll 300 lignes pour se rendre compte qu'il n'y avait pas de else après le "if (score > 100)")

Ce qu'il ne faut pas faire
private void GoToNextLevel()
{
    if (score > 100)
    {
        // On fait tout un tas de tests
        // On set quelques variables
        // Quelques commentaires pour expliquer

        if (playerSpeed > 5)
        {
            // On refait quelques autres tests
            // On assigne une variable
        }

        // On load enfin le prochain niveau
    }
}
Il vaut mieux faire
private void GoToNextLevel()
{
    // On sait directement que si score <= 100 on sort de la fonction
    if (score <= 100)
    {
        return;
    }

    // On fait toutes les actions nécessaires

    if (playerSpeed > 5)
    {
        // Some code ...
    }

    // Load next level
}



Fonctions :

Une fonction ne doit pas être plus grande que la taille d'un écran (on ne doit pas scroll)
⚠️ --> J'ai un écran 20" landscape <-- ⚠️
Faites des sous-fonctions plutôt qu'une énorme fonction de 1000 lignes



Espaces :

  • Placer un espace après une virgule, entre des opérateurs, après un if, un while ou un for...
  • Pas d'espace après l'ouverture, ou avant la fermeture d'une parenthèse, d'un crochet...
  • Pas d'espace entre un appel de fonction et ses paramètres --> var myVariable = MyFunction();


Booléens :

Préférez des booléens positifs.

private bool canShoot = false; // booléen "positif"
private bool cannotFly = true; // booléen "négatif"

private void Start()
{
    // Préférez
    if (!canShoot)
    {
        // Action if you cannot shoot;
    }

    // Plutôt que
    if (!cannotFly)
    {
        // Action if you cannot not fly 😵
    }
}


Commentaires :

Vos fonctions doivent posséder un header (Visual studio le génère automatiquement en faisant /// au dessus de la fonction) clair et concis.
Il est inutile de commenter chaque ligne de code. Cependant, les autres utilisateurs doivent pouvoir comprendre votre code.

Supprimez ce code que vous avez gardé, commenté, au cas où... Git est là pour s'en souvenir à votre place.



Variables :

Toutes les variables qui n'ont pas besoin d'être public doivent être private !
Toutes les variables utilisées doivent avoir un nom compréhensible, en rapport avec leur usage. (On accepte une variable i dans un for, "nous ne sommes pas des sauvages.")

Utilisez "var" le moins souvent possible. Tant que ça ne gène pas la lisibilité, préférez écrire le type.

// Plus lisible
int score;
int speed;
double accuracy = 2.5f;
string name;
bool hasShield = true;
List<GameObject> inventory;

// Moins lisible
var score = 0;
var name = "";
var speed = 0;
var hasShield = true;
var accuracy = 2.5f;
var inventory = new List<GameObject>();

En fait, utilisez toujours le nom du type quand il n'est pas explicitement nommé après, pour faciliter la lecture :

// Utilisation invalide
var e = GetHRByUniqueID(456); // Qui peut me dire de quel est le type de `e` ?
    
    
    
// Utilisation valide
HumanResource employee = GetHRByUniqueID(123); // Quelqu'un ne connaissant pas le code ne peut pas savoir quel est le type de retour de la fonction. Il faut donc expliciter le type
var car = new Car(); // Le code est suffisamment explicite (on crée un objet `Car`), il n'est donc pas nécessaire de préciser le type 


Autres :

  • On supprime les using non utilisés. Ils augmentent le temps de compilation pour rien.
    (Par défaut sur Visual Studio "ctrl + r - ctrl + g" supprime et ordonne vos using.)
  • Utilisez les initialiseurs pour faciliter la création d'objets.
var user = new User 
{
    Name = "me",
    Manager = "me",
    Salary = 0
};
  • Utilisez les using plutôt que les try-finally, si dans le finally la seule chose que vous faites est de fermer la connexion/pointeur sur un fichier, c'est que vous devriez utiliser un using !


Programmation défensive

Quand vous utilisez == toujours tester si la valeur est égale à la variable.

if (100 == score)
{
    // Do some code
}

Même si le problème n'existe pas en C#, en C++ le code suivant ne crée pas d'erreur.
(Une faute de frappe est si vite arrivée)

if (score = 100)
{

}

Pire, il assigne la valeur 100 à la variable, et retourne true (donc passe aussi dans le if, quoi qu'il arrive)
(Si vous essayez de faire "100 = score", cela ne compile pas, et vous vous épargnez pas mal de soucis)

Voyez l'exemple si dessous.
Exemple C++ Il est toujours bon de prendre de bonnes habitudes afin de les avoir et de ne pas se faire piéger quand on change de langage.

  • Faites des null check sur les objets et leurs membres
public User GetUserDetails(Address address)
{
    if (null != address && null != address.User)
    {
        return address.User;
    }
}


Strings



  • Quand vous souhaitez assembler des strings utilisez l'interpolation plutôt que la concaténation :
int year = 2020;
string helloWorld = $"Hey we are in {year}";
  • Pour comparer des strings utilisez toujours ToUpper() ou ToLower() avant de faire la comparaison pour éviter les surprises. Si vous voulez comparer une string vide, utilisez string.IsNullOrEmpty;
  • Utilisez des fichiers de ressources plutôt que des string hardcoded (exemple: identifiants BDD, chaines de connexion etc...)
  • Dans les boucles utilisez le StringBuilder aulieu de String qui consomme moins de ressources


Getter/Setter

Le rôle des getters et setters est d'accèder à un membre ou lui assigner une valeur. Faire du traitement complémentaire dans les getters / setters d'un membre est considéré comme une mauvaise pratique car le traitement est caché à l'utilisateur. Si l'on veut faire du traitement complémentaire sur un membre on définit une fonction dont c'est le rôle, nommé correctement et éventuellement avec un cartouche pour faciliter la compréhension.

C'est pour cela que l'on préfèrera utiliser les auto properties de C# pour définir les getters / setters dont les rôle est d'uniquement assigner ou retourner une valeur :

public int Test { get; private set; }

Si l'on souhaite faire un traitement supplémentaire sur la valeur avant l'assignation dans ce cas on utilisera un getter classique et une fonction dédié à l'assignation :

public int Test { get; private set; }

void AssignAndClamp(int value) 
{
    Test = value > 100 ? 20 : value; 
}

Pour rappel, les getters / setters ne sont pas automatique, par défaut la visibilité des memebres doit être privé cf: section variables



Unity

N'oubliez pas de télécharger le template de script que nous utilisons
(N'oubliez pas de changer la partie Author)
Pour changer le template par défaut, remplacer le script 81-C# par celui que vous venez de récupérer.

  • Windows: C:\Program Files\Unity\Editor\Data\Resources\ScriptTemplates
  • Mac: /Applications/Unity/Unity.app/Contents/Resources/ScriptTemplates

Dans Unity toutes les variables tout ce qui ne nécessite pas d'être public ou protected (variables static, functions public, variables Instance...) est PRIVATE
Les variables devant être affichées dans l'inspecteur sont [SerializeField] (NB: les variables public sont automatiquement sérializées, donc il n'y a pas de différence de performances avec une variable privée + sérializée. Pourquoi faire du privé + sérializé ? Parce qu'on ne laisse JAMAIS rien de public inutilement. On ne veut pas que n'importe qui accède à n'importe quoi !)

Même si par défaut tout est private, on indique toujours le mot clé private pour plus de lisibilité.



N'oubliez pas l'existence de [Tooltip()] pour afficher des informations dans l'éditeur

[Tooltip("Durée en seconde à attendre avant de récupérer sa vie\nValeur par défaut : 3")] float restTime = 3;

Exemple tooltip

N'oubliez pas l'existence de [Range(min, max)] pour créer un slider dans l'éditeur.
(Valable pour les int, float et double)

[Range(1, 10)] public int LifePoints = 3;

Exemple range

⚠️ Les *.meta doivent TOUJOURS être versionnés par git. Faites attention à ne pas les ignorer dans le .gitignore
(Ce gitignore devrait faire l'affaire pour la plupart des projets Unity)
(Il est bont de savoir que le site gitignore.io existe)



2 scripts GameManager peuvent être trouvés en suivant ce lien, le premier est le script singleton classique avec une variable Instance public. Le deuxième ajoute le minimum requis pour avoir des pubs en jeu.
Libre à vous de les utiliser ou non, à condition que vos scripts perso respectent ce qui est demandé dans ce document.



⚠️ Tous les scripts contenant un using UnityEditor; (sauf s'il y à un #if UNITY_EDITOR pour le gérer) doivent être placés dans le dossier Assets/Editor, sinon le cloud build ne fonctionne pas !



Les opérateurs '??', '?.' et '??=' ne doivent JAMAIS être utilisés avec des MonoBehaviour. En effet, Unity utilise sa propre implémentation de l'opérateur '==', or les opérateurs mentionnés précedemment utilisent l'opérateur '==' traditionnel. Le résultat ne sera donc pas toujours celui que vous retrouveriez en faisant le test vous même.



CSC.RSP

TOUJOURS compiler en considérant les warnings comme des erreurs !



Ajoutez un fichier csc.rsp dans votre dossier Assets, contenant la lligne suivante :
-warnaserror+

Il permet d'empêcher la compilation si des warnings sont présents.



Pour éviter les erreurs de type :
"error CS0649: Field 'm_myVariable' is never assigned to, and will always have its default value 0"
Une variable privée et sérializée doit toujours être initialisée à sa déclaration. (null, 0, "", ce que vous voulez...)



Si vous avez une erreur due à un warning que vous ne pouvez pas corriger (ex: l'utilisation de fonctions obsolète parce que vous devez vous servir de Unet, warning dans une dépendance...), vous pouvez supprimer l'erreur localement.
Ex : si vous avez une error CS0612 : function 'is obsolete" dans un fichier, ouvre le fichier et ajouter un #pragma warning disable CS0XXX (où XXX représente le code de l'erreur)

#pragma warning disable CS0612 // CS0612 is the code for obsolete warning
    [SerializeField, Obsolete] private int myVariable = 0;
#pragma warning restore

N'oubliez pas de faire un restore après.
(Le numéro de l'erreur s'affiche dans la console de Unity en même temps que l'erreur)



AutoSave.cs

Il est très fortement recommandé d'ajouter un fichier de type AutoSave dans votre dossier Assets/Editor



Ce script permet de sauvegarder votre scène automatiquement à chaque fois qu'on appuie sur Play.
(Très utile pour éviter les pertes de données en cas de crash d'Unity (une boucle infinie est si vite arrivée...))



Git

Vous devez toujours créer une nouvelle branche quand vous travaillez sur une nouvelle tâche.
Vous ne pouvez de toute façon rien pousser sur develop ou master sans être passés par une code review.



Une branche ne doit pas servir à faire l'entièreté du projet. Personne n'a envie de review 100 000 lignes de code en une fois.



Préfixez vos branches en fonction de ce que vous faites dessus :

  • Une feature : feature/add-speed-bonus
  • Un fix fix/#XX (référencez le numéro de l'issue git)
  • ...

Cela permet de savoir ce qu'il se passe sur cette branche.



Les noms et descriptions des commits doivent être le plus simple MAIS explicite possible.



Si vous avez une pull request ouverte, mais que vous n'avez pas fini de travailler dessus, vous devez OBLIGATOIREMENT la renommer pour la préfixer avec le tag WIP



N'hésitez pas à jeter un oeil à ce lien pour vous familiariser avec Git



Discord

Nous utilisons Discord pour communiquer au sein de la société.
Pour rejoindre le serveur, utilisez ce lien : https://discord.gg/TxfyJDR
Attention ce lien fait de vous un membre provisoire. Pensez à nous demander de vous attribuer un rôle, sinon vous serez expulsé du serveur à votre déconnexion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment