On sait maintenant stocker des valeurs, les
typer, prendre des décisions avec les
conditions et répéter des traitements avec
les boucles. De quoi écrire un programme qui
fonctionne. Le problème, c'est qu'à ce stade, tout s'empile dans un seul bloc, et plus le programme grossit, moins
on s'y retrouve.
Une fonction, c'est un bloc de code réutilisable auquel on donne un nom. On lui fournit des données en entrée, elle
fait un traitement et renvoie un résultat. C'est la brique avec laquelle on organise un programme.
Pourquoi des fonctions ?
Sans fonctions, le code serait une longue suite d'instructions sans organisation. Imagine un livre sans chapitres, sans paragraphes, sans titres. Les fonctions permettent de :
- Réutiliser du code : au lieu de recopier 20 fois le calcul de la TVA, on écrit une fonction
calculerTVAet on l'appelle partout où on en a besoin. - Nommer les choses : un bon nom de fonction remplace un commentaire.
envoyerEmailDeConfirmation(commande)est bien plus clair qu'un bloc de 30 lignes sans contexte. - Isoler la complexité : chaque fonction fait une seule chose et la fait bien. On peut comprendre un programme en lisant les noms des fonctions sans plonger dans les détails.
Ce dernier point mérite un exemple, car c'est de cette façon qu'un programme s'organise. Une fonction en appelle d'autres, déléguant le détail à celles du niveau en dessous.
// verifierStock, debiterCarte et envoyerEmailDeConfirmation
// sont définies ailleurs dans le programme.
function traiterCommande(commande) {
verifierStock(commande)
debiterCarte(commande)
envoyerEmailDeConfirmation(commande)
}En trois lignes, n'importe qui comprend le déroulé d'une commande sans savoir comment le stock est vérifié ni comment la carte est débitée.
Déclarer une fonction
En JavaScript, on déclare une fonction avec le mot-clé function, suivi de son nom et de ses
paramètres entre parenthèses :
function additionner(a, b) {
return a + b
}
console.log(additionner(3, 7)) // 10
console.log(additionner(100, 50)) // 150
La fonction additionner prend deux paramètres en entrée (a et b) et retourne
leur somme grâce au mot-clé return. On peut ensuite l'appeler autant de fois qu'on veut avec des
valeurs différentes.
Une nuance de vocabulaire au passage : a et b sont les
"paramètres", les cases vides prévues à la déclaration. Les 3 et 7 passés au moment de l'appel sont les "arguments",
les valeurs qui viennent remplir ces cases.
Paramètres et retour
Une fonction peut prendre des paramètres en entrée et retourner un résultat. Mais ce n'est pas obligatoire :
// Pas de paramètres, pas de retour
function direBonjour() {
console.log("Bonjour !")
}
// Paramètres sans retour
function afficherPrix(nom, prix) {
console.log(nom + " coûte " + prix + " euros")
}
// Paramètres avec retour
function estMajeur(age) {
return age >= 18
}
direBonjour() // Bonjour !
afficherPrix("Clavier", 49) // Clavier coûte 49 euros
console.log(estMajeur(25)) // true
console.log(estMajeur(12)) // false
Le mot-clé return met fin à l'exécution de la fonction et renvoie la valeur à celui qui l'a appelée.
Le résultat peut être n'importe quoi : un nombre, du texte, un booléen, un tableau...
Les développeurs essaient de garder leurs fonctions courtes et focalisées. Une fonction de 200 lignes est un signe
qu'il faut la découper en plusieurs fonctions plus petites. On entend souvent la règle, un peu extrême mais
parlante : "une fonction ne devrait pas dépasser 10 lignes".
Un paramètre peut aussi recevoir une valeur par défaut, utilisée quand l'appelant ne fournit rien. Il devient alors optionnel, ce qui permet d'enrichir une fonction sans toucher aux appels existants.
function calculerTTC(prixHT, taux = 0.20) {
return prixHT * (1 + taux)
}
console.log(calculerTTC(100)) // 120, le taux normal s'applique
console.log(calculerTTC(100, 0.055)) // 105.5, taux réduit sur l'alimentaireLes fonctions de rappel (callbacks)
On peut passer une fonction en paramètre d'une autre fonction.
C'est ce qu'on appelle un callback (fonction de rappel).
On l'a déjà croisé dans le chapitre sur les boucles avec forEach :
let clients = ["Alice", "Bob", "Charlie"]
clients.forEach(function(client) {
console.log("Bienvenue, " + client + " !")
})
Ici, la fonction passée à forEach est anonyme : elle n'a pas de nom. C'est forEach qui
décide quand et combien de fois l'appeler.
Les callbacks sont au coeur de la programmation moderne. Ils permettent de créer du code flexible : on écrit une
mécanique générale, et c'est celui qui l'utilise qui choisit le comportement spécifique.
Exemple :
Imagine une fonction trierTableau qui trie un tableau. Le tri lui-même est toujours la même
mécanique, mais le critère de tri change : par prix croissant ? Par nom alphabétique ? Par date ? Le callback
permet de personnaliser le critère sans réécrire la fonction de tri.
Les fonctions fléchées
Si tu jettes un oeil à du JavaScript écrit ces dernières années, tu vas tomber sur une autre écriture, avec une
flèche =>. On parle de fonction fléchée, et c'est exactement la même chose en plus court.
// Écriture classique
clients.forEach(function(client) {
console.log("Bienvenue, " + client + " !")
})
// Fonction fléchée
clients.forEach(client => {
console.log("Bienvenue, " + client + " !")
})
Le mot function disparaît, remplacé par une flèche placée après les paramètres. Quand la fonction tient
en une seule instruction, on peut même se passer des accolades et du return.
const doubler = nombre => nombre * 2
console.log(doubler(21)) // 42
Cette écriture s'est imposée parce que les callbacks sont partout, et que répéter function à chaque
fois finissait par noyer l'intention sous la syntaxe. Il existe quelques différences techniques entre les deux
formes, mais pour lire du code et suivre une discussion entre développeurs, retiens simplement que la flèche est un
raccourci d'écriture.
Tu entendras aussi parler de lambda, le terme générique venu des mathématiques pour désigner une fonction définie
sur place et sans nom. C'est un mot-clé en Python, et on parle d'expressions lambda en Java. En JavaScript, on
l'emploie souvent comme synonyme de fonction fléchée.
La portée des variables
Une variable déclarée à l'intérieur d'une fonction n'existe que dans cette fonction. C'est ce qu'on appelle la portée (ou "scope") :
function calculer() {
let resultat = 42
console.log(resultat) // 42
}
calculer()
console.log(resultat) // Erreur ! resultat n'existe pas ici
C'est un mécanisme de protection. Chaque fonction est un espace isolé : les variables qu'elle crée ne "fuient" pas
vers l'extérieur. Ça évite qu'une fonction modifie accidentellement les données d'une autre.
En revanche, en JavaScript, une fonction lit naturellement les variables déclarées autour d'elle :
let tva = 0.20
function prixTTC(prixHT) {
return prixHT * (1 + tva)
}
console.log(prixTTC(100)) // 120
La variable tva est accessible partout : on dit qu'elle est globale. Les développeurs évitent au
maximum les variables globales, car elles rendent le code plus difficile à comprendre et à maintenir. On préfère
passer les valeurs en paramètres pour rendre les dépendances explicites.
Sur ce point, les langages se répartissent en trois familles. Certains, comme JavaScript ou Python, laissent la
fonction lire librement ce qui l'entoure comme dans l'exemple ci-dessus. D'autres, comme PHP, ne lui montrent
rien tant qu'on ne l'y a pas explicitement autorisée. D'autres enfin, comme le C ou Rust, l'interdisent purement et
simplement, une fonction n'ayant jamais accès aux variables locales d'une autre. Il faut alors les lui passer en
paramètre, ce qui est de toute façon la bonne pratique partout ailleurs.
Ne pas tout écrire soi-même
Les développeurs ne codent pas eux-mêmes toutes les fonctions qu'ils utilisent, loin de là. Réinventer la roue est
même l'un des reproches les plus fréquents qu'ils se font entre eux, parce que ce qu'on réécrit dans son coin
existe presque toujours ailleurs.
Une partie vient du langage lui-même. Depuis le début de cette section, tu as croisé console.log pour
afficher quelque chose, forEach, map et reduce pour parcourir une liste. Ces
fonctions sont fournies d'office.
Le reste vient de bibliothèques, des ensembles de fonctions écrites par d'autres et que l'on installe dans son
projet. Manipuler des dates, générer un PDF, dessiner un graphique, envoyer un email, valider un numéro de carte
bancaire, il existe une bibliothèque pour chacun de ces besoins. Chaque écosystème a son magasin, npm pour
JavaScript, Composer pour PHP, pip pour Python, Maven pour Java.
La quasi-totalité de ces bibliothèques sont
open source, c'est-à-dire que leur code
est public, consultable et modifiable par tous, le plus souvent gratuitement. Un projet moderne s'appuie ainsi sur
des centaines de milliers d'heures de travail offertes par d'autres développeurs.
Mais l'utilisation de bibliothèques n'est pas sans conséquences. Chaque
bibliothèque installée est une dépendance, donc un morceau de ton produit que tu ne maîtrises pas. Si une faille de
sécurité y est découverte, tu dois mettre à jour. Si son auteur cesse de la maintenir, il faudra en changer un
jour. Et ces bibliothèques en utilisent elles-mêmes d'autres, si bien qu'un projet en compte souvent plusieurs
centaines sans que personne ne les ait toutes choisies. C'est la raison pour laquelle les équipes techniques
réclament régulièrement du temps pour "mettre à jour les dépendances", une tâche invisible côté produit mais qui
évite de se réveiller un matin avec une faille béante.