Leçon 14 - Bonnes Pratiques de Programmation

P3 - Bonnes pratiques

Apprenez les bonnes pratiques pour écrire du code propre, maintenable et professionnel.

Quand créer une fonction ?

Une fonction bien conçue améliore la lisibilité et la maintenabilité de votre code :

Règle 1 : Éliminer la duplication

✅ Bon : Créez une fonction dès que vous répétez au moins 2 lignes de code

C#
// ✅ Bon : Fonction réutilisable static void AfficherPersonne(string nom, int age, string ville) { Console.WriteLine($"=== Profil ==="); Console.WriteLine($"Nom: {nom}"); Console.WriteLine($"Âge: {age} ans"); Console.WriteLine($"Ville: {ville}"); Console.WriteLine(); } // Utilisation AfficherPersonne("Alice", 25, "Paris"); AfficherPersonne("Bob", 30, "Lyon");

❌ Mauvais : Code dupliqué

C#
// ❌ Duplication de code Console.WriteLine($"=== Profil ==="); Console.WriteLine($"Nom: Alice"); Console.WriteLine($"Âge: 25 ans"); Console.WriteLine($"Ville: Paris"); Console.WriteLine(); Console.WriteLine($"=== Profil ==="); Console.WriteLine($"Nom: Bob"); Console.WriteLine($"Âge: 30 ans"); Console.WriteLine($"Ville: Lyon"); Console.WriteLine();

Règle 2 : Limiter la longueur

Si votre fonction dépasse 50 lignes, divisez-la en sous-fonctions :

C#
// ✅ Bon : Fonctions courtes et spécialisées static void TraiterCommande(Commande commande) { if (!ValiderCommande(commande)) return; CalculerTotaux(commande); AppliquerReductions(commande); EnvoyerConfirmation(commande); } static bool ValiderCommande(Commande commande) { return commande != null && commande.Articles.Count > 0 && commande.Client != null; } static void CalculerTotaux(Commande commande) { commande.SousTotal = commande.Articles.Sum(a => a.Prix * a.Quantite); commande.Taxes = commande.SousTotal * 0.20m; commande.Total = commande.SousTotal + commande.Taxes; }

Nommage des fonctions et variables

Conventions de nommage C#

✅ Conventions C# correctes :

C#
// ✅ Bon nommage public class GestionnaireUtilisateurs { private readonly string _connectionString; private static readonly int MaxTentativesConnexion = 3; public bool AuthentifierUtilisateur(string nomUtilisateur, string motDePasse) { int nombreTentatives = 0; bool authentificationReussie = false; // Logique d'authentification return authentificationReussie; } private void JournalerTentativeConnexion(string utilisateur, bool succes) { // Logique de journalisation } }

Noms expressifs

✅ Noms clairs et expressifs

C#
// ✅ Bon : Noms explicites bool estUtilisateurValide = ValiderDonneesUtilisateur(utilisateur); decimal montantTotalAvecTaxes = CalculerMontantFinal(montantBase, tauxTaxe); List<Commande> commandesEnAttente = ObtenirCommandesParStatut(StatutCommande.EnAttente); // Fonctions avec noms d'action clairs void EnvoyerEmailConfirmation(string email, string numeroCommande); bool PeutAccederAuModule(Utilisateur utilisateur, Module module); decimal ConvertirEurosEnDollars(decimal montantEuros, decimal tauxChange);

❌ Noms vagues ou abrégés

C#
// ❌ Mauvais : Noms peu clairs bool b = Val(usr); decimal m = Calc(mb, t); List<Commande> cmd = Get(st); void Send(string e, string n); bool Check(User u, Mod m); decimal Conv(decimal e, decimal t);

Organisation du code

Fonctions d'interface vs utilitaires

C#
public class ServiceCommande { // ✅ Fonctions d'interface (publiques) - API externe public bool CreerCommande(DonneesCommande donnees) { if (!ValiderDonneesCommande(donnees)) // Fonction utilitaire return false; var commande = ConstruireCommande(donnees); // Fonction utilitaire return SauvegarderCommande(commande); // Fonction utilitaire } public List<Commande> ObtenirCommandesClient(int clientId) { return RecupererCommandesDepuisBD(clientId); // Fonction utilitaire } // ✅ Fonctions utilitaires (privées) - Logique interne private bool ValiderDonneesCommande(DonneesCommande donnees) { return donnees != null && donnees.Articles.Any() && donnees.ClientId > 0; } private Commande ConstruireCommande(DonneesCommande donnees) { return new Commande { ClientId = donnees.ClientId, Articles = donnees.Articles, DateCreation = DateTime.Now }; } private bool SauvegarderCommande(Commande commande) { // Logique de sauvegarde return true; } }

Commentaires et documentation

Commentaires XML pour la documentation

C#
/// <summary> /// Calcule le montant total d'une commande incluant les taxes /// </summary> /// <param name="montantBase">Le montant avant taxes</param> /// <param name="tauxTaxe">Le taux de taxe (ex: 0.20 pour 20%)</param> /// <returns>Le montant total avec taxes</returns> /// <exception cref="ArgumentException"> /// Lancée si le montant est négatif ou le taux invalide /// </exception> public static decimal CalculerMontantAvecTaxes(decimal montantBase, decimal tauxTaxe) { if (montantBase < 0) throw new ArgumentException("Le montant ne peut pas être négatif", nameof(montantBase)); if (tauxTaxe < 0 || tauxTaxe > 1) throw new ArgumentException("Le taux de taxe doit être entre 0 et 1", nameof(tauxTaxe)); return montantBase * (1 + tauxTaxe); }

Commentaires explicatifs

✅ Bons commentaires : Expliquent le "pourquoi"

C#
// Utilise un délai exponentiel pour éviter de surcharger l'API int delaiMs = (int)Math.Pow(2, numeroTentative) * 1000; await Task.Delay(delaiMs); // Cache le résultat pendant 5 minutes pour améliorer les performances _cache.Set(cle, donnees, TimeSpan.FromMinutes(5)); // Validation selon la norme ISO 8601 if (!DateTime.TryParseExact(dateString, "yyyy-MM-dd", null, DateTimeStyles.None, out DateTime date)) { throw new FormatException("Format de date invalide. Utilisez yyyy-MM-dd"); }

❌ Mauvais commentaires : Répètent le code

C#
// ❌ Commentaires inutiles int age = 25; // Assigne 25 à age if (age > 18) // Si age est supérieur à 18 { Console.WriteLine("Majeur"); // Affiche "Majeur" }

Gestion des erreurs et validation

Validation précoce des paramètres

C#
public static decimal CalculerPourcentage(decimal valeur, decimal total) { // ✅ Validation immédiate des paramètres if (total == 0) throw new ArgumentException("Le total ne peut pas être zéro", nameof(total)); if (valeur < 0 || total < 0) throw new ArgumentException("Les valeurs ne peuvent pas être négatives"); return (valeur / total) * 100; } public static void TraiterFichier(string cheminFichier) { // ✅ Vérifications préalables if (string.IsNullOrWhiteSpace(cheminFichier)) throw new ArgumentException("Le chemin du fichier est requis", nameof(cheminFichier)); if (!File.Exists(cheminFichier)) throw new FileNotFoundException($"Le fichier {cheminFichier} n'existe pas"); // Traitement du fichier... }

Style d'écriture cohérent

Formatage et indentation

✅ Code bien formaté

C#
// ✅ Bon : Formatage cohérent et lisible public class CalculatriceFinanciere { private const decimal TauxTVAStandard = 0.20m; public static decimal CalculerMontantTTC(decimal montantHT, decimal tauxTVA = TauxTVAStandard) { if (montantHT < 0) throw new ArgumentException("Le montant HT doit être positif"); decimal montantTVA = montantHT * tauxTVA; decimal montantTTC = montantHT + montantTVA; return Math.Round(montantTTC, 2); } public static (decimal ht, decimal tva, decimal ttc) DecomposerMontantTTC(decimal montantTTC, decimal tauxTVA = TauxTVAStandard) { decimal montantHT = montantTTC / (1 + tauxTVA); decimal montantTVA = montantTTC - montantHT; return ( ht: Math.Round(montantHT, 2), tva: Math.Round(montantTVA, 2), ttc: montantTTC ); } }

Principes SOLID simplifiés

Single Responsibility (Responsabilité unique)

C#
// ✅ Bon : Une classe, une responsabilité public class ValidateurEmail { public bool EstValide(string email) { return !string.IsNullOrWhiteSpace(email) && email.Contains("@") && email.Contains("."); } } public class EnvoyeurEmail { public void Envoyer(string destinataire, string sujet, string message) { // Logique d'envoi d'email } } public class JournaliseurEmail { public void JournalerEnvoi(string destinataire, DateTime dateEnvoi, bool succes) { // Logique de journalisation } }

Récapitulatif des bonnes pratiques

Exercice pratique

Créez un système de gestion de comptes bancaires en appliquant toutes les bonnes pratiques :