Numérique et Sciences Informatiques1ère

Critères de qualité
Exercices corrigés

Maîtrisez les critères de qualité : lisibilité, efficacité, robustesse, maintenabilité, documentation grâce à ces 5 exercices détaillés.

Concepts & Exercices
Qualité = Lisibilité + Efficacité + Robustesse + Maintenabilité
Équation de la qualité
Lisibilité
nommage_clair + indentation
Code compréhensible
Efficacité
complexité_algo
Performance optimale
Robustesse
gestion_erreurs
Gestion des erreurs
🎯
Définition : Ensemble des caractéristiques qui définissent la qualité d'un programme.
📊
Objectif : Produire des logiciels fiables, performants et maintenables.
🔍
Évaluation : Processus systématique d'analyse des critères de qualité.
Amélioration : Identification des axes de progrès pour le code.
Exercice 1
Évaluer la lisibilité d'un code avec des noms de variables clairs
Exercice 2
Analyser l'efficacité d'un algorithme en termes de complexité
Exercice 3
Vérifier la robustesse face aux erreurs et cas limites
Exercice 4
Évaluer la maintenabilité et la modularité du code
Exercice 5
Analyser la documentation et les commentaires du code
Corrigé : Exercices 1 à 3
1 Évaluation de lisibilité
Définition :

Lisibilité : Facilité avec laquelle un programme peut être lu et compris par un humain.

Méthode d'évaluation :
  1. Examiner les noms de variables et fonctions
  2. Vérifier l'indentation et la structure
  3. Analyser la clarté des commentaires
  4. Identifier la logique du programme
  5. Noter la facilité de compréhension
# Code peu lisible def f(x): s=0 for i in range(len(x)): if x[i]>0: s+=x[i]**2 return s # Code lisible def somme_carres_positifs(nombres): """ Calcule la somme des carrés des nombres positifs. Args: nombres (list): Liste de nombres Returns: int: Somme des carrés des nombres positifs """ somme = 0 for nombre in nombres: if nombre > 0: somme += nombre ** 2 return somme # Exemple d'utilisation donnees = [-2, 3, -1, 4, 0, 5] resultat = somme_carres_positifs(donnees) print(f"Somme des carrés positifs: {resultat}")
Étape 1 : Analyser les noms

Les noms de variables et fonctions doivent être descriptifs.

Étape 2 : Vérifier l'indentation

Une indentation correcte améliore la structure visuelle du code.

Étape 3 : Évaluer la clarté

Le code lisible permet de comprendre rapidement la logique.

Lisibilité: Bon nommage + Bonne indentation = Compréhension facilitée
Réponse finale :

Un code lisible utilise des noms descriptifs, une indentation correcte et des commentaires pertinents.

Règles appliquées :

Nommage : Variables et fonctions avec noms descriptifs

Structure : Indentation cohérente et logique

Commentaires : Expliquer les parties complexes

2 Analyse d'efficacité
Définition :

Efficacité : Capacité d'un algorithme à résoudre un problème avec un minimum de ressources.

# Version inefficace - O(n²) def recherche_max_incorrect(tableau): """Recherche du maximum avec complexité quadratique.""" for i in range(len(tableau)): est_maximum = True for j in range(len(tableau)): if tableau[j] > tableau[i]: est_maximum = False break if est_maximum: return tableau[i] # Version efficace - O(n) def recherche_max_correct(tableau): """Recherche du maximum avec complexité linéaire.""" if not tableau: return None maximum = tableau[0] for element in tableau[1:]: if element > maximum: maximum = element return maximum # Analyse de performance import time import random tailles = [1000, 5000, 10000] for taille in tailles: donnees = [random.randint(1, 1000) for _ in range(taille)] # Version inefficace debut = time.time() resultat1 = recherche_max_incorrect(donnees) temps1 = time.time() - debut # Version efficace debut = time.time() resultat2 = recherche_max_correct(donnees) temps2 = time.time() - debut print(f"Taille: {taille}") print(f" Version O(n²): {temps1:.6f}s") print(f" Version O(n): {temps2:.6f}s") print(f" Gain: {temps1/temps2:.2f}x")
Étape 1 : Analyser la complexité

Identifier la complexité algorithmique en fonction de la taille des données.

Étape 2 : Comparer les versions

Tester différentes implémentations pour la même fonctionnalité.

Étape 3 : Mesurer les performances

Utiliser des mesures concrètes pour évaluer l'efficacité.

Efficacité: O(n) vs O(n²) → Gain exponentiel avec la taille
Réponse finale :

L'efficacité d'un algorithme dépend de sa complexité et de sa performance en pratique.

Règles appliquées :

Complexité : Analyser la complexité théorique

Optimisation : Chercher des algorithmes plus efficaces

Tests : Mesurer les performances avec des données réelles

3 Vérification de robustesse
Définition :

Robustesse : Capacité d'un programme à gérer correctement les erreurs et les cas limites.

# Version non robuste def division_simple(a, b): return a / b # Version robuste def division_securisee(a, b): """ Effectue une division avec gestion des erreurs. Args: a (float): Numérateur b (float): Dénominateur Returns: float: Résultat de la division Raises: ZeroDivisionError: Si b est zéro TypeError: Si a ou b ne sont pas des nombres """ if not isinstance(a, (int, float)) or not isinstance(b, (int, float)): raise TypeError("Les arguments doivent être des nombres") if b == 0: raise ZeroDivisionError("Division par zéro impossible") return a / b # Tests de robustesse cas_tests = [ (10, 2), # Cas normal (10, 0), # Division par zéro (10, "2"), # Type incorrect ("10", 2), # Type incorrect (-5, 2), # Cas négatif (0, 5), # Zéro divisé ] for numerateur, denominateur in cas_tests: try: resultat = division_securisee(numerateur, denominateur) print(f"{numerateur} / {denominateur} = {resultat}") except Exception as e: print(f"Erreur avec {numerateur} / {denominateur}: {type(e).__name__} - {e}")
Étape 1 : Identifier les cas limites

Rechercher les situations qui pourraient causer des erreurs.

Étape 2 : Ajouter des validations

Vérifier les types et les valeurs des paramètres.

Étape 3 : Gérer les exceptions

Lever des erreurs appropriées avec des messages clairs.

Robustesse: Gestion des erreurs + Validation des entrées
Réponse finale :

Un code robuste gère les erreurs et les cas limites sans se planter.

Règles appliquées :

Validation : Vérifier les types et les valeurs

Exceptions : Lever des erreurs appropriées

Tests : Vérifier les cas limites

Corrigé : Exercices 4 à 5
4 Évaluation de maintenabilité
Définition :

Maintenabilité : Facilité de modification, correction et évolution du code.

# Code non maintenable - tout dans une fonction def gestion_comptes_utilisateurs(operations): """Fonction complexe qui fait trop de choses.""" # Chargement des données with open("utilisateurs.txt", "r") as f: utilisateurs = f.read().splitlines() # Traitement des opérations for operation in operations: if operation.startswith("ajouter"): nom = operation.split()[1] utilisateurs.append(nom) elif operation.startswith("supprimer"): nom = operation.split()[1] utilisateurs.remove(nom) elif operation.startswith("rechercher"): nom = operation.split()[1] if nom in utilisateurs: print(f"{nom} trouvé") # Sauvegarde des données with open("utilisateurs.txt", "w") as f: f.write("\n".join(utilisateurs)) # Code maintenable - fonctions modulaires def charger_utilisateurs(fichier): """Charge la liste des utilisateurs depuis un fichier.""" with open(fichier, "r") as f: return f.read().splitlines() def sauvegarder_utilisateurs(fichier, utilisateurs): """Sauvegarde la liste des utilisateurs dans un fichier.""" with open(fichier, "w") as f: f.write("\n".join(utilisateurs)) def ajouter_utilisateur(utilisateurs, nom): """Ajoute un utilisateur à la liste.""" utilisateurs.append(nom) def supprimer_utilisateur(utilisateurs, nom): """Supprime un utilisateur de la liste.""" if nom in utilisateurs: utilisateurs.remove(nom) def rechercher_utilisateur(utilisateurs, nom): """Recherche un utilisateur dans la liste.""" return nom in utilisateurs def gerer_operations(operations, fichier="utilisateurs.txt"): """Gère les opérations sur les utilisateurs.""" utilisateurs = charger_utilisateurs(fichier) for operation in operations: if operation.startswith("ajouter"): nom = operation.split()[1] ajouter_utilisateur(utilisateurs, nom) elif operation.startswith("supprimer"): nom = operation.split()[1] supprimer_utilisateur(utilisateurs, nom) elif operation.startswith("rechercher"): nom = operation.split()[1] if rechercher_utilisateur(utilisateurs, nom): print(f"{nom} trouvé") sauvegarder_utilisateurs(fichier, utilisateurs)
Étape 1 : Identifier la modularité

Chaque fonction doit avoir une responsabilité unique.

Étape 2 : Vérifier la réutilisabilité

Les fonctions doivent pouvoir être réutilisées dans d'autres contextes.

Étape 3 : Évaluer la facilité de modification

Changer une fonctionnalité ne devrait pas affecter d'autres parties.

Maintenabilité: Fonctions modulaires + Responsabilités uniques
Réponse finale :

Un code maintenable est divisé en fonctions modulaires avec des responsabilités claires.

Règles appliquées :

Modularité : Diviser le code en fonctions spécialisées

Séparation : Chaque fonction a une seule responsabilité

Réutilisabilité : Fonctions indépendantes et réutilisables

5 Analyse de documentation
Définition :

Documentation : Ensemble des textes explicatifs dans ou à côté du code.

def tri_fusion(tableau): """ Trie un tableau en utilisant l'algorithme du tri fusion (merge sort). Le tri fusion est un algorithme de tri stable qui fonctionne selon le principe de diviser pour régner. Il divise récursivement le tableau en deux moitiés jusqu'à ce que chaque sous-tableau contienne un seul élément, puis fusionne les sous-tableaux triés. Args: tableau (list): Liste d'éléments comparables à trier Returns: list: Nouvelle liste triée en ordre croissant Time Complexity: - Best case: O(n log n) - Average case: O(n log n) - Worst case: O(n log n) Space Complexity: O(n) Example: >>> tri_fusion([3, 1, 4, 1, 5]) [1, 1, 3, 4, 5] >>> tri_fusion([]) [] >>> tri_fusion([42]) [42] """ # Cas de base : tableau vide ou avec un élément if len(tableau) <= 1: return tableau # Division du tableau en deux moitiés milieu = len(tableau) // 2 gauche = tableau[:milieu] droite = tableau[milieu:] # Tri récursif des deux moitiés gauche_triee = tri_fusion(gauche) droite_triee = tri_fusion(droite) # Fusion des deux moitiés triées return fusionner(gauche_triee, droite_triee) def fusionner(gauche, droite): """ Fusionne deux listes triées en une seule liste triée. Args: gauche (list): Première liste triée droite (list): Deuxième liste triée Returns: list: Liste fusionnée et triée """ resultat = [] i = j = 0 # Comparer les éléments des deux listes et les fusionner while i < len(gauche) and j < len(droite): if gauche[i] <= droite[j]: resultat.append(gauche[i]) i += 1 else: resultat.append(droite[j]) j += 1 # Ajouter les éléments restants resultat.extend(gauche[i:]) resultat.extend(droite[j:]) return resultat # Documentation du module """ Module de tri fusion (Merge Sort) Ce module implémente l'algorithme de tri fusion, un algorithme de tri stable avec une complexité garantie de O(n log n). Fonctions principales: - tri_fusion(): Trie un tableau - fusionner(): Fusionne deux sous-tableaux triés """
Étape 1 : Vérifier la présence de docstrings

Chaque fonction devrait avoir une docstring décrivant son utilité.

Étape 2 : Analyser le contenu de la documentation

La documentation doit inclure les paramètres, la valeur de retour, et des exemples.

Étape 3 : Évaluer la clarté et la complétude

La documentation doit être compréhensible et complète.

Documentation: Description + Paramètres + Retour + Exemples
Réponse finale :

Une bonne documentation explique clairement ce que fait le code et comment l'utiliser.

Règles appliquées :

Complétude : Inclure description, paramètres, retour

Clarté : Utiliser un langage simple et descriptif

Exemples : Fournir des cas d'utilisation concrets

Cours bien détaillé
\text{Qualité} = \frac{\text{Lisibilité} + \text{Efficacité} + \text{Robustesse} + \text{Maintenabilité}}{4}
Indicateur de qualité
🎯
Objectif : Produire des programmes fiables et durables.
📊
Évaluation : Processus systématique d'analyse des critères.
🔍
Amélioration : Identification des axes de progrès.
Standard : Respect des bonnes pratiques de développement.
💡
Conseil : Écrire le code comme si quelqu'un d'autre devait le lire
🔍
Attention : Tester les cas limites et les erreurs possibles
Astuce : Diviser les grosses fonctions en petites fonctions
📋
Méthode : Suivre les conventions de nommage et de style
Vérification : Documenter chaque fonction avec une docstring
Critères de qualité :
  • Lisibilité : Code clair, noms descriptifs, indentation correcte
  • Efficacité : Algorithmes optimaux, complexité minimale
  • Robustesse : Gestion des erreurs, validation des entrées
  • Maintenabilité : Modularité, réutilisabilité, documentation
  • Portabilité : Compatibilité avec différents environnements
Bonnes pratiques :
  • Nommage : Variables et fonctions avec noms descriptifs
  • Structure : Code bien organisé en modules/logiques
  • Documentation : Docstrings pour chaque fonction
  • Validation : Vérification des paramètres et gestion des erreurs
  • Tests : Vérification des cas limites et erreurs possibles
Règles importantes :
  • Écrire du code lisible avant d'optimiser la performance
  • Valider les entrées pour garantir la robustesse
  • Diviser le code en fonctions modulaires
  • Documenter chaque fonction avec des exemples
  • Tester les cas limites et les erreurs possibles
Critères de qualité Évaluation de projet