Publié le
-

Le Guide Ultime pour Créer des Fonctions Propres en Suivant les Meilleures Pratiques de Clean Code 🚀

7 min lecture - 1240 mots
Auteurs
  • avatar
    Nom
    Cédric RIBALTA
    Twitter
post image

Le développement de fonctions propres est un pilier fondamental pour tout développeur qui souhaite améliorer la qualité de son code. Dans cet article, nous allons explorer les 10 bonnes pratiques tirées de Clean Code de Robert C. Martin (alias Uncle Bob) 💻, qui peuvent aider à créer des fonctions efficaces et maintenables. Que tu sois un développeur débutant ou expérimenté, ces conseils te permettront d'écrire un code plus propre, plus lisible, et plus facile à maintenir à long terme 🛠️.

Pourquoi ces pratiques sont-elles importantes ?

Avant de plonger dans les 10 bonnes pratiques, il est essentiel de comprendre pourquoi elles comptent. Des fonctions bien écrites améliorent la lisibilité et la maintenabilité du code. Cela permet à d'autres développeurs (ou à toi-même dans le futur) de comprendre rapidement ce que fait une fonction, de la tester et de la modifier sans avoir à réécrire des portions entières de code.

Ces pratiques ne sont pas seulement des règles théoriques ; elles répondent à des besoins pratiques que tout développeur rencontre au quotidien : réduire la complexité, minimiser les bugs 🐛, et faciliter le travail collaboratif 🤝. Maintenant que tu comprends mieux leur importance, voici les 10 étapes à suivre.


1. Plus c'est court, plus c'est bon ✂️

La première règle d'une fonction propre est sa taille. Selon Uncle Bob, une fonction doit être courte. Idéalement, elle ne devrait pas dépasser 20 lignes de code. Pourquoi ? Parce qu'une fonction courte est plus facile à lire et à comprendre. Cela permet également de limiter le nombre de responsabilités d'une fonction (ce que nous verrons plus loin).

Prenons un exemple simple en JavaScript :

function processOrder(order) {
  if (order.isPaid) {
    updateStock(order.items)
    sendEmailConfirmation(order)
    generateInvoice(order)
  } else {
    notifyUser(order)
  }
}

Voici une version refactorisée, avec des fonctions plus courtes et plus spéficiques :

function processOrder(order) {
  if (order.isPaid) {
    handlePaidOrder(order)
  } else {
    notifyUser(order)
  }
}

function handlePaidOrder(order) {
  updateStock(order.items)
  sendEmailConfirmation(order)
  generateInvoice(order)
}

2. Mollo sur la touche "Tab" ⌨️

Un bon indicateur de la complexité d'une fonction est son niveau d'indentation. Plus une fonction est imbriquée, plus elle devient complexe à comprendre et à maintenir. Uncle Bob recommande de ne pas dépasser 2 niveaux d'indentation. Si tu en as besoin de plus, c'est probablement le signe que ta fonction doit être scindée.

Exmple d'une fonction trop imbriquée :

function validateOrder(order) {
  if (order.isPaid) {
    if (order.isInStock) {
      processOrder(order)
    } else {
      console.log('Item not in stock')
    }
  } else {
    console.log('Order not paid')
  }
}

Voici comment améliorer cela en extrayant des sous-fonctions :

function validateOrder(order) {
  if (!order.isPaid) {
    return handleUnpaidOrder()
  }
  if (!order.isInStock) {
    return handleOutOfStock()
  }
  processOrder(order)
}

La fonction est maintenant plus simple et plus lisible.


3. Monotâche 🎯

Une fonction doit faire une seule chose et le faire bien ✅. Si elle fait plus d'une chose, elle est probablement trop complexe. L'une des façons de vérifier cela est de lire le nom de la fonction : si tu ne peux pas le formuler sans utiliser le mot "et", la fonction fait trop de choses.

Voici un exemple de fonction qui fait trop de choses :

function saveAndNotifyUser(user) {
  saveToDatabase(user)
  sendEmail(user.email)
}

Il vaut mieux la découper ainsi :

function saveUser(user) {
  saveToDatabase(user)
}

function notifyUser(user) {
  sendEmail(user.email)
}

En séparant les responsabilités, tu rends le code plus modulaire et plus facile à tester.


4. Une fonction c'est comme un ascenseur 🛗

Une fonction doit faire monter ou descendre d'un niveau d'abstraction. Cela signifie que tu ne devrais pas mélanger différents niveaux d'abstraction dans une même fonction. Par exemple, si une fonction manipule à la fois des détails bas-niveau et des concepts haut-niveau, il est temps de la scinder 🚀.

Un mauvais exemple serait :

function manageOrder(order) {
  if (order.isPaid) {
    console.log('Processing order')
    updateStock(order.items)
    sendEmailConfirmation(order)
  }
}

Voici une meilleure façon de structurer cela :

function manageOrder(order) {
  if (order.isPaid) {
    handlePaidOrder(order)
  }
}

function handlePaidOrder(order) {
  updateStock(order.items)
  sendEmailConfirmation(order)
}

Chaque fonction reste à un seul niveau d'abstraction.


5. Un joli petit nom 🏷️

Le nom d'une fonction doit immédiatement indiquer ce qu'elle fait. Si tu dois ajouter un commentaire pour expliquer ce que fait ta fonction, c'est que son nom n'est pas assez explicite. Une règle simple : si le nom de ta fonction est clair, son code le sera probablement aussi. Un bon nom de fonction améliore instantanément la lisibilité de ton code.

Par exemple, évite les noms vagues comme :

function doStuff() {
  // Code ici
}

Préfère des noms explicites :

function updateInventory(items) {
  // Code pour mettre à jour l'inventaire
}

Un bon nom de fonction améliore instantanément la lisibilité de ton code.


6. Le nombre de paramètres 🔢

Le nombre idéal de paramètres pour une fonction est 0. Après cela, un seul paramètre est acceptable, mais évite autant que possible les fonctions avec plus de 3 paramètres. Chaque paramètre supplémentaire augmente la complexité et rend la fonction plus difficile à comprendre. Regrouper plusieurs paramètres dans un objet peut parfois apporter de la clarté.

Voici un exemple de fonction avec trop de paramètres :

function createUser(firstName, lastName, email, password, age) {
  // Code ici
}

Il est souvent préférable de regrouper ces paramètres dans un objet :

function createUser(userDetails) {
  // Code ici
}

Cela rend la fonction plus simple et plus flexible.


7. Les flags 🚩

Un flag est une variable booléenne qui modifie le comportement d'une fonction. Son utilisation suggère généralement que la fonction fait trop de choses. Au lieu d'utiliser un flag, il vaut mieux scinder la fonction en deux, chacune avec une responsabilité claire et unique.

Exemple à éviter :

function processOrder(order, isPaid) {
  if (isPaid) {
    updateStock(order.items)
  } else {
    notifyUser(order)
  }
}

Voici une meilleure approche :

function processPaidOrder(order) {
  updateStock(order.items)
}

function processUnpaidOrder(order) {
  notifyUser(order)
}

En séparant les responsabilités, le code devient plus facile à comprendre et à maintenir.


8. Les effets secondaires ⚡

Une fonction ne devrait pas produire d'effets secondaires. Cela signifie qu'elle ne doit pas modifier des variables globales ou des paramètres passés. Un effet secondaire introduit un couplage entre la fonction et son environnement, ce qui peut créer des bugs difficiles à identifier.

Par exemple, éviter ceci :

function addItemToCart(item) {
  cart.push(item)
}

Préférez ceci :

function addItemToCart(cart, item) {
  return [...cart, item]
}

Cela rend la fonction plus prévisible et plus facile à tester.


9. Command Query Separation (CQS) 🔄

Le principe de Command Query Separation stipule qu'une fonction doit soit modifier l'état d'un objet (command), soit renvoyer des informations sur cet état (query), mais jamais les deux à la fois. Cela permet de clarifier les rôles des fonctions et de rendre le code plus lisible 👓.

Un mauvais exemple serait :

function updateAndFetchUser(user) {
  user.updateProfile()
  return user
}

Une meilleure approche consiste à séparer les deux :

function updateUser(user) {
  user.updateProfile()
}

function getUserInfo(user) {
  return user
}

10. Préférence pour les exceptions ❗

Il est préférable d'utiliser des exceptions plutôt que des codes d'erreurs pour gérer les erreurs. Les exceptions permettent de séparer le code de gestion des erreurs du code métier, rendant ce dernier plus lisible. Les exceptions facilitent également la gestion des erreurs en les propagant aux couches supérieures sans alourdir le code métier.

Exemple de mauvais usage de code d'erreur :

function saveUser(user) {
  if (!user.isValid()) {
    return -1
  }
  // Sauvegarde du user
}

Préfère l'utilisation des exceptions :

function saveUser(user) {
  if (!user.isValid()) {
    throw new Error('Invalid user')
  }
  // Sauvegarde du user
}

Les exceptions permettent de propager les erreurs aux couches supérieures et de mieux gérer le flux d'exécution.


Conclusion

Appliquer ces 10 pratiques de Clean Code te permettra de coder des fonctions plus propres, lisibles et maintenables 🧹. Bien entendu, il existe des exceptions, mais il est essentiel de connaître ces règles pour les appliquer intelligemment. En résumé, garde tes fonctions courtes, claires, et évite les effets secondaires pour améliorer ton code.

Articles connexes :