Dans le chapitre précédent, on a vu qu'une variable est une boite dans laquelle on stocke une valeur. Mais quelle sorte de valeur ? Un nombre ? Du texte ? Vrai ou faux ? C'est ce qu'on appelle le type d'une variable.
Les types de base
Les langages de programmation fournissent un ensemble de types de base, appelés types primitifs. Ce sont les briques élémentaires avec lesquelles on construit tout le reste.
Les nombres
On distingue deux grandes familles de nombres :
- Les nombres entiers, appelés "integer" (ou "int") : 5, 42, -12... Pas de virgule, pas de décimale.
- Les nombres à virgule, appelés "float" (flottants) : 3.14, 0.001, -27.5... Historiquement, les processeurs étaient bien moins à l'aise avec eux, puisque les calculs à virgule étaient simulés par du logiciel, donc très lents. Depuis, un circuit dédié appelé FPU (Floating Point Unit) a été intégré aux processeurs, et les deux familles sont aujourd'hui traitées à des vitesses comparables. Les langages continuent de les différencier car leur représentation en mémoire diffère : un entier est stocké de façon exacte, un nombre à virgule seulement de façon approchée.
console.log(0.1 + 0.2) // 0.30000000000000004Oui, 0.1 + 0.2 ne donne pas exactement 0.3. La raison ? Comme on l'a vu dans le chapitre Binaire et hexadécimal, toute donnée est stockée en binaire, en 0 et en 1. Les nombres à virgule ne font pas exception. Or, tout comme 1/3 est infini en base 10 (0.3333... sans jamais s'arrêter), certaines fractions, comme 0.1, sont infinies en binaire (0.000110011001100... sans jamais s'arrêter non plus). Comme la mémoire d'un ordinateur est finie, la valeur est arrondie au plus proche, et ce minuscule écart finit par se voir au moment du calcul. Ça semble anecdotique, mais ça l'est beaucoup moins quand tu travailles sur des millions de transactions bancaires ou des calculs scientifiques. Les développeurs utilisent des outils spécialisés pour contourner ce problème quand la précision est critique.
Tous les langages ne font pas cette distinction entre entiers et flottants. JavaScript, par exemple, propose un type numérique "number" : tout est considéré comme un nombre à virgule, 1 est traité en interne comme 1.0. C'est plus simple pour le développeur, mais pour certains nombres, ça peut avoir un coût sur la précision ou sur la mémoire utilisée.
Dernier réflexe à prendre, tout ce qui ressemble à un nombre n'a pas vocation à être stocké comme un nombre.
Exemple :
Le cas d'école, c'est le code postal. Il n'est composé que de chiffres, donc on le stocke naturellement comme un nombre. Sauf qu'un nombre n'a pas de zéro initial, alors le 01000 de Bourg-en-Bresse devient 1000, et tous les clients de l'Ain se retrouvent avec un code postal à quatre chiffres.
Le texte
On distingue deux sous-types de texte :
- Caractère : appelé un "char". C'est un symbole unique :
'a','?','5'... Oui, un chiffre est aussi un caractère. On parle ici de sa représentation en tant que symbole, pour l'afficher quelque part par exemple. C'est la même différence qu'entre le chiffre 5 et sa représentation romaine V. Un caractère est entouré de quotes simples, ce qui permet de différencier5(le nombre) et'5'(le symbole). - Chaîne de caractères : appelée une "string". C'est une suite de caractères :
"Nico","Bienvenue","5".
Certains langages, comme Java par exemple, donnent de l'importance à cette distinction entre un caractère et une
chaîne de caractères. Ils n'offrent pas les mêmes fonctionnalités selon le type : par exemple, tu peux compter
le nombre de caractères d'une string, mais pas d'un char (ça n'aurait pas de sens). Les guillemets doubles
permettent de différencier un caractère et une chaîne d'un seul caractère : 'a' vs
"a".
Ce concept de caractère seul tend à disparaître dans les langages récents. Le développeur manipule directement des strings. En JavaScript par exemple, tu peux mettre indifféremment des quotes simples ou des guillemets doubles, c'est strictement la même chose :
let phrase1 = "C'est parti" // simple à l'intérieur, doubles à l'extérieur
let phrase2 = 'Il a dit "bonjour"' // doubles à l'intérieur, simples à l'extérieurIci, tu choisis l'un ou l'autre selon ce qui t'arrange : si ton texte contient un guillemet double, entoure-le avec des simples, et inversement.
D'autres langages, comme PHP, Perl, Ruby et Groovy, traitent différemment la chaîne de caractères selon qu'elle est entourée de quotes ou de guillemets. Les guillemets doubles servent à l'interpolation, c'est-à-dire la capacité à injecter et interpréter des variables dans un texte.
Pour gérer l'interpolation, JavaScript a introduit une troisième syntaxe : les backticks (`).
let prenom = "Nico"
let message = `Bienvenue ${prenom} sur Le Guide Tech`
console.log(message) // Bienvenue Nico sur Le Guide Tech
Avant l'interpolation, pour construire du texte dynamique, on utilisait la concaténation : coller des morceaux
de texte bout à bout. Dans la grande majorité des langages ça se fait avec l'opérateur + ou
..
// Concaténation (ancienne méthode)
let message1 = "Bienvenue " + prenom + " sur Le Guide Tech"
// Interpolation (méthode moderne)
let message2 = `Bienvenue ${prenom} sur Le Guide Tech`Le résultat est identique, mais l'interpolation est bien plus lisible.
La concaténation reste une pratique très utilisée pour les cas non adaptés à l'interpolation. Le symbole
+ est donc utilisé à la fois pour des strings et des nombres. Une mauvaise maîtrise du contenu des
variables peut amener à des comportements imprévus.
console.log(5 + 3) // 8 (addition)
console.log("5" + "3") // "53" (concaténation)Les booléens
Un booléen, c'est le type le plus simple : vrai ou faux.
let estMajeur = true
let aPayeSonAbonnement = false
console.log(estMajeur) // trueÇa a l'air basique, mais les booléens sont partout. À chaque fois qu'un programme prend une décision ("est-ce que l'utilisateur est connecté ?", "est-ce que le panier est vide ?"), il manipule des booléens. On y reviendra en détail dans le chapitre sur les conditions.
Les tableaux
Un tableau, appelé "array", c'est une liste ordonnée de valeurs. Contrairement aux types primitifs vus jusqu'ici, ce n'est pas une brique élémentaire mais un type composite, qui en regroupe plusieurs :
let fruits = ["pomme", "banane", "cerise"]
console.log(fruits[0]) // pomme (premier élément)
console.log(fruits[2]) // cerise (troisième élément)
console.log(fruits[3]) // undefined (n'existe pas !)On accède à un élément par son index, c'est-à-dire sa position dans le tableau. Et attention, on commence à compter à partir de 0, pas de 1. Le premier élément est à l'index 0, le deuxième à l'index 1, etc. Dans un tableau de 10 éléments, les indices vont donc de 0 à 9. C'est une convention héritée du langage C, et une source inépuisable d'erreurs : accéder à l'index 3 d'un tableau qui contient 3 éléments, c'est un dépassement d'index, provoqué par ce qu'on appelle une "off-by-one error", une erreur d'une unité. C'est l'un des bugs les plus classiques en programmation.
Les langages proposent de nombreuses structures pour manipuler des collections de données. Certaines garantissent l'unicité des valeurs, certaines imposent que toutes les valeurs aient le même type, certaines lient les valeurs entre elles pour qu'on puisse les parcourir dans l'ordre. Le tableau est la plus courante et la plus simple. On verra comment parcourir son contenu dans le chapitre sur les boucles.
En JavaScript, un tableau peut contenir n'importe quoi : [1, "toto", true, -3.14]. C'est
techniquement possible, mais c'est rarement une bonne idée. En pratique, on s'efforce de ne stocker que des
valeurs du même type dans un tableau.
Typage statique et typage dynamique
Maintenant qu'on connaît les types, une question se pose : est-ce que le développeur doit les déclarer explicitement, ou est-ce que le langage se débrouille tout seul ?
La réponse dépend du langage, et c'est un choix de conception fondamental.
Les langages à typage statique
Dans un langage à typage statique comme Java ou C++, le développeur doit déclarer le type de chaque variable, et ce type ne changera plus :
int age = 28;
String prenom = "Nico";
boolean estMajeur = true;
// Erreur de compilation : on ne peut pas mettre du texte dans un int
age = "vingt-huit";Si tu essaies de mettre du texte dans une variable déclarée comme nombre entier, le programme refuse de se lancer. Le compilateur joue le rôle de gardien : il vérifie que tout est cohérent avant même que le programme ne tourne.
C'est contraignant, mais ça offre de la robustesse. Beaucoup de bugs sont détectés avant même l'exécution. Et dans des environnements critiques (avions, robots, systèmes bancaires), c'est indispensable.
Les langages à typage dynamique
Dans un langage à typage dynamique comme JavaScript, pas besoin de déclarer le type. L'environnement d'exécution se débrouille tout seul :
let age = 28 // nombre
age = "vingt-huit" // string... et ça passeEn JavaScript, rien ne t'empêche de changer le type d'une variable en cours de route. C'est souple, c'est rapide à écrire, mais ça ouvre la porte à des bugs sournois. Le programme ne plante pas immédiatement : il continue avec des données incohérentes, et le problème ne se manifeste parfois que bien plus tard, dans un endroit complètement différent du code.
Typage fort et typage faible
On confond souvent le typage fort et le typage faible avec le typage statique et le typage dynamique, alors qu'ils ne parlent pas de la même chose. Statique ou dynamique répond à la question "quand le type est-il fixé ?". Fort ou faible demande plutôt jusqu'où le langage se permet de convertir une valeur tout seul, sans te prévenir.
Un langage à typage fort refuse de mélanger les torchons et les serviettes. Un langage à typage faible tente de s'en sortir coûte que coûte, quitte à inventer un résultat :
console.log(1 + "a") // "1a", il a converti le nombre en texte tout seulUn langage à typage fort, lui, s'arrête net et te signale l'erreur plutôt que d'inventer ce "1a".
Ces deux axes sont indépendants, et les quatre combinaisons existent :
- Rust : statique et fort. Le type est déclaré et aucune conversion ne se fait dans ton dos. Il va jusqu'à refuser d'additionner deux nombres entiers si l'un est stocké sur 32 bits et l'autre sur 64. C'est au développeur de dire explicitement ce qu'il veut, plutôt que de laisser le langage deviner et risquer de tronquer une valeur.
- Python : dynamique et fort. Le type d'une variable peut changer en cours de route, mais le langage n'acceptera jamais d'additionner un nombre et du texte.
- JavaScript : dynamique et faible. Le type change quand il veut, et le langage convertit à tout va.
- C : statique et faible. Chaque variable a un type déclaré une fois pour toutes, mais le langage convertit quand même dans ton dos.
Ce dernier cas est le plus surprenant, et il mérite un exemple :
int i = 'A'; // le caractère devient l'entier 65, sans le moindre avertissement
float f = 3.9;
int n = f; // devient 3, la partie décimale est jetée silencieusementLe 65 n'a rien d'un hasard, c'est la position de la lettre A dans la table ASCII, qui associe un nombre à chaque caractère. Pour le C, un caractère n'est rien d'autre qu'un petit nombre, et il passe de l'un à l'autre sans sourciller. C'est ce qui en fait un langage redoutablement efficace pour la programmation système, et tout aussi dangereux, puisque de nombreuses failles de sécurité historiques viennent de conversions de ce genre passées inaperçues.
Le typage faible produit les résultats les plus déroutants, parce que le programme ne se plaint jamais. Il calcule quelque chose, et c'est seulement plus tard que tu découvriras que ça n'avait aucun sens.
Les dégâts du typage permissif
C'est cette permissivité, dynamique et faible à la fois, qui a donné mauvaise presse à des langages comme JavaScript ou PHP. Ils sont faciles d'utilisation, avec une courbe d'apprentissage rapide, le développement des projets va plus vite... Mais justement, ça va trop vite ! La flexibilité de ces langages ouvre la porte aux mauvaises pratiques :
- Involontaires : les développeurs avec un faible niveau peuvent monter des architectures abracadabrantesques. Et puisque ça semble marcher, ils n'ont aucune raison de se former davantage et d'améliorer leurs pratiques.
- Volontaires : même les développeurs aguerris sont tentés de prendre de mauvais raccourcis si on leur met trop de pression avec des délais intenables.
Ça explique en grande partie la dette technique colossale que l'on croise dans les entreprises du web basées sur ces technologies. Attention, je ne dis pas qu'il n'y a pas de dette avec des langages statiquement typés, mais elle est souvent plus limitée et plus facilement remboursable car les architectures sont, par force, mieux conçues.
Le typage oblige le développeur à connaître sa donnée et à construire son code de sorte qu'il respecte des contrats. Si un bout de code s'attend à un nombre et que tu lui passes du texte, ça pète immédiatement au lieu de silencieusement propager l'erreur.
Le retour du typage
Les communautés qui, il y a 20 ans, prônaient la flexibilité du non-typage ("c'est plus simple, pourquoi vous vous embêtez pour rien !") ont compris leurs erreurs et tentent maintenant d'introduire du typage.
PHP est nativement de plus en plus typé à chaque version. Il reste quelques trucs qui m'agacent, mais il se rapproche beaucoup de ce que font les langages à typage statique.
L'écosystème JavaScript compte maintenant TypeScript, une surcouche qui permet un typage très solide. Personnellement, j'adore TypeScript, c'est simple et efficace. Ça sera encore mieux quand il sera complètement indépendant du JavaScript et qu'il se répandra nativement dans les navigateurs web.
Python, lui, a introduit une syntaxe d'annotations permettant la déclaration explicite des types dans le code. Pour le moment ça sert surtout pour faciliter la vie des développeurs, ça n'a pas d'impact réel sur l'exécution des scripts : il n'y aura pas d'erreur si le typage n'est pas respecté lors de l'exécution.
Tous ces exemples offrent un mode hybride : le développeur peut typer s'il le souhaite, mais rien ne l'y oblige. On garde ainsi un peu de la souplesse des langages dynamiques, et notamment du duck typing : cette approche où l'on ne se soucie pas du type déclaré d'un objet, mais uniquement de son comportement. S'il fait ce qu'on attend de lui, il fait l'affaire :
Quand je vois un oiseau marcher comme un canard, nager comme un canard et cancaner comme un canard, j'appelle cet oiseau un canard. <James Whitcomb RILEY>