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
protectedpour 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-catchutilise dans la couche infrastructurestd::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 |