Recommandations générales
Formatage
clang-format.
2. L’indentation est de 4 espaces. Configurez votre environnement de développement de sorte qu’une tabulation ajoute quatre espaces.
3. Les accolades ouvrantes et fermantes doivent figurer sur une ligne distincte.
statement, il peut être placé sur une seule ligne. Ajoutez des espaces autour des accolades (à l’exception de l’espace en fin de ligne).
if, for, while et autres, une espace est insérée avant la parenthèse ouvrante (contrairement aux appels de fonction).
+, -, *, /, %, …) et de l’opérateur ternaire ?:.
., ->.
Si nécessaire, l’opérateur peut être renvoyé à la ligne suivante. Dans ce cas, le décalage qui le précède est augmenté.
11. N’utilisez pas d’espace pour séparer les opérateurs unaires (--, ++, *, &, …) de l’argument.
12. Placez un espace après une virgule, mais pas avant. La même règle s’applique au point-virgule à l’intérieur d’une expression for.
13. N’utilisez pas d’espaces pour séparer l’opérateur [].
14. Dans une expression template <...>, insérez un espace entre template et < ; pas d’espace après < ni avant >.
public, private et protected au même niveau que class/struct, et indentez le reste du code.
namespace est utilisé pour l’ensemble du fichier et qu’il n’y a rien d’autre de significatif, aucun décalage n’est nécessaire à l’intérieur du namespace.
17. Si le bloc d’une expression if, for, while ou autre ne contient qu’une seule statement, les accolades sont facultatives. Placez la statement sur une ligne séparée à la place. Cette règle s’applique également aux if, for, while imbriqués, …
Mais si l’instruction interne contient des accolades ou else, le bloc externe doit également être entouré d’accolades.
A const (associé à une valeur) doit être écrit avant le nom du type.
* et & doivent être entourés d’espaces.
using (sauf dans les cas les plus simples).
Autrement dit, les paramètres du template sont spécifiés uniquement dans using et ne sont pas répétés dans le code.
using peut être déclaré localement, par exemple à l’intérieur d’une fonction.
Commentaires
/// et les commentaires sur plusieurs lignes commencent par /**. Ces commentaires sont considérés comme de la “documentation”.
Remarque : vous pouvez utiliser Doxygen pour générer de la documentation à partir de ces commentaires. Mais Doxygen n’est généralement pas utilisé, car il est plus pratique de parcourir le code dans l’IDE.
9. Les commentaires sur plusieurs lignes ne doivent pas comporter de lignes vides au début et à la fin (à l’exception de la ligne qui ferme le commentaire).
10. Pour commenter du code, utilisez des commentaires ordinaires, pas des commentaires de “documentation”.
11. Supprimez les portions de code mises en commentaire avant de valider.
12. N’utilisez pas de grossièretés dans les commentaires ou le code.
13. N’utilisez pas de majuscules. N’abusez pas de la ponctuation.
Noms
using se nomment comme les classes.
5. Noms des arguments de type de template : dans les cas simples, utilisez T ; T, U ; T1, T2.
Pour les cas plus complexes, suivez les règles de nommage des classes ou ajoutez le préfixe T.
N dans les cas simples.
I.
define et des constantes globales s’écrivent en MAJUSCULES avec des traits de soulignement.
- Pour les noms de variables, l’abréviation doit être en minuscules
mysql_connection(et nonmySQL_connection). - Pour les noms de classes et de fonctions, conservez les majuscules dans l’abréviation
MySQLConnection(et nonMySqlConnection).
enum, utilisez le CamelCase avec une majuscule initiale. ALL_CAPS est également accepté. Si l’enum n’est pas local, utilisez un enum class.
AST, SQL.
Pas NVDH (une suite de lettres aléatoires)
Les mots tronqués sont acceptables si la forme abrégée est d’usage courant.
Vous pouvez également utiliser une abréviation si le nom complet figure à côté dans les commentaires.
17. Les noms de fichiers de code source C++ doivent avoir l’extension .cpp. Les fichiers d’en-tête doivent avoir l’extension .h.
18. Le nom du produit s’écrit ClickHouse — en un seul mot, avec un C majuscule et un H majuscule. La vérification de style clickhouse_spelling accepte également les orthographes conventionnelles des jetons clickhouse et CLICKHOUSE ; toutes les autres variantes sont des fautes d’orthographe. Elle vérifie le code, les commentaires, les messages, la documentation et les noms de fichiers. Utilisez clickhouse pour les jetons uniquement en minuscules, tels que les noms de paquets, d’exécutables et d’hôtes ; utilisez ClickHouse dans les identifiants CamelCase ; et utilisez CLICKHOUSE pour les macros et les variables d’environnement.
ClickHouse, clickhouse-client, CLICKHOUSE_DATABASE
Pas Clickhouse, clickHouse, click_house, CLICK_HOUSE, click-house, Click House
Comment écrire du code
delete) ne doit être utilisée que dans le code de bibliothèque.
Dans le code de bibliothèque, l’opérateur delete ne doit être utilisé que dans les destructeurs.
Dans le code applicatif, la mémoire doit être libérée par l’objet qui en est propriétaire.
Exemples :
- Le plus simple est de placer un objet sur la pile, ou d’en faire un membre d’une autre classe.
- Pour un grand nombre de petits objets, utilisez des conteneurs.
- Pour désallouer automatiquement un petit nombre d’objets alloués sur le tas, utilisez
shared_ptr/unique_ptr.
RAII et reportez-vous à ce qui précède.
3. Gestion des erreurs.
Utilisez des exceptions. Dans la plupart des cas, il suffit de lever une exception, sans avoir besoin de la capturer (grâce à RAII).
Dans les applications de traitement de données hors ligne, il est souvent acceptable de ne pas capturer les exceptions.
Dans les serveurs qui traitent les requêtes des utilisateurs, il suffit généralement de capturer les exceptions au niveau supérieur du gestionnaire de connexion.
Dans les fonctions de thread, vous devez capturer et conserver toutes les exceptions afin de les relancer dans le thread principal après join.
errno, vérifiez toujours le résultat et lancez une exception en cas d’erreur.
- Créez une fonction (
done()oufinalize()) qui effectuera à l’avance tout le travail susceptible de provoquer une exception. Si cette fonction a été appelée, il ne devrait plus y avoir d’exception dans le destructeur ensuite. - Les tâches trop complexes (comme l’envoi de messages sur le réseau) peuvent être placées dans une méthode distincte que l’utilisateur de la classe devra appeler avant la destruction.
- S’il y a une exception dans le destructeur, il vaut mieux l’écrire dans le journal que la masquer (si le logger est disponible).
- Dans les applications simples, il est acceptable de s’appuyer sur
std::terminate(pour les cas oùnoexceptest utilisé par défaut en C++11) pour gérer les exceptions.
- Essayez d’obtenir les meilleures performances possibles sur un seul cœur de CPU. Vous pourrez ensuite paralléliser votre code si nécessaire.
- Utilisez le pool de threads pour traiter les requêtes. Jusqu’à présent, nous n’avons eu aucune tâche nécessitant un changement de contexte en espace utilisateur.
fork n’est pas utilisé pour la parallélisation.
8. Synchronisation des threads.
Il est souvent possible de faire en sorte que différents threads utilisent des zones mémoire distinctes (mieux encore : des lignes de cache distinctes) et de se passer de toute synchronisation entre threads (à l’exception de joinAll).
Si une synchronisation est nécessaire, dans la plupart des cas, il suffit d’utiliser un mutex avec lock_guard.
Dans les autres cas, utilisez les primitives de synchronisation du système. N’utilisez pas l’attente active.
Les opérations atomiques ne doivent être utilisées que dans les cas les plus simples.
N’essayez pas d’implémenter des structures de données sans verrou, sauf si c’est votre domaine d’expertise principal.
9. Pointeurs ou références.
Dans la plupart des cas, privilégiez les références.
10. const.
Utilisez des références constantes, des pointeurs vers des constantes, const_iterator et des méthodes const.
Considérez const comme le choix par défaut et n’utilisez le non-const que lorsque c’est nécessaire.
Lors du passage de variables par valeur, utiliser const n’a généralement pas de sens.
11. unsigned.
Utilisez unsigned si nécessaire.
12. Types numériques.
Utilisez les types UInt8, UInt16, UInt32, UInt64, Int8, Int16, Int32 et Int64, ainsi que size_t, ssize_t et ptrdiff_t.
N’utilisez pas ces types pour représenter des nombres : signed/unsigned long, long long, short, signed/unsigned char, char.
13. Passage d’arguments.
Passez les valeurs complexes par valeur si elles doivent être déplacées, et utilisez std::move ; passez-les par référence si vous voulez mettre à jour la valeur dans une boucle.
Si une fonction prend possession d’un objet créé sur le tas, le type de l’argument doit être shared_ptr ou unique_ptr.
14. Valeurs de retour.
Dans la plupart des cas, utilisez simplement return. N’écrivez pas return std::move(res).
Si la fonction alloue un objet sur le tas et le renvoie, utilisez shared_ptr ou unique_ptr.
Dans de rares cas (mise à jour d’une valeur dans une boucle), il peut être nécessaire de renvoyer la valeur via un argument. Dans ce cas, l’argument doit être une référence.
namespace.
Il n’est pas nécessaire d’utiliser un namespace distinct pour le code applicatif.
Les petites bibliothèques n’en ont pas non plus besoin.
Pour les bibliothèques de taille moyenne à grande, placez tout dans un namespace.
Dans le fichier .h de la bibliothèque, vous pouvez utiliser namespace detail pour masquer les détails d’implémentation dont le code applicatif n’a pas besoin.
Dans un fichier .cpp, vous pouvez utiliser un namespace static ou anonyme pour masquer des symboles.
De plus, un namespace peut être utilisé pour un enum afin d’éviter que les noms correspondants ne se retrouvent dans un namespace externe (mais il est préférable d’utiliser un enum class).
16. Initialisation différée.
Si des arguments sont nécessaires pour l’initialisation, il ne faut normalement pas écrire de constructeur par défaut.
Si, plus tard, vous devez retarder l’initialisation, vous pouvez ajouter un constructeur par défaut qui créera un objet invalide. Ou, pour un petit nombre d’objets, vous pouvez utiliser shared_ptr/unique_ptr.
std::string et char *. N’utilisez pas std::wstring ni wchar_t.
19. Journalisation.
Voir les exemples présents partout dans le code.
Avant de valider, supprimez toute journalisation inutile ou de débogage, ainsi que tout autre type de sortie de débogage.
La journalisation dans les boucles doit être évitée, même au niveau Trace.
Les logs doivent être lisibles à tous les niveaux de journalisation.
La journalisation ne devrait, pour l’essentiel, être utilisée que dans le code applicatif.
Les messages de log doivent être rédigés en anglais.
Le log devrait de préférence être compréhensible pour l’administrateur système.
N’utilisez pas de grossièretés dans le log.
Utilisez l’encodage UTF-8 dans le log. Dans de rares cas, vous pouvez y utiliser des caractères non ASCII.
20. Entrée-sortie.
N’utilisez pas iostreams dans les boucles internes critiques pour les performances de l’application (et n’utilisez jamais stringstream).
Utilisez plutôt la bibliothèque DB/IO.
21. Date et heure.
Voir la bibliothèque DateLUT.
22. include.
Utilisez toujours #pragma once au lieu des gardes d’inclusion.
23. using.
using namespace ne doit pas être utilisé. Vous pouvez utiliser using pour quelque chose de spécifique. Mais limitez-le à une portée locale, à l’intérieur d’une classe ou d’une fonction.
24. N’utilisez pas trailing return type pour les fonctions, sauf si nécessaire.
virtual dans la classe de base, mais override à la place de virtual dans les classes dérivées.
Éléments de C++ non utilisés
Plateforme
clang. Au moment de la rédaction (mars 2025), le code est compilé avec clang version >= 19.
La bibliothèque standard est utilisée (libc++).
4. OS : Ubuntu Linux, version non antérieure à Precise.
5. Le code est écrit pour l’architecture CPU x86_64.
Le jeu d’instructions du CPU correspond au plus petit ensemble pris en charge par nos serveurs. Actuellement, il s’agit de SSE 4.2.
6. Utilisez les options de compilation -Wall -Wextra -Werror -Weverything avec quelques exceptions.
7. Utilisez l’édition de liens statique avec toutes les bibliothèques, sauf celles qu’il est difficile de lier statiquement (voir la sortie de la commande ldd).
8. Le code est développé et débogué en configuration Release.
Outils
gdb, valgrind (memcheck), strace, -fsanitize=... ou tcmalloc_minimal_debug.
3. Pour le profilage, utilisez Linux Perf, valgrind (callgrind) ou strace -cf.
4. Le code source est dans Git.
5. La compilation utilise CMake.
6. Les programmes sont publiés sous forme de paquets deb.
7. Les commits vers master ne doivent pas faire échouer la compilation.
Cependant, seules certaines révisions sont considérées comme exploitables.
8. Faites des commits aussi souvent que possible, même si le code n’est que partiellement prêt.
Utilisez des branches à cette fin.
Si votre code dans la branche master ne peut pas encore être compilé, excluez-le de la compilation avant le push. Vous devrez le terminer ou le supprimer dans les jours qui suivent.
9. Pour les modifications non triviales, utilisez des branches et publiez-les sur le serveur.
10. Le code inutilisé est supprimé du dépôt.
Bibliothèques
boost et Poco.
2. L’utilisation de bibliothèques issues de paquets du système d’exploitation est interdite. L’utilisation de bibliothèques préinstallées l’est également. Toutes les bibliothèques doivent être placées sous forme de code source dans le répertoire contrib et compilées avec ClickHouse. Voir Directives pour l’ajout de nouvelles bibliothèques tierces pour plus de détails.
3. La préférence est toujours donnée aux bibliothèques déjà utilisées.
Recommandations générales
using plutôt que des classes ou des structs.
5. Si possible, n’écrivez ni constructeurs de copie, ni opérateurs d’affectation, ni destructeurs (à l’exception d’un destructeur virtuel, si la classe contient au moins une fonction virtuelle), ni constructeurs de déplacement, ni opérateurs d’affectation par déplacement. En d’autres termes, les fonctions générées par le compilateur doivent fonctionner correctement. Vous pouvez utiliser default.
6. La simplification du code est encouragée. Réduisez la taille de votre code lorsque c’est possible.
Recommandations supplémentaires
std:: pour les types de stddef.h
n’est pas recommandé. Autrement dit, nous recommandons d’écrire size_t plutôt que std::size_t, car c’est plus court.
Ajouter std:: reste acceptable.
2. Spécifier explicitement std:: pour les fonctions de la bibliothèque standard C
n’est pas recommandé. Autrement dit, écrivez memcpy plutôt que std::memcpy.
La raison est qu’il existe des fonctions non standard similaires, comme memmem. Il nous arrive d’utiliser ces fonctions. Elles n’existent pas dans namespace std.
Si vous écrivez systématiquement std::memcpy au lieu de memcpy, alors memmem sans std:: paraîtra étrange.
Néanmoins, vous pouvez quand même utiliser std:: si vous le préférez.
3. Utiliser des fonctions C lorsque les mêmes fonctions sont disponibles dans la bibliothèque standard C++.
C’est acceptable si cela est plus efficace.
Par exemple, utilisez memcpy plutôt que std::copy pour copier de gros blocs de mémoire.
4. Arguments de fonction sur plusieurs lignes.
Tous les styles de retour à la ligne ci-dessous sont autorisés :