Passer au contenu principal

Recommandations générales

Ce qui suit relève de recommandations, et non d’exigences. Si vous modifiez du code, il est logique de respecter la mise en forme du code existant. Un style de code est nécessaire pour garantir la cohérence. La cohérence facilite la lecture du code et les recherches dans le code. Nombre de ces règles n’ont pas de justification logique ; elles découlent des pratiques établies.

Formatage

1. La majeure partie de la mise en forme est effectuée automatiquement par 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.
inline void readBoolText(bool & x, ReadBuffer & buf)
{
    char tmp = '0';
    readChar(tmp, buf);
    x = tmp != '0';
}
4. Si le corps entier de la fonction est un seul statement, il peut être placé sur une seule ligne. Ajoutez des espaces autour des accolades (à l’exception de l’espace en fin de ligne).
inline size_t mask() const                { return buf_size() - 1; }
inline size_t place(HashValue x) const    { return x & mask(); }
5. Pour les fonctions. Ne pas mettre d’espaces autour des parenthèses.
void reinsert(const Value & x)
memcpy(&buf[place_value], &x, sizeof(x));
6. Dans les expressions if, for, while et autres, une espace est insérée avant la parenthèse ouvrante (contrairement aux appels de fonction).
for (size_t i = 0; i < rows; i += storage.index_granularity)
7. Ajoutez des espaces autour des opérateurs binaires (+, -, *, /, %, …) et de l’opérateur ternaire ?:.
UInt16 year = (s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 + (s[3] - '0');
UInt8 month = (s[5] - '0') * 10 + (s[6] - '0');
UInt8 day = (s[8] - '0') * 10 + (s[9] - '0');
8. Si un saut de ligne est saisi, placez l’opérateur sur une nouvelle ligne et augmentez l’indentation qui le précède.
if (elapsed_ns)
    message << " ("
        << rows_read_on_server * 1000000000 / elapsed_ns << " rows/s., "
        << bytes_read_on_server * 1000.0 / elapsed_ns << " MB/s.) ";
9. Vous pouvez utiliser des espaces pour aligner le contenu au sein d’une ligne, si vous le souhaitez.
dst.ClickLogID         = click.LogID;
dst.ClickEventID       = click.EventID;
dst.ClickGoodEvent     = click.GoodEvent;
10. N’utilisez pas d’espaces autour des opérateurs ., ->. 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 >.
template <typename TKey, typename TValue>
struct AggregatedStatElement
{}
15. Dans les classes et les structures, écrivez public, private et protected au même niveau que class/struct, et indentez le reste du code.
template <typename T>
class MultiVersion
{
public:
    /// Version of object for usage. shared_ptr manage lifetime of version.
    using Version = std::shared_ptr<const T>;
    ...
}
16. Si le même 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.
/// Finish write.
for (auto & stream : streams)
    stream.second->finalize();
18. Il ne doit pas y avoir d’espaces en fin de ligne. 19. Les fichiers sources sont encodés en UTF-8. 20. Les caractères non-ASCII peuvent être utilisés dans les littéraux de chaîne de caractères.
<< ", " << (timer.elapsed() / chunks_stats.hits) << " μsec/hit.";
21. N’écrivez pas plusieurs expressions sur une même ligne. 22. Regroupez les sections de code à l’intérieur des fonctions et ne les séparez pas par plus d’une ligne vide. 23. Séparez les fonctions, les classes, etc. par une ou deux lignes vides. 24. A const (associé à une valeur) doit être écrit avant le nom du type.
//correct
const char * pos
const std::string & s
//incorrect
char const * pos
25. Lors de la déclaration d’un pointeur ou d’une référence, les symboles * et & doivent être entourés d’espaces.
//correct
const char * pos
//incorrect
const char* pos
const char *pos
26. Lorsque vous utilisez des types template, donnez-leur un alias avec le mot-clé 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.
//correct
using FileStreams = std::map<std::string, std::shared_ptr<Stream>>;
FileStreams streams;
//incorrect
std::map<std::string, std::shared_ptr<Stream>> streams;
27. Ne déclarez pas plusieurs variables de types différents dans une seule instruction.
//incorrect
int x, *y;
28. N’utilisez pas de transtypages de style C.
//incorrect
std::cerr << (int)c <<; std::endl;
//correct
std::cerr << static_cast<int>(c) << std::endl;
29. Dans les classes et les structs, regroupez séparément les membres et les fonctions dans chaque scope de visibilité. 30. Pour les petites classes et les petits structs, il n’est pas nécessaire de séparer la déclaration des méthodes de leur implémentation. Il en va de même pour les petites méthodes, dans n’importe quelle classe ou struct. Pour les classes template et les structs template, ne séparez pas les déclarations des méthodes de leur implémentation (car sinon, elles doivent être définies dans la même unité de traduction). 31. Vous pouvez revenir à la ligne à 140 caractères, au lieu de 80. 32. Utilisez toujours les opérateurs de pré-incrémentation/prédécrémentation si la forme postfixée n’est pas nécessaire.
for (Names::const_iterator it = column_names.begin(); it != column_names.end(); ++it)

Commentaires

1. Veillez à commenter toutes les parties non triviales du code. C’est très important. Le simple fait de rédiger un commentaire peut vous faire comprendre que le code n’est pas nécessaire ou qu’il est mal conçu.
/** Part of piece of memory, that can be used.
  * For example, if internal_buffer is 1MB, and there was only 10 bytes loaded to buffer from file for reading,
  * then working_buffer will have size of only 10 bytes
  * (working_buffer.end() will point to position right after those 10 bytes available for read).
  */
2. Les commentaires peuvent être aussi détaillés que nécessaire. 3. Placez les commentaires avant le code qu’ils décrivent. Dans de rares cas, ils peuvent aussi se trouver après le code, sur la même ligne.
/** Parses and executes the query.
*/
void executeQuery(
    ReadBuffer & istr, /// Where to read the query from (and data for INSERT, if applicable)
    WriteBuffer & ostr, /// Where to write the result
    Context & context, /// DB, tables, data types, engines, functions, aggregate functions...
    BlockInputStreamPtr & query_plan, /// Here could be written the description on how query was executed
    QueryProcessingStage::Enum stage = QueryProcessingStage::Complete /// Up to which stage process the SELECT query
    )
4. Les commentaires doivent être rédigés en anglais uniquement. 5. Si vous écrivez une bibliothèque, incluez dans le fichier d’en-tête principal des commentaires détaillés qui l’expliquent. 6. N’ajoutez pas de commentaires qui n’apportent pas d’informations supplémentaires. En particulier, ne laissez pas de commentaires vides comme ceci :
/*
* Procedure Name:
* Original procedure name:
* Author:
* Date of creation:
* Dates of modification:
* Modification authors:
* Original file name:
* Purpose:
* Intent:
* Designation:
* Classes used:
* Constants:
* Local variables:
* Parameters:
* Date of creation:
* Purpose:
*/
L’exemple est tiré de la ressource http://home.tamk.fi/~jaalto/course/coding-style/doc/unmaintainable-code/. 7. N’écrivez pas de commentaires superflus (auteur, date de création, etc.) au début de chaque fichier. 8. Les commentaires sur une seule ligne commencent par trois barres obliques : /// 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.
/// WHAT THE FAIL???
14. N’utilisez pas de commentaires comme délimiteurs.
///******************************************************
15. N’engagez pas de discussion dans les commentaires.
/// Why did you do this stuff?
16. Il n’est pas nécessaire d’ajouter un commentaire à la fin d’un bloc pour en expliquer le contenu.
/// for

Noms

1. Utilisez des lettres minuscules et des caractères de soulignement dans les noms de variables et de membres de classe.
size_t max_block_size;
2. Pour les noms de fonctions (méthodes), utilisez le camelCase, avec une minuscule initiale.
std::string getName() const override { return "Memory"; }
3. Pour les noms de classes (structs), utilisez le CamelCase en commençant par une majuscule. Les interfaces n’utilisent pas de préfixe autre que I.
class StorageMemory : public IStorage
4. Les 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.
template <typename TKey, typename TValue>
struct AggregatedStatElement
6. Les noms des arguments constants d’un template : soit ils respectent les règles de nommage des variables, soit on utilise N dans les cas simples.
template <bool without_www>
struct ExtractDomain
7. Pour les classes abstraites (interfaces), vous pouvez ajouter le préfixe I.
class IProcessor
8. Si vous utilisez une variable localement, vous pouvez lui donner un nom court. Dans tous les autres cas, utilisez un nom qui décrit clairement ce qu’elle représente.
bool info_successfully_loaded = false;
9. Les noms des define et des constantes globales s’écrivent en MAJUSCULES avec des traits de soulignement.
#define MAX_SRC_TABLE_NAMES_TO_STORE 1000
10. Les noms de fichiers doivent suivre le même style que leur contenu. Si un fichier contient une seule classe, donnez-lui le même nom que la classe (CamelCase). Si le fichier contient une seule fonction, donnez-lui le même nom que la fonction (camelCase). 11. Si le nom contient une abréviation :
  • Pour les noms de variables, l’abréviation doit être en minuscules mysql_connection (et non mySQL_connection).
  • Pour les noms de classes et de fonctions, conservez les majuscules dans l’abréviation MySQLConnection (et non MySqlConnection).
12. Les arguments du constructeur utilisés uniquement pour initialiser les membres de la classe doivent porter le même nom que ces membres, mais avec un trait de soulignement à la fin.
FileQueueProcessor(
    const std::string & path_,
    const std::string & prefix_,
    std::shared_ptr<FileHandler> handler_)
    : path(path_),
    prefix(prefix_),
    handler(handler_),
    log(&Logger::get("FileQueueProcessor"))
{
}
Le suffixe « _ » peut être omis si l’argument n’est pas utilisé dans le corps du constructeur. 13. Il n’y a pas de différence entre les noms des variables locales et ceux des membres de la classe (aucun préfixe n’est requis).
timer (not m_timer)
14. Pour les constantes d’un enum, utilisez le CamelCase avec une majuscule initiale. ALL_CAPS est également accepté. Si l’enum n’est pas local, utilisez un enum class.
enum class CompressionMethod
{
    QuickLZ = 0,
    LZ4     = 1,
};
15. Tous les noms doivent être en anglais. La translittération de mots hébreux n’est pas autorisée. pas T_PAAMAYIM_NEKUDOTAYIM 16. Les abréviations sont acceptables si elles sont bien connues (si vous pouvez facilement en trouver la signification sur Wikipedia ou dans un moteur de recherche). 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.

Comment écrire du code

1. Gestion de la mémoire. La désallocation manuelle de la mémoire (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.
2. Gestion des ressources. Utilisez 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.
/// If there weren't any calculations yet, calculate the first block synchronously
if (!started)
{
    calculate();
    started = true;
}
else /// If calculations are already in progress, wait for the result
    pool.wait();

if (exception)
    exception->rethrow();
Ne masquez jamais les exceptions sans les traiter. Ne consignez jamais aveuglément toutes les exceptions dans les logs.
//Not correct
catch (...) {}
Si vous devez ignorer certaines exceptions, ne le faites que pour des exceptions bien précises et relancez toutes les autres.
catch (const DB::Exception & e)
{
    if (e.code() == ErrorCodes::UNKNOWN_AGGREGATE_FUNCTION)
        return nullptr;
    else
        throw;
}
Lorsque vous utilisez des fonctions avec des codes de réponse ou errno, vérifiez toujours le résultat et lancez une exception en cas d’erreur.
if (0 != close(fd))
    throw ErrnoException(ErrorCodes::CANNOT_CLOSE_FILE, "Cannot close file {}", file_name);
Vous pouvez utiliser assert pour vérifier un invariant dans le code. 4. Types d’exception. Il n’est pas nécessaire d’utiliser une hiérarchie d’exceptions complexe dans le code de l’application. Le message de l’exception doit être compréhensible pour un administrateur système. 5. Lever des exceptions depuis les destructeurs. Ce n’est pas recommandé, mais c’est autorisé. Utilisez les options suivantes :
  • Créez une fonction (done() ou finalize()) 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ù noexcept est utilisé par défaut en C++11) pour gérer les exceptions.
6. Blocs de code anonymes. Vous pouvez créer un bloc de code distinct à l’intérieur d’une même fonction afin de rendre certaines variables locales, de sorte que les destructeurs soient appelés à la sortie du bloc.
Block block = data.in->read();

{
    std::lock_guard<std::mutex> lock(mutex);
    data.ready = true;
    data.block = block;
}

ready_any.set();
7. Multithreading. Dans les programmes de traitement de données hors ligne :
  • Essayez d’obtenir les meilleures performances possibles sur un seul cœur de CPU. Vous pourrez ensuite paralléliser votre code si nécessaire.
Dans les applications serveur :
  • 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.
using AggregateFunctionPtr = std::shared_ptr<IAggregateFunction>;

/** Allows creating an aggregate function by its name.
  */
class AggregateFunctionFactory
{
public:
    AggregateFunctionFactory();
    AggregateFunctionPtr get(const String & name, const DataTypes & argument_types) const;
15. 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.
Loader(DB::Connection * connection_, const std::string & query, size_t max_block_size_);

/// For deferred initialization
Loader() {}
17. Fonctions virtuelles. Si la classe n’est pas destinée à un usage polymorphique, il n’est pas nécessaire de déclarer les fonctions comme virtuelles. Cela s’applique également au destructeur. 18. Encodages. Utilisez UTF-8 partout. Utilisez 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.
auto f() -> void
25. Déclaration et initialisation des variables.
//right way
std::string s = "Hello";
std::string s{"Hello"};

//wrong way
auto s = std::string{"Hello"};
26. Pour les fonctions virtuelles, écrivez 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

1. L’héritage virtuel n’est pas utilisé. 2. Les constructions qui bénéficient d’une syntaxe simplifiée en C++ moderne, par ex.
// Traditional way without syntactic sugar
template <typename G, typename = std::enable_if_t<std::is_same<G, F>::value, void>> // SFINAE via std::enable_if, usage of ::value
std::pair<int, int> func(const E<G> & e) // explicitly specified return type
{
    if (elements.count(e)) // .count() membership test
    {
        // ...
    }

    elements.erase(
        std::remove_if(
            elements.begin(), elements.end(),
            [&](const auto x){
                return x == 1;
            }),
        elements.end()); // remove-erase idiom

    return std::make_pair(1, 2); // create pair via make_pair()
}

// With syntactic sugar (C++14/17/20)
template <typename G>
requires std::same_v<G, F> // SFINAE via C++20 concept, usage of C++14 template alias
auto func(const E<G> & e) // auto return type (C++14)
{
    if (elements.contains(e)) // C++20 .contains membership test
    {
        // ...
    }

    elements.erase_if(
        elements,
        [&](const auto x){
            return x == 1;
        }); // C++20 std::erase_if

    return {1, 2}; // or: return std::pair(1, 2); // create pair via initialization list or value initialization (C++17)
}

Plateforme

1. Nous écrivons du code pour une plateforme spécifique. Mais, toutes choses égales par ailleurs, le code multiplateforme ou portable est préférable. 2. Langage : C++20 (voir la liste des fonctionnalités C++20 disponibles). 3. Compilateur : 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

1. KDevelop est un bon IDE. 2. Pour le débogage, utilisez 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

1. La bibliothèque standard C++20 est utilisée (les extensions expérimentales sont autorisées), ainsi que les frameworks 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

1. Écrivez le moins de code possible. 2. Essayez la solution la plus simple. 3. N’écrivez pas de code tant que vous ne savez pas comment il va fonctionner ni comment fonctionnera la boucle interne. 4. Dans les cas les plus simples, utilisez 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

1. Spécifier explicitement 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 :
function(
  T1 x1,
  T2 x2)
function(
  size_t left, size_t right,
  const & RangesInDataParts ranges,
  size_t limit)
function(size_t left, size_t right,
  const & RangesInDataParts ranges,
  size_t limit)
function(size_t left, size_t right,
      const & RangesInDataParts ranges,
      size_t limit)
function(
      size_t left,
      size_t right,
      const & RangesInDataParts ranges,
      size_t limit)
Dernière modification le 29 juin 2026