Les bonnes pratiques QML

Les bonnes pratiques QML


En bref

Bonnes pratiques QML pour écrire un code clair, maintenable et performant avec Qt Quick

Éviter les pièges classiques du QML (bindings, logique dans l’UI)
Organiser ses fichiers et dossiers avec des structures adaptées
Appliquer un ordre clair pour les propriétés, signaux et fonctions
Découpler l’interface de la logique métier pour plus de maintenabilité
Concevoir des composants avec une API simple et réutilisable
15 min
Intermédiaire

Sommaire

Dans cet article, je partage avec vous quelques bonnes pratiques QML qui vous permettront d’éviter les pièges classiques et de construire des interfaces à la fois claires, réactives et faciles à faire évoluer.

Les bonnes pratiques en développement : le secret des projets qui durent

Combien de fois avez-vous entendu (ou pensé) : “Ce code est un bordel, on devrait tout réécrire !”

Que vous codiez en QML, Python, JavaScript ou autre, cette phrase est un symptôme : quelque part, les bonnes pratiques n’ont pas été appliquées. Ou pire, elles ont été ignorées au profit de la rapidité.

Dans cet article, je partage avec vous ce que j’ai appris (parfois à la dure) sur les bonnes pratiques, les conventions et les pièges à éviter.


Une bonne pratique, c’est quoi ?

Pour moi, c’est une habitude ou une règle qui apporte un bénéfice tangible :

  • Visible : Moins de bugs, livraisons plus rapides.
  • Invisible (mais crucial) : Code plus lisible, équipe plus sereine, projet plus facile à faire évoluer.

Exemple : Nommage clair des variables, séparation des responsabilités, ordre de déclaration des propriétés… Des détails ? Oui. Mais c’est comme l’huile dans un moteur : sans elle, tout grince.

⚠️ Attention : Certaines bonnes pratiques demandent un effort initial. Un junior peut les trouver “inutiles”, un sénior y verra un gain de temps sur le long terme. C’est normal : leur valeur se révèle avec l’expérience.


Convention : l’art de s’accorder

Une convention, c’est une règle arbitraire mais partagée. Peu importe que vous utilisiez des tabulations ou des espaces, l’important est que toute l’équipe fasse de même.

Pourquoi ?

  • Cohérence : Tout le monde parle le même langage.
  • Efficacité : Moins de temps perdu à décrypter le style de chacun.

Mon conseil : Documentez vos conventions (un CONTRIBUTING.md ou un wiki suffisent). Ça évite les débats sans fin et les Merge/Pull Request bloquées pour des détails.


Les mauvaises pratiques : le piège du “ça marche, donc c’est bon”

Les mauvaises pratiques sont comme des dettes techniques : on les contracte sans y penser, et un jour, elles nous explosent à la figure.

Quelques signes qui ne trompent pas :

  • “On n’a pas le temps de refactorer, on verra plus tard.” (Spoiler : ce jour n’arrive jamais.)
  • “C’est plus simple de copier-coller ce code que de le généraliser.” (Jusqu’à ce que vous ayez 10 versions du même bout de logique.)
  • “Ça compile, donc c’est bon.” (Jusqu’à ce qu’un bug se cache dans un coin et gâche votre vendredi soir.)
  • “Des fichiers QML de 500+ lignes, parce que c’est plus simple de tout mettre au même endroit’.” (Spoiler : non, ce n’est pas plus simple quand il faut déboguer ou ajouter une feature.)

Mon conseil : Prenez le temps de faire les choses bien, même si c’est plus long au début. Votre vous futur vous remerciera.


À qui s’adresse cet article ?

  • Aux débutants : Pour partir sur de bonnes bases.
  • Aux développeurs expérimentés : Pour revisiter vos habitudes.
  • Aux équipes : Pour aligner tout le monde et éviter les frustrations.

Et maintenant, place à la pratique ! Nous allons maintenant voir comment appliquer ces principes concrètement en QML (organisation, lisibilité, maintenabilité…). Mais d’abord, une question : Quelle est la pire mauvaise pratique que vous ayez vue (ou commise) ? 😉 N’hésitez pas à répondre en commentaire de cet article.

Mon cauchemar à moi ? Un projet où tous les fichiers étaient entassés à la racine – pas de dossiers, pas de structure, juste 200 ou 300 fichiers C++ perdus dans un même dossier. Résultat : une chasse au trésor quotidienne pour retrouver une classe. Le pire ? Les seniors de l’équipe trouvaient ça normal après 15 ans de développement, et les juniors en alternance croyaient dur comme fer que c’était la façon standard d’organiser un projet. Bref, l’enfer du développeur.


Pourquoi QML mérite une attention particulière ?

QML est un langage déclaratif et flexible, idéal pour créer des interfaces fluides et dynamiques. Mais cette liberté a un prix : sans rigueur, on se retrouve vite avec du code spaghetti, où la hiérarchie visuelle ne reflète plus la structure logique, et où les bindings (property: value) deviennent des pièges sournois.

Pourquoi QML est-il différent ? QML a une particularité : il laisse la possibilité de fusionner interface et logique (grâce au JavaScript intégré). Sans vigilance, cette flexibilité se retourne contre vous :

  • La structure du code doit épouser la hiérarchie visuelle — sinon, vous vous retrouvez avec des composants impossibles à réutiliser ou à tester.
  • Les bindings mal gérés (comme un width: parent.width * someComplexFormula) rendent le comportement de l’application imprévisible et difficile à déboguer.
  • La logique métier glisse dans l’UI — un Button qui calcule un prix ou valide un formulaire, c’est tentant… mais c’est aussi la porte ouverte aux bugs et à la dette technique.

Conséquence ? Un code où tout dépend de tout, où une modification mineure casse trois écrans, et où personne n’ose plus toucher à rien.

Heureusement, quelques règles simples suffisent à éviter le chaos. Passons-les en revue, section par section.


Organisation du code

Par où commencer ? Par l’organisation, bien sûr ! Une structure de projet claire, c’est comme une bonne fondation : si elle est solide, tout le reste suit. Sinon, préparez-vous à des nuits blanches de refactoring.

Comment organiser son répertoire projet ?

Dans un projet professionnel mixant C++ et QML, on se retrouve souvent avec une tonne de fichiers : .h, .cpp, .qml, .js, .png, .svg, .ttf, CMakeLists.txt, etc. L’enjeu ? Trouver un équilibre entre rigueur et flexibilité, car il n’y a pas de solution universelle. Voici deux exemples de structures que j’ai pu expérimenter, avec leurs forces et leurs limites.


Structure de base (projet simple)

project_name/
├── include/               # En-têtes C++
│   └── project_name/
│       └── cpp_folder/
│           └── MyClass.h
├── resources/             # Ressources statiques (images, polices, etc.)
│   ├── images/
│   └── fonts/
├── src/
│   ├── main.cpp           # Point d'entrée C++
│   ├── cpp_folder/        # Implémentations C++
│   │   └── MyClass.cpp
│   └── qml/               # Code QML
│       └── qml_folder/
│           └── Component.qml
├── test/                  # Tests unitaires
│   └── cpp_folder/
│       └── MyClassTest.h
├── .clang-format          # Formatage automatique
├── .clang-tidy            # Analyse statique
├── .gitignore
└── CMakeLists.txt         # Configuration du build

Pourquoi ça marche ?

  • Séparation claire entre C++ et QML.
  • Ressources centralisées dans un dossier dédié.
  • Tests isolés pour éviter les conflits.

Structure avancée (projet complexe)

project_name/
├── app/                 # Application principale (même structure que ci-dessus)
|
├── core_library/        # Librairie C++ (métier + technique, sous dépôt Git)
|
├── ui_library/          # Librairie QML (composants graphiques réutilisables, sous dépôt Git)
│   ├── token/           # Tokens de design (couleurs, polices, tailles)
│   ├── atom/            # Composants atomiques (boutons, champs de texte)
│   ├── molecule/        # Composants composites (section + titre + bouton)
│   ├── organism/        # Composants complexes (headers, footers)
│   └── resources/       # Ressources spécifiques à l’UI
|
├── modules/             # Modules indépendants (chaque module = un sous-projet Git)
│   ├── module1/
│   └── module2/
|
├── .clang-format
├── .clang-tidy
├── .gitignore
├── .gitmodules          # Pour les sous-modules Git
└── CMakeLists.txt

Pourquoi cette structure ?

  • Modularité : Chaque partie du projet est autonome et peut évoluer indépendamment.
  • Réutilisabilité : Les librairies core_library et ui_library sont autonomes et peuvent être partagées entre plusieurs projets.
  • Scalabilité : Ajouter un nouveau module ou une nouvelle feature ne casse pas l’existant.

À retenir :

  • Adaptez la structure à votre équipe et vos contraintes.
  • Documentez vos choix ! Peu importe où, le principal étant que tout le monde puisse y accéder facilement (README.md, Confluence, Notion, etc.).
  • Évitez les dossiers “fourre-tout” comme misc/ ou temp/ – ils deviennent vite des poubelles numériques.

Comment organiser ses fichiers et dossiers QML ?

Je vous présente ici deux approches pour organiser vos fichiers QML. Libre à vous de les mixer selon vos besoins !

Organisation fonctionnelle (par feature)

Idéale si votre application est orientée métier (ex. : une app de bons plans).

project_name/
└── qml/
    ├── header/                         # En-tête de l'application
    │   ├── Header.qml
    │   └── SettingsPanel.qml
    ├── dashboard/                      # Tableau de bord
    │   ├── Dashboard.qml
    │   └── MyGoodDeals.qml
    ├── menu/                           # Menu de navigation
    │   ├── Menu.qml
    │   └── MenuItem.qml
    ├── feature/                        # Features métiers
    │   ├── profil/
    │   │   ├── ProfilPage.qml
    │   │   └── ProfilResumePanel.qml
    │   └── deal/
    │       ├── DealPage.qml
    │       └── LastDealListPanel.qml
    └── Main.qml                        # Point d'entrée QML

Pourquoi ?

  • Intuitif : Les développeurs retrouvent facilement les fichiers liés à une feature.
  • Cohérent avec le métier : La structure reflète l’organisation de l’application.

Organisation par catégorie de composant

Idéale si vous utilisez un design system ou une librairie de composants.

project_name/
└── qml/
    ├── token/                          # Style de l'application et variables de base
    │   ├── Fonts.qml
    │   ├── Icons.qml
    ├── atom/                           # Composants simples (atomiques)
    │   ├── Button.qml
    │   ├── Divider.qml
    │   ├── SpecificButton.qml
    ├── molecule/                       # Composants complexes (composés d'atomes)
    │   ├── ProfilPanel.qml
    │   └── DealCard.qml
    ├── organism/                       # Panneaux entiers (composés de mollecules / atomes)
    │   ├── Header.qml
    │   └── Menu.qml
    └── page/                           # Pages entières
        └── Dashboard.qml

Pourquoi ?

  • Réutilisabilité : Les composants sont découplés et faciles à maintenir.
  • Compatibilité avec les design systems (comme Atomic Design).

Mon conseil :

  • Mixez les deux approches si nécessaire (ex. : organisation par catégorie pour les composants, fonctionnelle pour les fonctions métier).
  • Soyez cohérent : Une fois la structure choisie, appliquez-la partout.
  • Et surtout, adaptez l’approche à vos besoins !

Lisibilité

La lisibilité, c’est comme l’orthographe : tout le monde sait que c’est important, mais peu de gens en font une priorité. Pourtant, un code lisible, c’est un code maintenable, collaboratif et moins stressant.

Je ne vais pas vous ressasser les bonnes pratiques que vous connaissez déjà par cœur :

  • “Donnez des noms clairs à vos variables, classes et composants.” (Oui, userProfileAvatar > img1.)
  • “Dans un commentaire, expliquez le pourquoi, pas le comment.” (Le code est là pour ça.)

Non, concentrons-nous maintenant sur ce qui est propre au QML.


Ordre de déclaration des éléments en QML

En QML, l’ordre des déclarations compte. Pas pour le compilateur, mais pour vos collègues (et votre futur vous). Voici l’ordre que je recommande, testé et approuvé sur des projets réels :

  1. id (toujours en premier)
  2. Propriétés (déclarations property)
  3. Propriétés héritées (ex. : anchors, width, height)
  4. Signaux
  5. Fonctions JavaScript
  6. Eléments privées (déclarations qu’on souhaite garder en interne)
  7. Objets enfants (composants imbriqués)
  8. États (states)
  9. Transitions (transitions)

Mon template QML

A copier-coller sans modération

import QtQuick     // Imports Qt
import my.project  // Plugin ou module interne
import "my/folder" // Import relatif

Item {
    id: root  // Toujours en premier !

    //========================================================================
    // Propriétés
    //========================================================================
    property bool isActive      // false par défaut, donc pas de ré-assignation
    property string userName    // "" par défaut, donc pas de ré-assignation

    //========================================================================
    // Propriétés héritées
    //========================================================================
    anchors.fill: parent
    width: 200
    height: childrenRect.height

    //========================================================================
    // Signaux
    //========================================================================
    signal userUpdated(string newName)

    //========================================================================
    // Fonctions
    //========================================================================
    function calculateWidth() {
        return root.width * 0.8
    }

    //========================================================================
    // Eléments privées
    //========================================================================
    
    QtObject {
        id: internal

        property string userPwd
    }

    //========================================================================
    // Objets enfants
    //========================================================================
    Rectangle {
        color: "lightgray"
        anchors.fill: parent
    }

    //========================================================================
    // États
    //========================================================================
    states: [
        State {
            name: "active"
            when: root.isActive
            PropertyChanges { target: root; color: "blue" }
        }
    ]

    //========================================================================
    // Transitions
    //========================================================================
    transitions: [
        Transition {
            from: "*"
            to: "active"
            ColorAnimation { duration: 200 }
        }
    ]
}

Pourquoi cet ordre ?

  • Logique : On part du plus global (le composant) vers le plus spécifique (les enfants, états, transitions).
  • Orienté utilisateur : On présente en premier l’API publique du composant avec ces properties
  • Lisible : Les sections sont visuellement séparées par des commentaires.
  • Maintenable : Ajouter ou modifier une section ne casse pas la lecture.

Petit bonus :

  • Utilisez des commentaires visuels (// ====) pour délimiter les sections.
  • Alignez les propriétés pour un rendu propre (ex. : property bool isActive: false).

Maintenabilité

La maintenabilité, c’est l’art de ne pas se tirer une balle dans le pied dans 6 mois. Un code maintenable, c’est un code où :

  • Chaque composant a une seule responsabilité.
  • La logique métier est découplée de l’interface.
  • Les dépendances sont minimales et claires.

Une entité, une responsabilité

Un principe fondamental : chaque élément de votre code — qu’il s’agisse d’un composant QML, d’une classe C++, ou de toute autre entité — doit avoir une seule responsabilité. Cela signifie qu’il doit répondre à un besoin précis et unique.

Pourquoi est-ce si important ? Combien de fois avons-nous vu des composants ou des classes surchargés, où s’entassent des fonctions aux responsabilités multiples ? Ce mélange de responsabilités rend le code difficile à maintenir, à tester et à faire évoluer. Un composant QML doit faire une chose, et une seule.

La séparation des responsabilités : UI vs. Logique métier

Il est crucial de souligner que la logique métier ne doit jamais être gérée par le QML. Le rôle du QML est de présenter l’interface utilisateur et de réagir aux interactions utilisateur, mais il doit rester découplé de la logique métier.

Cette séparation est d’ailleurs un pilier des architectures logicielles modernes, comme :

  • Clean Architecture : Dans ce modèle, l’interface utilisateur (UI) est considérée comme une entité externe, au même titre qu’une base de données. Elle est isolée du cœur métier pour garantir une indépendance maximale.
  • QML Core UI Architecture : Une approche qui promeut un découplage strict entre la couche UI et la logique métier.

Exemple visuel : Dans le schéma ci-dessous, issu de la Clean Architecture, la couche UI (IHM) est clairement positionnée comme un plugin externe, distinct du domaine métier.

Clean Architecture


Pourquoi adopter cette approche ?

  • Maintenabilité : Un code découplé est plus facile à corriger et à faire évoluer.
  • Testabilité : La logique métier peut être testée indépendamment de l’UI…et inversement.
  • Réutilisabilité : Les composants UI ou métier peuvent être réutilisés dans d’autres contextes.

En résumé : Respectez le principe “une entité, une responsabilité”, et gardez votre QML exclusivement dédié à l’interface. La logique métier a sa place ailleurs.

Au final, la règle d’or (qui s’adapte d’ailleurs à la plupart des langages) :

“Un composant QML = une responsabilité. Point.”

Exemple concret :

  • À éviter :
    // ProfilePage.qml (500 lignes)
    Item {
        // 1. Affiche le profil utilisateur
        // 2. Gère la connexion API
        // 3. Valide les champs du formulaire
        // 4. Envoie des notifications
        // ...
    }
    
  • À faire :
    // ProfilePage.qml (50 lignes)
    Item {
        ProfileHeader { user: userManager.currentUser() }
        ProfileForm { onSubmit: userManager.save() }
        NotificationPopup { }
    }
    
    La logique métier ? Elle est dans userManager (C++).

Pourquoi ça marche ?

  • Découplage : Chaque composant est autonome et testable.
  • Réutilisabilité : ProfileHeader peut être utilisé ailleurs.
  • Maintenabilité : Modifier la logique métier ne casse pas l’UI.

L’interface de communication

Un bon composant, c’est comme une boîte noire : on ne voit que ce qui est nécessaire pour l’utiliser, le reste reste caché. Mais pour y parvenir, il est indispensable de réfléchir aux entrées/sorties, à ce qui doit être public et ce qui doit rester privé avant même d’écrire une ligne de code.

La clé ? Adoptez une approche centrée sur l’utilisateur final de votre composant (que ce soit vous dans 6 mois ou un collègue). Demandez-vous :

  • Comment aimerais-je utiliser ce composant idéalement ?
  • Quelles informations dois-je lui fournir pour qu’il fonctionne ?
  • Que doit-il me retourner ou m’indiquer ?

Mon petit rituel (inspiré du TDD) : Avant de coder, j’écris souvent du pseudo-code en imaginant mon composant déjà terminé. Par exemple :

// Pseudo-code : Comment j''aimerais utiliser mon composant ?
UserProfile {
    user: backend.currentUser  // Entrée claire
    editable: isAdmin         // Option simple

    onSaved: showSuccess()    // Sortie explicite
}

Ensuite, je travaille à faire en sorte que ce pseudo-code devienne réalité. Cette méthode me force à :

  • Penser en termes de besoins réels (et pas juste “faire en sorte que ça marche”).
  • Éviter les composants fourre-tout qui font tout et n’importe quoi.
  • Rendre l’API intuitive dès la conception.

Résultat ? Un composant plus simple à utiliser, à tester et à maintenir – et surtout, qui répond vraiment aux besoins de ceux qui vont s’en servir.


Exemple concret (repris et amélioré) :

// UserProfile.qml - API conçue "à l'envers"
Item {
    id: root

    // ===== API PUBLIQUE (ce que l'utilisateur regarde) =====

    //========================================================================
    // Propriétés
    //========================================================================
    required property User user       // Données utilisateur (entrée)
    required property bool editable   // Mode édition (entrée)

    //========================================================================
    // Signals
    //========================================================================
    signal saved()         // Événement de sauvegarde (sortie)
    signal error(string)   // Gestion des erreurs (sortie)

    
    // ===== FIN API PUBLIQUE =====

    // ===== IMPLÉMENTATION (intérieur de la boîte noire) =====

    //========================================================================
    // Eléments privées
    //========================================================================
    QtObject {
        id: internal

        function validate() 
        { 
            /* Logique interne */ 
        }
    }

    //========================================================================
    // Objets enfants
    //========================================================================
    Column {
        TextField { text: root.user.name; enabled: root.editable }
        Button {
            text: "Enregistrer"
            onClicked: {
                if (internal.validate()) 
                {
                    root.saved(root.user?.name ?? "");
                }
                else 
                {
                    root.error("Champs invalides");
                }
            }
        }
    }
}

Pourquoi cette approche change tout ?

  • Clarté : L’API est conçue pour être utilisée, pas juste pour “fonctionner”.
  • Pérennité : Moins de refactoring douloureux plus tard.
  • Collaboration : Vos collègues (ou vous-même dans le futur) vous remercieront.

En résumé :

  1. Découplez la logique métier de l’UI.
  2. Imaginez l’usage idéal avant de coder (pseudo-code > implémentation).
  3. Séparez clairement vos API public/privé.
  4. Testez l’API en conditions réelles dès le début.

Conclusion

Les bonnes pratiques, ce n’est pas du luxe ni de la théorie de puriste : c’est ce qui fait qu’un projet reste vivant et agréable à maintenir dans la durée.
En QML comme ailleurs, un code clair, découplé et lisible, c’est moins de bugs, plus de sérénité et une équipe qui avance sans se faire peur !

Si vous êtes arrivé jusque-là, vous avez déjà fait un grand pas : la prise de conscience.
La suite ? C’est l’application au quotidien — et c’est souvent là que le bât blesse. Parce qu’entre les deadlines, la dette technique et la réalité d’un projet, il est facile de retomber dans les travers que j’ai décrits.

💡 Pour approfondir le sujet, je vous recommande vivement ce dépôt GitHub qui rassemble de nombreux conseils et bonnes pratiques en QML. Vous y trouverez aussi bien des règles de style (nommage, indentation, organisation des fichiers) que des recommandations de conception (structuration des vues, utilisation pertinente des propriétés, optimisation des performances, etc.).

👉 QML Coding Guide – Furkanzmc


Et si on en parlait pour votre projet ?

En tant que développeur et architecte freelance spécialisé en C++ / Qt / QML, j’accompagne mes clients pour :

  • concevoir des architectures solides et évolutives,
  • mettre en place des bonnes pratiques adaptées à leur contexte,
  • former les équipes aux spécificités de QtQuick et QML,
  • et livrer des applications robustes, maintenables et répondent véritablement aux besoins des utilisateur finaux.

👉 Si vous voulez appliquer ces principes à votre projet (ou simplement éviter qu’il parte en code spaghetti 😉), je serai ravi d’échanger avec vous.

📩 Me contacter
Étiquettes :
Partager :
comments powered by Disqus