Chapitre 8
Les bases de la programmation - Variables et types

Stocker en mémoire le résultat d'un traitement.
Temps de lecture : 10 minutes


Coder est à la portée de tous.

La vraie difficulté réside dans l’architecture du code. Il faut qu’il soit testable, adaptable en cas d’idée de nouvelle fonctionnalité, maintenable sur le long terme et compréhensible par les 50 développeurs qui vont se succéder dessus.

Bien que chaque langage ait ses particularités, les plus connus ont d’énormes similarités dans les concepts et les syntaxes basiques. Ça s’explique grâce à un ancêtre commun qui a beaucoup influencé les nouveaux langages : le langage C. Ça nous permet, en tant que développeurs, de pouvoir passer de l’un à l’autre sans trop de difficulté.

C’est d’ailleurs pour ça que les entreprises pointues se fichent de recruter des dev qui connaissent déjà leurs technologies. Pendant les entretiens, elles accordent du crédit au raisonnement, aux concepts architecturaux, aux compétences métier… Même sur un nouveau langage, un dev à l’aise sur ces sujets deviendra, en quelques semaines seulement, bien meilleur qu’un simple “pisseur de code” utilisant ce même langage depuis 10 ans.

Le langage n’est qu’un des nombreux outils du développeur.
Aujourd’hui, tu commences l’apprentissage des bases.

Le but n’est bien sûr pas de faire de toi un développeur. Ça te permettra de mieux comprendre ce qu’on fait dans notre vie quotidienne et peut-être d’ouvrir la porte sur une façon différente de penser. 💪

Les exemples se serviront du langage JavaScript. C’est l’un des langages les plus utilisés au monde, avec une syntaxe inspirée du C. Surtout, tu vas pouvoir utiliser la console de ton navigateur web pour exécuter du JavaScript sans installer quoi que ce soit.

Tous les navigateurs proposent une console. Sur Chrome, il faut appuyer sur F12 puis aller dans l’onglet “Console”.

Les variables

Une variable est une boite dans laquelle on stocke une valeur pour la réutiliser après.

a = 1 + 2
b = a * 3

La variable “a” s’est vue assigner la valeur 3, la variable “b” vaut donc 3 * 3 = 9.

Comme son nom l’indique, une variable peut varier, elle peut changer de valeur.

nombreDeLecteursDeTheTechGuide = 200

// Tu partages à tes amis puis :
nombreDeLecteursDeTheTechGuide = 220

À l’inverse, il existe le concept de constante : une fois qu’on a stocké une valeur, on ne peut plus la changer.

const meilleurGuideTech = “Nico”

// Ça fait une erreur
meilleurGuideTech = “Quelqu’un d’autre”

Les variables sont physiquement stockées dans la mémoire de la machine, principalement dans les fameuses barrettes de RAM. Il faut donc faire attention à ne pas la saturer.

Dans une variable, tu peux mettre tout un tas de choses : du texte, des nombres et des trucs beaucoup plus compliqués. En langage C, le développeur doit maîtriser chaque variable et son contenu pour manuellement réserver l’espace nécessaire dans la mémoire puis le libérer quand il devient inutile.

Imagine une mémoire de 32 gigaoctets comme étant un meuble avec 32 milliards d’étagères. Tu veux assigner la valeur “Nico” dans une variable. Le développeur doit compter “il y a 4 caractères dans ce mot, chaque caractère prend 1 octet sur la mémoire, du coup il faut que je réserve 4 octets”. Et gare à lui s'il essaie de stocker quelque chose de plus gros que l’espace qu’il a réservé !

🥳 Bonne nouvelle pour toi !

Les nouveaux langages, à travers les technologies qui les exécutent, gèrent tout seuls l’allocation mémoire.

Attention, ce n’est pas une bonne raison pour ne pas savoir ce qui se trouve dans tes variables !

Les types

Pour la cohérence et la maintenabilité du système, il est primordial de maîtriser le contenu des variables. On essaie au maximum d’explicitement typer (indiquer un type) nos variables dans le code.

Il existe tout un tas de types. Certains sont nativement intégrés dans les langages et certains sont ajoutés par les développeurs. Les types primitifs les plus répandus :

  • Nombre entier, appelé un integer ou int. Exemples : 5, 42, -12…

    Certains systèmes demandent quelques précisions sur la taille du nombre parce qu'ils le stockent différemment. Est-ce que c’est un nombre inférieur à 256 ? Il existe donc des sous-types plus précis : short int, long int…
  • Nombre à virgule / flottant, appelé un float. Exemples : 3.14, 0.00001…

    Les processeurs sont optimisés pour travailler avec des nombres entiers mais sont super nuls pour les nombres à virgule. Les langages les différencient donc pour adapter leur traitement et leur représentation en mémoire.

    Par exemple, en Python, 0.1 + 0.2 n’est pas égal à 0.3 mais à 0.30000000004. C’est beaucoup moins anecdotique que ça en a l’air si tu travailles sur des millions de transactions bancaires ou sur un alignement de planètes. Heureusement, la communauté a développé des outils pour compenser les lacunes natives des processeurs.

    Float a, lui aussi, son sous-type : le double float. Il permet de doubler la précision et de représenter des chiffres très petits ou très grands. En contrepartie, il prend plus de mémoire et est plus lent à traiter.
  • Caractère, appelé un char. Exemples : ‘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, permettant ainsi de différencier 5 et ‘5’.
  • Chaine de caractères, appelée une string. Exemples : “Nico”, …, “5” 😛.

    Certains langages, comme Java par exemple, donnent de l’importance dans la distinction entre un caractère et une chaine 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 chaine.

    Les guillemets / double quotes, permettent de différencier un caractère et une chaine de caractères d’un seul caractère : ‘a’ vs “a”.

    Le concept de caractère tout seul tend à se perdre dans les langages récents. Le développeur manipule directement des strings. Donc pas d’inquiétude, en JavaScript, tu pourras mettre indifféremment des quotes ou des guillemets.

    Petit aparté : Java et JavaScript, en dehors de 4 lettres dans leur nom, n’ont strictement aucun rapport.
  • Booléen ou boolean. C’est vrai ou faux. Certains systèmes utilisent true ou false, d’autres 0 ou 1.
  • Tableau ou array. C’est un ensemble de valeurs. Par exemple : [1, 2, 3, “toto”].

    Ils sont généralement représentés par des crochets.

    Les langages proposent de nombreuses structures permettant de manipuler des lots de données. Certaines garantissent l’unicité d’une valeur, comme un dictionnaire. Certaines garantissent que toutes les valeurs ont le même type. Certaines permettent de stocker de tout et n’importe quoi. Certaines lient les valeurs entre-elles pour qu’on puisse les parcourir dans l’ordre. Etc.

    L’array en JavaScript fait partie des structures très (trop) permissives. On peut y stocker des choux et des carottes. Je pourrais râler des heures sur les dev qui utilisent des array à tort et à travers pour transmettre des lots de données sans se soucier de ce qu’elles contiennent.

    Dans la suite, nous l’utiliserons pour représenter une collection de valeurs du même type.
  • Void : le néant. Quand tu sais qu’il n’y a pas de valeur. On va y revenir.

Certains langages sont fortement typés, c'est-à-dire que le développeur doit absolument déclarer explicitement tous les types. Java, C++, etc.

D’autres langages ne sont pas, ou alors très faiblement typés. L’environnement d’exécution se débrouille tout seul pour comprendre ce que contiennent les variables. Python, JavaScript, etc.

Les langages typés offrent plus de robustesse et souvent plus de performances. C’est indispensable dans des environnements critiques ou embarqués : avions, robots…

Le typage permet aussi une plus grande maintenabilité des projets sur le long terme. Il 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 chiffre et que tu lui passes un texte, ça va péter !

C’est notamment ce faible typage 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 pratiques de merde :

  • 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 typés, mais elle est souvent plus limitée et plus facilement remboursable car les architectures sont, par force, mieux conçues.

Les communautés qui, il y a 20 ans, promulguaient la flexibilité du non-typage “Hey c’est plus simple, pourquoi vous vous faites chier pour rien !”, ont compris leurs erreurs et tentent maintenant d’introduire du typage.

Ainsi, 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 à fort typage.

L’écosystème JavaScript compte maintenant TypeScript, une surcouche qui permet un typage très solide. Perso, 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 un module 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. Ça permet de garder un peu de flexibilité contrôlée et pour certains cas de rester dans la philosophie du Duck typing :

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>

Les objets

Les développeurs créent leurs propres types représentant les concepts métiers qu’ils manipulent.

C’est pour ça qu’il est crucial, avant un projet, de briefer au maximum les développeurs sur le domaine métier : son vocabulaire, ses concepts, son organisation, le besoin présent, les idées futures déjà envisagées… Tout ça peut modifier la conception au plus profond du code.

On appelle Domain Driven Design (DDD), l’approche de conception visant à calquer le domaine métier de la vie réelle.

Dans l’entreprise, il y a un service marketing et un service finance : il y aura deux briques logicielles distinctes.

Les deux services suivent les ventes. Le marketing veut savoir le nombre de paiements et le chiffre d'affaires généré. La finance a besoin du détail des transactions pour calculer la reversion de la TVA, les frais du partenaire de paiement…

En tant que développeur, en première impression :

  • Ces 2 équipes n’utilisent naturellement pas le même vocabulaire : paiement vs transaction. Est-ce que c’est vraiment le même concept ?

    Par exemple, une vision possible : c’est l’heure de renouveler l’abonnement du client. Il doit payer : faire un paiement. Pour ça, on provoque une transaction bancaire pour débiter son compte. Elle échoue. Dans 2 jours, on initiera une autre transaction. Les 2 transactions ont généré des frais bancaires. Dans cet exemple, un paiement est composé d’un ensemble de transactions.

    On va affiner pour avoir un système cohérent et mettre en place un langage ubiquitaire : commun entre tout le monde.
  • L’équipe marketing ne semble pas intéressée par les remboursements, les fraudes, etc. Elle ne regarde que les ventes.
  • La notion de paiement marketing ne contient qu’une date et un montant. La finance, a besoin de la TVA, du nom du partenaire utilisé, etc.

Cette modélisation peut prendre un paquet de temps. Globalement, une équipe métier qui utilise un vocabulaire depuis des années se fiche d’essayer de l’approfondir et de le corriger s’il se révèle faux ou empli d’abus de langage. Pour elle, c’est une perte de temps qui ne lui apporte rien dans la vie quotidienne. Pas toujours facile d’organiser des sessions, de leur expliquer qu’ils pervertissent des concepts et de s’accorder pour que tout le monde parle la même langue. En théorie, le logiciel doit s’adapter au métier. Dans les faits, si le métier est trop incohérent, les dev peuvent forcer un peu pour assurer la fiabilité du système.

Mon exemple préféré : produit vs offre. Je pense y être confronté au moins 2 fois par an 👌.

Une fois toutes ces questions répondues, on peut identifier les entités impliquées, leurs liens et les actions possibles. Dans mon exemple, pour la finance :

  • J’ai un client.
  • Ce client peut payer et se faire rembourser à travers des transactions bancaires.
  • Ces transactions ont un montant nominal : montant numérique + devise.
  • J’ai plusieurs partenaires bancaires.
  • Chaque partenaire a un contrat.
  • Dans ce contrat, il y a des frais calculés selon des règles : 0.01€ pour 100 transactions…

Pour représenter tout ça dans le code, nous créons des types dédiés. Nous avons donc un type Client, Partenaire, Transaction, Frais, Contract, Montant, etc.

Pour créer un type, nous devons le décrire. Cette description, qu’on appelle “une classe”, nous permettra ensuite de construire des instances concrètes : des objets.

Imagine un architecte qui dessine un plan pour une maison. À partir de ce plan, il peut construire tout un lotissement de maisons quasi-identiques.

Les langages divergent un peu sur cette partie. Dans la plupart, nous aurons quelque chose comme :

Définition de la classe Client (le plan de la maison), puis construction de robert (la maison) à l’aide du mot clé “new”.
Un client est composé d’un tableau de transactions et de son email.
class Client {

    // Déclaration de l'attribut "email" en tant que "chaine de caractères".
    email: string

    /*
     * Déclaration de l'attribut "historiqueDesRenouvellements"
     * qui est un tableau vide d'objets du type "Transaction".
     */
    historiqueDesRenouvellements: Transaction[] = []

    /*
     * Déclaration de la fonction "demanderRemboursement".
     * Elle prend en entrée un paramètre "transactionARembourser"
     * du type "Transaction".
     * Elle ne retourne rien : void.
     */
    demanderRemboursement(transactionARembourser: Transaction): void {
        // Traitement pour rembourser.
    }
}

// Création de l'objet "robert", qui est un "Client".
robert = new Client('robert@gmail.com')

// Création d'une transaction
renouvellementAbonnement = new Transaction(10, '€', '2024-02-04')

robert.demanderRemboursement(renouvellementAbonnement)

C'est du code de démonstration, il ne fonctionne pas en l'état 😛.
Les slashs "//" et les étoiles "/*" servent à commenter le code dans la plupart des langages. Même si en théorie, le code doit être suffisamment clair et explicite pour être lu par n'importe qui, en pratique les commentaires sauvent bien des situations.

On voit dans l'exemple qu'un client a une action possible : demanderRemboursement. On nomme la déclaration de cette action “fonction” et, plus particulièrement dans le cadre d’un objet : “méthode”. J'ignore totalement pourquoi on utilise deux mots différents 🤷🏻‍♂️.

Robert est un client de la même façon que 42 est un nombre et “toto” une chaine de caractères.
D’ailleurs, dans certains langages, les types primitifs sont aussi représentés comme des objets.
Sur le type string, on peut ainsi faire des choses comme “toto”.length pour directement connaître la longueur d’un texte.