Aller au contenu

Conventions de Codage — Pratiques actuelles de Christophe

Contexte

Ce document regroupe les conventions de codage appliquées dans nos projets précédents. Il est destiné à Léo comme base pour rédiger le document de référence officiel du projet.

1. Nommage

1.1 Classes et Structs

Convention : PascalCase

class GameWorld { };
class MongoDBUserRepository { };
struct PlayerState { };
struct NetworkPlayer { };
struct AABB { };              // Exception : acronymes tout en majuscules

1.2 Interfaces

Convention : Prefixe I + PascalCase

class IWindow { };            // Abstraction graphique
class IScene { };             // Scene de jeu
class ILogger { };            // Logging
class IUIElement { };         // Element d'interface
class IUserRepository { };    // Port de persistence

Regles :

  • Methodes uniquement pure virtual (= 0)
  • Destructeur virtuel par defaut (virtual ~IScene() = default;)
  • Pas de membres prives (sauf protected pour contexte derive)

1.3 Fonctions et Methodes

Convention : camelCase

void processInput();
float calculateDistance();
void handleEvent(const events::Event& event);
void loadTexture(const std::string& key, const std::string& path);

Patterns de nommage :

Pattern Usage Exemples
get<Propriete>() Getter getPosition(), getSize(), getId()
set<Propriete>(val) Setter setPosition(pos), setVisible(true)
is<Etat>() Predicat booleen isAlive(), isConnected(), isOpen()
has<Chose>() Verification d'existence hasForce(), hasOverlay()
on<Evenement>() Callback/handler onConnected(), onReceive()

1.4 Variables

Variables locales : camelCase

int playerCount;
float deltaTime;
std::string sessionToken;

Membres de classe prives/proteges : _camelCase (prefixe underscore)

class Player {
private:
    int _health;
    float _speed;
    std::string _displayName;
    std::vector<NetworkPlayer> _players;
    std::mutex _playersMutex;

protected:
    bool _visible = true;
    bool _focused = false;
};

Membres statiques : s_camelCase (prefixe s_)

class Logger {
private:
    static std::shared_ptr<spdlog::logger> s_networkLogger;
    static std::shared_ptr<spdlog::logger> s_domainLogger;
};

Membres publics de structs : camelCase (pas de prefixe)

struct NetworkPlayer {
    uint8_t id;
    uint16_t x;
    uint16_t y;
    bool alive;
    uint32_t score;
    uint8_t chargeLevel = 0;
};

1.5 Constantes

Convention : SCREAMING_SNAKE_CASE

static constexpr float SCREEN_WIDTH = 1920.0f;
static constexpr float MOVE_SPEED = 200.0f;
static constexpr uint8_t DEFAULT_HEALTH = 100;
static constexpr size_t BUFFER_SIZE = 4096;
static constexpr const char* COLLECTION_NAME = "user";

1.6 Enums

Valeurs d'enum : PascalCase

enum class Key {
    A, B, C, D, E,
    Num0, Num1, Num2,
    Space, Enter, Escape,
    Up, Down, Left, Right,
    LShift, RShift, LCtrl, RCtrl,
    Unknown
};

enum class MessageType : uint16_t {
    HeartBeat = 0x0001,
    JoinGame = 0x0010,
    Snapshot = 0x0040,
    PlayerInput = 0x0061,
    ShootMissile = 0x0080,
};

1.7 Namespaces

Convention : snake_case, aligne sur la structure de repertoires

// Serveur - Architecture hexagonale
namespace domain::entities { }
namespace domain::value_objects { }
namespace domain::value_objects::user { }
namespace domain::exceptions { }
namespace domain::services { }
namespace domain::constants { }
namespace application::use_cases::auth { }
namespace application::ports::out::persistence { }
namespace application::services { }
namespace infrastructure::game { }
namespace infrastructure::room { }
namespace infrastructure::session { }
namespace infrastructure::adapters::in::network { }
namespace infrastructure::adapters::out::persistence { }
namespace infrastructure::ecs::systems { }
namespace infrastructure::ecs::components { }

// Client
namespace core { }
namespace client::network { }
namespace graphics { }
namespace events { }
namespace audio { }
namespace ui { }
namespace gameplay { }

// Commun
namespace protocol { }
namespace collision { }

1.8 Type Aliases et Callbacks

Type aliases : PascalCase

using Event = std::variant<None, WindowClosed, KeyPressed, KeyReleased>;
using PlayerMap = std::unordered_map<uint8_t, ConnectedPlayer>;

Callbacks : PascalCase + suffixe Callback

using OnConnectedCallback = std::function<void()>;
using OnDisconnectedCallback = std::function<void()>;
using OnReceiveCallback = std::function<void(const std::string&)>;
using OnSnapshotCallback = std::function<void(const std::vector<NetworkPlayer>&)>;

1.9 Nommage des Fichiers

Convention : PascalCase, correspond au nom de la classe

Player.hpp / Player.cpp
UDPClient.hpp / UDPClient.cpp
GameWorld.hpp / GameWorld.cpp
MongoDBUserRepository.hpp / MongoDBUserRepository.cpp
IWindow.hpp
AABB.hpp

Extensions : .hpp pour les headers, .cpp pour les implementations.

1.10 Acronymes

Regle : garder tels quels, pas de decoupage

class UDPClient { };          // Pas "UdpClient"
class TCPAuthServer { };      // Pas "TcpAuthServer"
class MongoDBUserRepository { }; // Pas "MongoDbUserRepository"
struct AABB { };              // Pas "Aabb"

1.11 Tableau Recapitulatif Nommage

Element Convention Exemple
Classes PascalCase GameWorld, UDPClient
Interfaces I + PascalCase IWindow, IScene
Methodes camelCase getPosition(), handleEvent()
Variables locales camelCase playerCount, deltaTime
Membres prives _camelCase _health, _players
Membres statiques s_camelCase s_networkLogger
Membres publics (struct) camelCase id, score, alive
Constantes SCREAMING_SNAKE_CASE MAX_PLAYERS, MOVE_SPEED
Valeurs d'enum PascalCase HeartBeat, KeyPressed
Namespaces snake_case domain::entities
Fichiers PascalCase GameWorld.hpp
Callbacks PascalCase + Callback OnConnectedCallback
Type aliases PascalCase Event, PlayerMap

2. Formatage

2.1 Indentation

4 espaces. Jamais de tabs.

void function() {
    if (condition) {
        doSomething();
    }
}

2.2 Accolades

Style K&R (meme ligne) pour fonctions, blocs, if/else, for, while :

void function() {
    if (condition) {
        // ...
    } else {
        // ...
    }

    for (int i = 0; i < n; i++) {
        // ...
    }

    while (running) {
        // ...
    }
}

Note : Dans la pratique, certaines definitions de classes utilisent un style Allman (accolade sur nouvelle ligne). La norme officielle est K&R partout.

2.3 Longueur de Ligne

Maximum : 100 caracteres.

Quand une signature depasse, aligner les parametres :

// Mauvais
void function(int veryLongParameterName, float anotherVeryLongParameterName, std::string yetAnotherLongName);

// Bon
void function(int veryLongParameterName,
              float anotherVeryLongParameterName,
              std::string yetAnotherLongName);

2.4 Espacement

Pas d'espace avant les parentheses d'appel de fonction :

doSomething();          // Bon
doSomething ();         // Mauvais

Pas d'espace a l'interieur des parentheses :

if (condition) { }      // Bon
if ( condition ) { }    // Mauvais

Espace autour des operateurs binaires :

int result = a + b;     // Bon
int result=a+b;         // Mauvais

if (x == 0 && y > 1) { }  // Bon
if (x==0&&y>1) { }        // Mauvais

2.5 Placement de const (West Const)

const a gauche du type :

const Player& player;          // Bon (West const)
Player const& player;          // Mauvais (East const)

const std::string& getName() const;  // Bon
void process(const std::vector<int>& data);  // Bon

2.6 Alignement Pointeurs et References

Attaches au type, pas a la variable :

int* ptr;                      // Bon
int *ptr;                      // Mauvais

const Player& player;          // Bon
const Player &player;          // Mauvais

std::unique_ptr<Player> player;
graphics::IGraphicPlugin* plugin = nullptr;

2.7 Lignes Vides

Une ligne vide entre les methodes :

void Player::move(float dx, float dy) {
    _position.move(dx, dy);
}

void Player::heal(float value) {
    _health = _health.heal(value);
}

Pas de lignes vides entre les declarations de membres :

private:
    Vec2f _position;
    Vec2f _velocity;
    std::string _tag;
    bool _alive = true;

Ligne vide entre sections logiques :

void update() {
    // Recuperer les entites
    auto entities = getEntities();
    auto missiles = getMissiles();

    // Traiter les collisions
    for (const auto& e : entities) {
        checkCollision(e, missiles);
    }
}

2.8 Formatage des Lambdas

Compact si simple, multiligne si complexe :

// Simple - meme ligne
auto getId = [this]() { return _nextId++; };

// Multiligne
_missileIdGenerator = [this]() {
    return _nextMissileId++;
};

// Dans un visitor
std::visit(overloaded {
    [&](graphic::GraphicTexture& t) { initTexture(t); },
    [&](graphic::GraphicSprite& s) { initSprite(s); },
}, asset);

2.9 Formatage Switch/Case

Cases au niveau du switch, contenu indente de 4 espaces :

switch (input.enemyType) {
    case 0:  // Basic
    {
        float amplitude = enemy::AMPLITUDE;
        // ...
        break;
    }
    case 1:  // Tracker
    {
        // ...
        break;
    }
    default:
        break;
}

3. Headers

3.1 Include Guards

Format : #ifndef FILENAME_HPP_

#ifndef GAMEWORLD_HPP_
#define GAMEWORLD_HPP_

// ...

#endif /* !GAMEWORLD_HPP_ */
  • Nom en SCREAMING_SNAKE_CASE + _HPP_
  • Commentaire de fermeture : /* !FILENAME_HPP_ */
  • Ne PAS utiliser #pragma once

3.2 Ordre des Includes

// 1. Header associe (pour les .cpp)
#include "GameWorld.hpp"

// 2. Headers du projet (quotes, chemins relatifs)
#include "Protocol.hpp"
#include "domain/entities/Player.hpp"

// 3. Headers de bibliotheques tierces
#include <boost/asio.hpp>
#include <spdlog/spdlog.h>

// 4. Headers standard (grossierement alphabetique)
#include <memory>
#include <string>
#include <vector>

3.3 Forward Declarations

Preferer quand possible pour reduire les dependances :

namespace domain::entities {
    class Player;
}

class GameWorld {
    std::vector<std::unique_ptr<domain::entities::Player>> _players;
};

4. Structure des Classes

4.1 Ordre des Sections

class GameWorld {
public:
    // 1. Types publics et aliases
    using PlayerMap = std::unordered_map<uint8_t, ConnectedPlayer>;

    // 2. Constantes
    static constexpr int MAX_PLAYERS = 4;

    // 3. Constructeurs / Destructeur
    explicit GameWorld(boost::asio::io_context& io_ctx);
    ~GameWorld() = default;

    // 4. Suppression copie
    GameWorld(const GameWorld&) = delete;
    GameWorld& operator=(const GameWorld&) = delete;

    // 5. Move (si necessaire)
    GameWorld(GameWorld&&) = default;
    GameWorld& operator=(GameWorld&&) = default;

    // 6. Methodes publiques
    void tick();
    std::optional<uint8_t> addPlayer(const udp::endpoint& endpoint);

private:
    // 7. Methodes privees
    void processInputs();
    void checkCollisions();

    // 8. Membres (underscore prefix)
    PlayerMap _players;
    uint32_t _currentTick = 0;
};

4.2 Constructeurs

Toujours utiliser les listes d'initialisation :

Position::Position(float x, float y, float z)
    : _x(x), _y(y), _z(z)
{
    validate(x, y, z);
}

explicit sur les constructeurs a un seul argument :

explicit Position(float x = 0.0f, float y = 0.0f, float z = 0.0f);
explicit Username(const std::string& username);

4.3 Organisation du .cpp

Meme ordre que le .hpp :

namespace domain::entities {

// Constructeur
Player::Player(...) : _health(health), _id(id), _position(pos) {}

// Getters
const PlayerId& Player::getId() const { return _id; }
const Position& Player::getPosition() const { return _position; }

// Methodes principales
void Player::move(float dx, float dy) { ... }
void Player::heal(float value) { ... }
void Player::takeDamage(float value) { ... }

}  // namespace domain::entities

4.4 Struct vs Class

Utiliser struct Utiliser class
Donnees pures (pas de methodes) Encapsulation necessaire
Structures reseau : UDPHeader, PlayerState Entites domaine : Player, User
Configuration : GameContext Managers/Services : Logger, Engine
Events : KeyPressed, MouseMoved Interfaces : IWindow, IScene

5. Commentaires

5.1 En-tete de Fichier (Style Epitech)

Obligatoire dans chaque fichier :

/*
** EPITECH PROJECT, 2025
** rtype
** File description:
** GameWorld - Manages game state and players
*/

5.2 Style de Commentaires

Commentaires inline : // (prefere)

// Clamp pour eviter les positions invalides hors ecran
position.x = std::clamp(position.x, 0.0f, WORLD_WIDTH);

Commentaires de bloc : /* */ uniquement pour les en-tetes de fichier.

Documentation Doxygen : /** avec @param, @brief :

/**
 * @brief Suite de tests pour l'entite Player
 *
 * Player est une entite de domaine representant un joueur.
 */

5.3 Regles de Commentaires

  • Commenter le POURQUOI, pas le QUOI
  • // TODO: pour les taches a faire
  • // FIXME: pour les bugs connus
  • // HACK: pour les workarounds temporaires
  • Separateurs de section avec // ===...=== pour regroupements visuels

6. Gestion des Erreurs

6.1 Exceptions

Hierarchie basee sur std::exception :

class DomainException : public std::exception {
private:
    std::string _message;
public:
    explicit DomainException(const std::string& message);
    const char* what() const noexcept override;
};

Regles :

  • Validation dans le constructeur ou methode privee validate()
  • Exceptions levees immediatement, jamais de codes d'erreur
  • try-catch utilise dans la couche infrastructure
  • std::optional<T> pour les resultats de requete
// Exception dans le domaine
void Position::validate(float x, float y, float z) {
    if (x < -1000.0f || x > 1000.0f)
        throw exceptions::PositionException(x, y, z);
}

// Optional pour les requetes
std::optional<User> findByName(const std::string& name);

6.2 Smart Pointers

Type Usage
std::unique_ptr<T> Ownership unique, transfert de propriete
std::shared_ptr<T> Interfaces partagees (IWindow, ILogger)
T* (raw) Parametres sans ownership, references temporaires
std::unique_ptr<DynamicLib> _dynamicLib;              // Unique
std::shared_ptr<graphics::IWindow> _window;           // Partage
graphics::IGraphicPlugin* _graphicPlugin = nullptr;   // Non-owning

7. C++ Moderne

7.1 auto

Utiliser avec parcimonie, quand le type est evident :

// Bon : type evident
auto it = map.find(key);
auto ptr = std::make_unique<Player>();
for (const auto& player : _players) { }

// Mauvais : type non evident
auto result = calculate();  // Quel type ?

7.2 const et constexpr

constexpr int MAX_PLAYERS = 4;              // Compile-time
void process(const Player& player);          // Parametre non modifie
bool isAlive() const;                        // Methode const
constexpr bool intersects(const AABB& o) const;  // Compile-time method

7.3 Range-based For

// Preferer
for (const auto& player : _players) {
    process(player);
}

// Eviter
for (size_t i = 0; i < _players.size(); i++) {
    process(_players[i]);
}

7.4 using vs typedef

// Preferer using (moderne)
using PlayerMap = std::unordered_map<uint8_t, ConnectedPlayer>;
using OnConnectedCallback = std::function<void()>;

// typedef seulement pour les pointeurs de fonction C-style
typedef graphics::IGraphicPlugin* (*create_t)();

7.5 Value Objects Immutables

Les value objects du domaine retournent de nouvelles instances :

class Health {
public:
    Health heal(float value) const {
        return Health(_healthPoint + value);  // Nouvelle instance
    }
};

// L'entite mute par reassignation
void Player::heal(float value) {
    _health = _health.heal(value);
}

8. Tests

8.1 Framework

Google Test (GTest) avec fixtures.

8.2 Nommage

Element Convention Exemple
Fichier test ComponentTest.cpp WeaponSystemTest.cpp
Classe fixture ComponentTest class HealthTest : public ::testing::Test
Cas de test TEST_F(Fixture, ActionDescriptive) TEST_F(HealthTest, HealIsImmutable)

8.3 Structure

class HealthTest : public ::testing::Test {
protected:
    void SetUp() override { }
    void TearDown() override { }
};

// Groupe par fonctionnalite avec commentaires de section
// ============ Tests de creation =============

TEST_F(HealthTest, CreateWithMinValue) {
    ASSERT_NO_THROW({
        Health health(0.0f);
        EXPECT_FLOAT_EQ(health.value(), 0.0f);
    });
}

// ============ Tests d'immutabilite =============

TEST_F(HealthTest, HealIsImmutable) {
    Health original(2.0f);
    Health healed = original.heal(1.0f);
    EXPECT_FLOAT_EQ(original.value(), 2.0f);
}

8.4 Emplacement

Les tests miroir la structure source :

tests/server/ecs/systems/WeaponSystemTest.cpp
 └─ teste ─→ src/server/infrastructure/ecs/systems/WeaponSystem.cpp

tests/common/CollisionSystemTest.cpp
 └─ teste ─→ src/common/collision/AABB.hpp

9. Logging

9.1 Framework

spdlog encapsule dans une classe statique Logger.

9.2 Loggers par Composant

Logger::getNetworkLogger()->info("Client connected: {}", endpoint);
Logger::getDomainLogger()->warn("Invalid position: ({}, {})", x, y);
Logger::getEngineLogger()->error("Failed to load plugin: {}", path);

9.3 Format

[2025-12-01 14:30:22.123] [info] [network] Client connected: 192.168.1.1:4124

9.4 Niveaux

Niveau Usage
trace Debug tres detaille
debug Information de debug
info Evenements normaux
warn Situations anormales mais gerees
error Erreurs necessitant attention

10. Conventions Git

10.1 Format des Commits

Conventional Commits : type(scope): description

feat(server): implement enemy wave system
fix(client): resolve memory leak in audio manager
docs(api): add GameWorld class documentation
refactor(network): simplify packet compression
test(ecs): add WeaponSystem unit tests

10.2 Types

Type Description
feat Nouvelle fonctionnalite
fix Correction de bug
docs Documentation
style Formatage (pas de changement de code)
refactor Refactoring
perf Amelioration de performance
test Tests
chore Maintenance (build, CI)

10.3 Scopes

Scope Description
server Code serveur
client Code client
network Protocole reseau
audio Systeme audio
graphics Rendu graphique
docs Documentation
ci CI/CD

10.4 Branches

Convention preferee : type/description-kebab-case

feature/voice-chat
feature/tls-csprng-security
fix/audio-memory-leak

11. Scripts

11.1 Nommage

kebab-case.sh : build.sh, run-client.sh, compile.sh, lint.sh

11.2 Structure Standard

#!/usr/bin/env bash
set -e

# Couleurs
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'

# Fonctions utilitaires
print_success() { echo -e "${GREEN}[OK]${NC} $1"; }
print_error()   { echo -e "${RED}[ERREUR]${NC} $1"; }

# Parsing des arguments
for arg in "$@"; do
    case $arg in
        --platform=*)
            PLATFORM="${arg#*=}"
            ;;
    esac
done

12. Documentation

12.1 Outil

MkDocs avec theme Material.

12.2 Nommage des fichiers

kebab-case.md : voice-chat.md, conventions.md, ci-cd.md

12.3 Front Matter

---
tags:
  - developpement
  - conventions
---

12.4 Langue

Francais pour toute la documentation et les commentaires de commits. Exception : termes techniques en anglais (Hexagonal Architecture, etc.).


13. Patterns Specifiques au Projet

13.1 Serialisation Reseau

struct PlayerState {
    static constexpr size_t WIRE_SIZE = 23;

    void to_bytes(uint8_t* buf) const {
        buf[0] = id;
        uint16_t net_x = swap16(x);  // Host to network (big-endian)
        std::memcpy(buf + 1, &net_x, 2);
    }

    static std::optional<PlayerState> from_bytes(
        const void* buf, size_t len)
    {
        if (len < WIRE_SIZE) return std::nullopt;
        // Parse avec swap16/swap32 pour network to host
    }
};

13.2 Architecture Hexagonale

Regle Description
Domain JAMAIS depend d'Infrastructure Dependances centripetes uniquement
Value Objects immutables Retournent de nouvelles instances
Protocole binaire big-endian to_bytes() / from_bytes() avec swap
Multi-backend graphique Toute modification d'IWindow necessite SFML ET SDL2

14. Configuration du Compilateur

14.1 Warnings Obligatoires

-Wall -Wextra -Wpedantic

14.2 Warnings Supplementaires (Clang)

-Wconversion -Wshadow -Wnon-virtual-dtor

14.3 Sanitizers (Debug, natif uniquement)

-fsanitize=address      # Erreurs memoire
-fsanitize=undefined    # Comportement indefini
-fsanitize=leak         # Fuites memoire

15. Fichiers de Configuration Manquants

Le projet ne possede pas encore de fichiers de configuration de formatage automatique. A considerer pour le referentiel final :

Fichier Statut Recommandation
.clang-format Absent A creer pour automatiser le formatage
.clang-tidy Absent A creer pour les verifications statiques
.editorconfig Absent A creer pour les editeurs

16. Checklist PR

  • Code formate correctement (4 espaces, K&R, 100 chars max)
  • Pas de warnings de compilation (-Wall -Wextra -Wpedantic)
  • Tests unitaires passent
  • Documentation mise a jour si necessaire
  • Commit messages suivent conventional commits
  • Namespaces corrects (infrastructure::, domain::, etc.)
  • Membres avec underscore prefix (_member)
  • En-tete Epitech present dans les nouveaux fichiers
  • Include guards (pas #pragma once)
  • Ordre des includes respecte (associe > projet > tiers > standard)

17. Ecarts Identifies entre Documentation et Pratique

Point Documentation Pratique Observee Recommandation
Accolades K&R partout Classes parfois Allman Harmoniser sur K&R
Includes Projet > Tiers > Standard Parfois melange Clarifier l'ordre
Langue commits Non precise Francais predominant Officialiser le francais
Nommage assets Non documente Mixte (PascalCase, camelCase, snake_case) Definir une convention