Leçon 14 - Bonnes Pratiques de Programmation
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 :
- Classes et méthodes : PascalCase
- Variables et paramètres : camelCase
- Constantes : PascalCase
- Champs privés : _camelCase ou camelCase
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
- Fonctions : Une responsabilité, moins de 50 lignes
- Nommage : Expressif et conforme aux conventions C#
- Commentaires : Expliquer le "pourquoi", pas le "quoi"
- Validation : Vérifier les paramètres immédiatement
- Organisation : API publique vs logique privée
- Style : Cohérent dans tout le projet
Exercice pratique
Créez un système de gestion de comptes bancaires en appliquant toutes les bonnes pratiques :
C#
using System;
public class GestionnaireComptesBancaires
{
private readonly decimal _limiteDecouvertDefaut = -500m;
/// <summary>
/// Effectue un virement entre deux comptes
/// </summary>
public bool EffectuerVirement(string compteSource, string compteDestination, decimal montant)
{
if (!ValiderParametresVirement(compteSource, compteDestination, montant))
return false;
var soldeSource = ObtenirSoldeCompte(compteSource);
if (!PeutDebiter(soldeSource, montant))
{
JournalerOperationEchouee(compteSource, "Solde insuffisant", montant);
return false;
}
return ExecuterVirement(compteSource, compteDestination, montant);
}
private bool ValiderParametresVirement(string source, string destination, decimal montant)
{
return !string.IsNullOrWhiteSpace(source) &&
!string.IsNullOrWhiteSpace(destination) &&
montant > 0 &&
source != destination;
}
private bool PeutDebiter(decimal soldeActuel, decimal montant)
{
return (soldeActuel - montant) >= _limiteDecouvertDefaut;
}
private decimal ObtenirSoldeCompte(string numeroCompte)
{
// Simulation - en réalité, récupération depuis la base de données
return 1000m;
}
private bool ExecuterVirement(string source, string destination, decimal montant)
{
// Logique de virement
JournalerOperationReussie(source, destination, montant);
return true;
}
private void JournalerOperationReussie(string source, string destination, decimal montant)
{
Console.WriteLine($"Virement de {montant:C} de {source} vers {destination} effectué");
}
private void JournalerOperationEchouee(string compte, string raison, decimal montant)
{
Console.WriteLine($"Échec virement {montant:C} sur {compte}: {raison}");
}
}