Be Software Architecture Wise
Be Wise Apps

Read in English · Leer en español · Auf Deutsch lesen

Section 0.3 sur 0.8 · Débutant · 16 min de lecture

Fonctions, modules, objets, interfaces, bibliothèques et frameworks

Leçon vidéo

5 min · narré en anglais, avec sous-titres
Écouter seulement (audio)
Lire la transcription

Pensez à un grand restaurant. Au lieu d’un seul cuisinier qui fait tout, il y a des postes, grill, pâtisserie et salades, chacun avec son personnel, ses outils et ses recettes. Les commandes passent d’un poste à l’autre sur des bons, si bien que le grill n’a pas besoin de savoir comment travaille le pâtissier. Le code s’organise exactement de la même façon, et cette leçon vous montre comment.

La plus petite brique est la fonction : un morceau de code nommé qui fait une seule tâche. Pensez à une machine à café. Vous mettez des grains, de l’eau, vous choisissez une boisson, et un café en sort. Les fonctions vous permettent d’écrire une règle une seule fois et de la réutiliser. Une boutique peut écrire son calcul de TVA dans une seule fonction, et l’appeler depuis le paiement, la facture et l’écran de remboursement. Ainsi, quand le taux de TVA change, une seule fonction change, et non vingt copies éparpillées qui finiraient vite par diverger.

Ensuite, les fonctions apparentées sont regroupées en modules, et les modules apparentés en un paquet. Ici, un paquet nommé shop contient deux modules : pricing et orders. Chaque module a une petite partie publique, destinée aux autres modules, et des détails privés. Dans le module pricing, la fonction de TVA est publique. La fonction auxiliaire en dessous est privée, ce que Python signale par un tiret bas initial. Le module orders n’appelle que la fonction publique de pricing, jamais la fonction privée. Pensez à un service qui a un guichet public et des procédures internes que personne à l’extérieur n’a besoin de voir. L’intérêt de cette séparation est que la partie privée peut être réécrite librement sans rien casser ailleurs, tant que la partie publique tient ses promesses.

Beaucoup de langages permettent de réunir des données et les fonctions qui travaillent dessus. Une classe est le modèle, et un objet est un exemplaire fabriqué à partir de ce modèle. Une classe compte bancaire indique que chaque compte a un titulaire et un solde ; le compte de chaque client est un objet. L’idée clé est l’encapsulation. Si n’importe quel code pouvait modifier directement le solde, rien ne l’empêcherait de devenir négatif. Au lieu de cela, la méthode withdraw est la seule porte d’entrée, et elle vérifie les règles à chaque fois, comme un guichetier de banque devant la chambre forte.

L’idée la plus importante de cette leçon est l’interface. C’est un contrat : les opérations qu’une partie offre aux autres, ce qu’elles reçoivent, ce qu’elles renvoient et ce qui peut mal tourner. Une prise électrique est une interface : tout appareil muni de la bonne fiche fonctionne. Voici pourquoi c’est important. Supposons que le code de paiement d’une boutique appelle directement, partout, les fonctions propres à un prestataire de paiement. Si la boutique change de prestataire l’an prochain, il faudra réécrire tout le code de paiement. Au lieu de cela, la boutique définit sa propre petite interface de passerelle de paiement, avec une seule opération : débiter ce montant sur cette carte. Elle écrit un adaptateur par prestataire. Le code de paiement ne dépend que de l’interface, si bien que changer de prestataire revient à écrire un nouvel adaptateur. Cela facilite aussi les tests. Dans les tests, une fausse passerelle qui ne contacte jamais de vraie banque peut remplacer la vraie.

Presque personne n’écrit tout à partir de zéro. Une bibliothèque est du code écrit par quelqu’un d’autre, que votre code appelle quand il en a besoin, par exemple une bibliothèque pour les dates ou pour créer des fichiers PDF. Votre code garde la main. Un framework, c’est l’inverse. Il fournit le squelette de l’application et appelle votre code aux bons moments. C’est ce qu’on appelle l’inversion de contrôle : ne nous appelez pas, c’est nous qui vous appellerons. Les frameworks épargnent beaucoup de travail de conception, mais ils sont généralement difficiles à remplacer par la suite.

Chaque bibliothèque ou framework que vous utilisez est une dépendance, récupérée par un gestionnaire de paquets comme pip pour Python ou npm pour JavaScript. Ici, une app de boutique choisit directement trois paquets. Mais ces paquets en apportent cinq autres, appelés dépendances transitives. Vous avez choisi trois paquets, mais vous dépendez de huit. Chacun est du code que vous n’avez pas écrit, exécuté avec toutes les permissions du vôtre. Une faille dans l’un d’eux, comme ce paquet crypto, est donc une faille dans votre app.

Pourquoi toute cette structure est-elle importante ? Parce que l’essentiel du coût d’un logiciel part dans sa modification. Deux idées décrivent une bonne structure. Le couplage mesure à quel point une partie dépend des détails d’une autre. La cohésion mesure à quel point le contenu d’une partie va bien ensemble. À gauche, quatre modules accèdent tous aux données les uns des autres. Une modification de l’un peut se propager à tous les autres. À droite, les mêmes modules n’ont que trois liens, chacun via une petite interface. Voilà l’objectif : un couplage faible et une forte cohésion. Cela permet aussi à des équipes distinctes de travailler et de livrer de façon indépendante.

L’architecture, c’est cette même activité à plus grande échelle : décider des grandes parties, de leurs interfaces et du sens dans lequel pointent les dépendances. La section complète contient le code et une liste de contrôle pour ajouter des dépendances. Ensuite, dans la section zéro point quatre, le code quitte l’ordinateur unique, et nous verrons comment les ordinateurs communiquent.

Ce qu’il faut savoir

Pourquoi organiser le code ?

Un petit script de vingt lignes se lit de haut en bas en une minute. Un vrai système, comme une plateforme de livraison de repas, peut compter des centaines de milliers, voire des millions de lignes, écrites par des dizaines de personnes pendant de nombreuses années. Personne ne peut garder tout cela en tête. La seule façon de travailler sur un tel système est de le découper en parties que l’on peut comprendre, tester et modifier une à une, avec des règles claires sur la manière dont les parties peuvent s’utiliser entre elles.

Les langages de programmation nous offrent pour cela une échelle de briques, de la plus petite à la plus grande : les fonctions, les modules et les paquets, les classes et les objets, et les interfaces entre eux. Au-dessus du code que nous écrivons nous-mêmes se trouvent les bibliothèques et les frameworks écrits par d’autres. Cette section gravit cette échelle. Ces mêmes idées, appliquées à plus grande échelle, sont l’objet même de l’architecture logicielle.

Les fonctions : nommer une tâche, puis la réutiliser

Une fonction est un morceau de code nommé qui fait une seule tâche. Elle reçoit des entrées (appelées paramètres ou arguments), effectue un travail et renvoie généralement une sortie (sa valeur de retour). Pensez à une machine à café : vous mettez des grains, de l’eau et vous choisissez une boisson ; elle fait son travail ; un café en sort. Vous n’avez pas besoin de savoir comment fonctionne la pompe pour vous en servir.

Les fonctions apportent trois avantages. La réutilisation : le calcul de la TVA est écrit une seule fois et appelé depuis le paiement, la facture et l’écran de remboursement. La lisibilité : total_with_vat(basket) dit ce qui se passe sans montrer comment. Le changement à un seul endroit : quand le taux de TVA change, une seule fonction change, et non vingt copies du même calcul éparpillées dans le code (des copies qui finiraient vite par diverger).

Une bonne fonction fait une seule chose, porte un nom qui dit ce qu’elle fait, reçoit en paramètres les entrées dont elle a besoin au lieu d’aller chercher des données cachées, et est assez courte pour se lire d’un coup d’œil. Une fonction qui se contente de calculer sa sortie à partir de ses entrées, sans rien modifier d’autre, est appelée fonction pure ; les fonctions pures sont particulièrement faciles à tester, car les mêmes entrées donnent toujours la même sortie.

VAT_RATE = 0.20

def total_with_vat(prices_in_pence):
    """Return the total of a basket in pence, including VAT."""
    subtotal = sum(prices_in_pence)
    return round(subtotal * (1 + VAT_RATE))

checkout_total = total_with_vat([1250, 399])   # 1979
invoice_total = total_with_vat([5000])         # 6000
Une seule fonction, réutilisée à plusieurs endroits

Un module regroupe des fonctions et des données apparentées dans une unité qui porte son propre nom. En Python, un module est simplement un fichier .py ; pricing.py pourrait contenir tout ce qui concerne les prix, les remises et les taxes. Un autre fichier l’utilise en écrivant import pricing. Un paquet (package) est un dossier de modules apparentés : une boutique pourrait ainsi avoir un paquet shop contenant les modules pricing, orders et customers. D’autres langages emploient d’autres mots (Java a des packages, C# des espaces de noms et des assemblies, JavaScript des modules), mais l’idée est la même.

Un module ressemble à un service dans une organisation. Le service financier a un guichet public où les autres peuvent faire des demandes (« merci d’émettre cette facture »), et des procédures internes que personne à l’extérieur n’a besoin de voir. De la même façon, les modules séparent une petite partie publique, destinée aux autres modules, de détails privés. Python signale les noms privés par convention avec un tiret bas initial, comme _round_pence ; Java et C# imposent la confidentialité avec des mots-clés tels que private. L’intérêt de cette séparation est que la partie privée peut être réécrite librement sans rien casser ailleurs, tant que la partie publique tient ses promesses.

Fonctions, modules et paquet

Un paquet nommé shop contient deux modules, pricing et orders ; chacun a une fonction publique et une fonction privée, et orders appelle la fonction publique de pricingPaquet : shopModule : pricing.pytotal_with_vat(items)publique : pour les autres modules_round_pence(amount)fonction privée (tiret bas initial)Module : orders.pyplace_order(customer, items)appelle total_with_vat_save(order)fonction privéeFonctions dans des modules ; modules liés réunis en paquet ; seuls les noms publics servent à l’extérieur
Le module orders n’utilise que la fonction publique du module pricing, jamais sa fonction privée.

Les classes et les objets : des données avec leur comportement

Beaucoup de langages permettent de réunir des données et les fonctions qui travaillent dessus. Une classe est le modèle ; un objet est un exemplaire fabriqué à partir de ce modèle. Une classe BankAccount pourrait indiquer que chaque compte a un titulaire et un solde, et peut accepter des dépôts et des retraits. Le compte de chaque client est alors un objet distinct créé à partir de cette classe. Les fonctions qui appartiennent à une classe s’appellent des méthodes. (Voir l’entrée du glossaire classe et objet.)

L’idée la plus utile ici pour l’architecture est l’encapsulation : un objet garde ses données privées et ne laisse le reste du code les modifier qu’au moyen de ses méthodes. Si n’importe qui pouvait modifier directement account.balance, rien n’empêcherait du code situé ailleurs de rendre un solde négatif ou de sauter le journal d’audit. Avec l’encapsulation, la méthode withdraw est la seule porte d’entrée, et elle peut faire respecter les règles à chaque fois. L’analogie est celle du guichetier de banque : les clients ne peuvent pas entrer dans la chambre forte, mais ils peuvent s’adresser au guichetier, qui vérifie les règles avant de remettre l’argent.

La programmation orientée objet va bien au-delà (héritage, polymorphisme, patrons de conception), et les praticiens ne s’accordent pas sur ce qui en est vraiment utile : certains langages, comme Go, se passent volontairement de l’héritage, et beaucoup d’équipes préfèrent un style plus fonctionnel. L’encapsulation, en revanche, est précieuse quel que soit le style.

class BankAccount:
    def __init__(self, owner):
        self.owner = owner
        self._balance = 0          # private by convention

    def deposit(self, pence):
        if pence <= 0:
            raise ValueError("Deposit must be positive")
        self._balance += pence

    def withdraw(self, pence):
        if pence > self._balance:
            raise ValueError("Insufficient funds")
        self._balance -= pence

    def balance(self):
        return self._balance

acct = BankAccount("Ana")
acct.deposit(10000)
acct.withdraw(2500)
print(acct.balance())   # 7500
L’encapsulation : le solde ne peut changer que par des méthodes qui font respecter les règles

Les interfaces : des contrats entre les parties

Une interface est l’ensemble convenu des opérations qu’une partie d’un système offre aux autres : leurs noms, les entrées qu’elles acceptent, ce qu’elles renvoient et ce qui peut mal tourner. C’est un contrat. L’appelant s’appuie sur le contrat, pas sur la manière dont le travail est fait. Une prise électrique est une interface : tout appareil muni de la bonne fiche fonctionne, et peu importe à l’appareil que l’électricité provienne du vent, du gaz ou du nucléaire.

Dans le code, une interface permet de remplacer une implémentation par une autre. Supposons qu’une boutique accepte aujourd’hui les paiements par carte via un prestataire de paiement, mais qu’elle puisse en changer l’an prochain. Si le code de paiement appelle directement, partout, les fonctions propres au prestataire, changer de prestataire impose de réécrire le paiement. Si, au contraire, la boutique définit sa propre petite interface PaymentGateway avec une seule opération, charge(amount, card_token), puis écrit un adaptateur par prestataire, le code de paiement ne dépend que de l’interface, et changer de prestataire revient à écrire un nouvel adaptateur. La même astuce facilite les tests : dans les tests, une fausse passerelle qui ne contacte jamais de vraie banque peut remplacer la vraie.

Pour les architectes, les interfaces sont l’idée la plus importante de cette section. Chaque frontière d’un système, de la signature d’une fonction à une API web utilisée par des milliers d’autres entreprises, est une interface. Les bonnes interfaces sont petites, stables et cachent les décisions susceptibles de changer ; ce principe, appelé masquage de l’information, a été formulé par David Parnas en 1972 et il est traité au chapitre 3.

from typing import Protocol

class PaymentGateway(Protocol):
    def charge(self, amount_pence: int, card_token: str) -> bool: ...

class ProviderAGateway:
    def charge(self, amount_pence, card_token):
        ...  # call Provider A's web API
        return True

class FakeGateway:                      # used in tests
    def charge(self, amount_pence, card_token):
        return amount_pence < 100_000   # pretend large payments fail

def checkout(basket_total, card_token, gateway: PaymentGateway):
    return "paid" if gateway.charge(basket_total, card_token) else "declined"
Une interface (ici un Protocol Python) avec deux implémentations interchangeables

Les bibliothèques et les frameworks : du code écrit par d’autres

Presque personne n’écrit tout à partir de zéro. Une bibliothèque est du code réutilisable que quelqu’un d’autre a écrit et que votre code appelle quand il en a besoin : une bibliothèque pour gérer les dates et les fuseaux horaires, pour générer des PDF, pour dialoguer avec une base de données. Vous gardez la main ; la bibliothèque est un outil que vous prenez et que vous reposez.

Un framework, c’est différent. Il fournit le squelette d’ensemble d’une application et décide du moment où les choses se produisent ; vous comblez les vides avec votre propre code, et le framework appelle ce code aux bons moments. Un framework web comme Django (Python), Spring (Java), Ruby on Rails ou Express (JavaScript) reçoit chaque requête web entrante, détermine laquelle de vos fonctions doit la traiter, appelle cette fonction et transforme son résultat en page web ou en données. Le moyen le plus simple de retenir la différence : vous appelez une bibliothèque ; un framework appelle votre code.

Ce renversement s’appelle l’inversion de contrôle. Martin Fowler attribue à Ralph Johnson et Brian Foote l’emploi de ce terme en 1988, et l’idée est souvent résumée par le principe d’Hollywood : Ne nous appelez pas, c’est nous qui vous appellerons. Une franchise de restauration est une analogie assez juste : le franchiseur fournit le bâtiment, le système de commande, les horaires d’ouverture et les règles ; le franchisé fournit la cuisine dans les créneaux que le franchiseur lui laisse.

Qui appelle qui

Avec une bibliothèque, votre code l’appelle ; avec un framework, c’est le framework qui appelle votre code : c’est l’inversion de contrôleBibliothèque : votre code dirigeVotre codedécide quoi faire et quandBibliothèquep. ex. dates ou fichiers PDFvous appelezelle renvoieFramework : il dirigeFrameworkpilote l’exécutionVotre codecomble les videsil appellevous rendez
Le sens du contrôle est la vraie différence entre une bibliothèque et un framework.

Bibliothèque ou framework

BibliothèqueFramework
Qui contrôle le déroulementVotre codeLe framework (inversion de contrôle)
ExemplesUne bibliothèque de dates, un générateur de PDF, un parseur JSONDjango, Spring, Ruby on Rails, Angular
Facilité de remplacement ultérieurEn général moyenne : modifier les endroits qui l’appellentEn général difficile : la structure du framework façonne toute l’application
Principal avantageÉvite d’écrire soi-même une fonctionnalitéÉvite de concevoir toute la structure ; des conventions communes dans l’équipe
Principal risqueUne dépendance de plus à maintenirDépendance à sa façon de travailler et à son cycle de mises à jour

Les dépendances et les gestionnaires de paquets

Chaque bibliothèque ou framework dont votre code a besoin est une dépendance. Les projets modernes en ont beaucoup ; ils utilisent donc un gestionnaire de paquets pour les récupérer : pip pour Python (depuis le Python Package Index, PyPI) et npm pour JavaScript (depuis le registre npm) sont les plus connus, avec des équivalents comme Maven pour Java et NuGet pour .NET. Un projet liste ce dont il a besoin, souvent avec des plages de versions, et le gestionnaire de paquets télécharge ces paquets et, surtout, les paquets dont eux-mêmes dépendent. Ces dépendances indirectes, dites transitives, sont souvent bien plus nombreuses que les dépendances directes.

Les versions sont couramment numérotées MAJEUR.MINEUR.CORRECTIF selon une convention appelée versionnage sémantique : un changement du numéro majeur signale une modification incompatible, un changement du numéro mineur de nouvelles fonctionnalités qui ne devraient pas gêner les utilisateurs existants, et un changement du numéro de correctif des corrections de bugs. Les projets conservent aussi un fichier de verrouillage (lock file, comme package-lock.json pour npm) qui enregistre la version exacte de chaque paquet installé, afin que la machine de chaque développeur et le build de production utilisent le même code.

Dépendances directes et transitives

Une application avec trois dépendances directes, qui apportent elles-mêmes cinq dépendances transitivesApp de boutiqueframework webdépendance directeSDK paiementsdépendance directebiblio. PDFdépendance directetemplatestransitiveclient HTTPtransitiveparseur JSONtransitivecryptotransitivepolicestransitiveVous avez choisi trois paquets ; vous dépendez de huit. Une faille dans l’un d’eux (p. ex. crypto) touche aussi votre app.
La plus grande partie du code exécuté par votre application découle des choix de quelqu’un d’autre.

Les dépendances font gagner énormément d’efforts, mais chacune est du code que vous n’avez pas écrit, exécuté avec toutes les permissions du vôtre. Deux incidents bien connus montrent les risques. En mars 2016, un développeur a retiré ses paquets de npm à la suite d’un différend, parmi lesquels left-pad, une minuscule fonction d’environ onze lignes servant à compléter du texte ; des milliers de projets qui en dépendaient, directement ou indirectement, ont soudain cessé de compiler jusqu’à ce que npm le rétablisse. En décembre 2021, une grave faille de sécurité, surnommée Log4Shell, a été révélée dans Log4j 2, une bibliothèque de journalisation Java très utilisée ; des organisations du monde entier ont passé des semaines à retrouver chaque système qui la contenait, souvent enfouie plusieurs niveaux plus bas dans leurs dépendances.

Liste de contrôle : avant d’ajouter une dépendance

Les questions qu’une équipe prudente se pose avant de s’appuyer sur le paquet de quelqu’un d’autre.

  • En avons-nous besoin ?Quelques lignes de notre propre code peuvent coûter moins cher, à la longue, qu’un paquet qu’il faut suivre et mettre à jour.
  • Est-il maintenu ?Regardez les versions récentes, les problèmes de sécurité non résolus et le nombre de personnes qui le maintiennent. Un paquet abandonné devient votre problème.
  • Qu’apporte-t-il avec lui ?Vérifiez combien de dépendances transitives il ajoute ; chacune est du code de plus auquel il faut faire confiance.
  • La licence est-elle compatible ?Les licences open source diffèrent ; certaines imposent des obligations lorsque vous distribuez votre logiciel.
  • Les versions sont-elles figées et analysées ?Utilisez un fichier de verrouillage et un outil automatique qui vous avertit lorsqu’une vulnérabilité connue touche une version que vous utilisez.
  • Pourrions-nous le remplacer ?Placez les dépendances importantes derrière notre propre interface, pour qu’un changement de fournisseur ne touche qu’un seul endroit.

Pourquoi la structure compte : le changement et les équipes

Le code est lu et modifié bien plus souvent qu’il n’est écrit au départ. L’essentiel du coût d’un logiciel sur sa durée de vie part dans sa modification : corriger des bugs, ajouter des fonctionnalités, s’adapter à de nouvelles lois et à de nouveaux partenaires. La structure détermine combien cela coûte. Dans un système bien organisé, une modification du calcul de la TVA touche un seul module. Dans un système mal organisé, elle touche quarante fichiers appartenant à cinq équipes, et quelque chose qui n’a rien à voir casse.

Deux idées, décrites pour la première fois par Larry Constantine et ses collègues dans le mouvement de la conception structurée, à la fin des années 1960 et dans les années 1970, résument ce que signifie « bien organisé ». Le couplage mesure à quel point une partie dépend des détails d’une autre : dans un système fortement couplé, modifier une partie oblige à en modifier d’autres. La cohésion mesure à quel point le contenu d’une partie va bien ensemble : un module très cohésif fait une seule tâche claire. L’objectif est un couplage faible et une forte cohésion : des parties qui font chacune bien une seule tâche et qui communiquent entre elles par de petites interfaces stables.

La structure compte aussi pour les personnes. Si deux équipes doivent toutes deux modifier le même module pour l’essentiel de leur travail, elles passeront leur temps à s’attendre et à se gêner mutuellement. Si chaque équipe possède des modules cohésifs aux interfaces claires, elles peuvent travailler et livrer de façon indépendante. Melvin Conway a observé en 1968 que les systèmes ont tendance à refléter la structure de communication des organisations qui les construisent, une observation connue sous le nom de loi de Conway et étudiée au chapitre 3.

Le couplage en un coup d’œil

À gauche, quatre modules sont tous reliés les uns aux autres ; à droite, les mêmes modules n’ont que trois liens, chacun via une interfaceFortement couplé, faible cohésionCommandeFacturationStockE-mailchaque module accède aux données des autresFaiblement couplé, forte cohésionCommandeFacturationStockE-mailinterfacepeu de liens, chacun via une petite interface
Les quatre mêmes modules, reliés de deux façons : chaque lien supplémentaire est un chemin de plus par lequel un changement peut se propager.

Pourquoi c’est important pour l’architecture

L’architecture est en grande partie la même activité que celle de cette section, menée à plus grande échelle et avec des enjeux plus élevés : décider quelles sont les grandes parties d’un système, de quoi chacune est responsable, quelles interfaces les relient et dans quel sens pointent les dépendances. Un module à l’intérieur d’un programme et un service déployé séparément de l’autre côté d’un réseau sont tous deux des parties dotées d’interfaces ; les questions de couplage, de cohésion et de masquage de l’information s’appliquent aux deux.

Le chapitre 3 précise ces principes, en commençant par la modularité ainsi que le couplage et la cohésion, et le chapitre 8 revient sur les dépendances sous l’angle de la sécurité dans la sécurité de la chaîne d’approvisionnement. Ensuite, le code quitte l’ordinateur unique : la section suivante explique comment les ordinateurs communiquent entre eux.

Vérifiez votre compréhension

Pourquoi vaut-il mieux appeler une seule fonction total_with_vat depuis trois endroits que d’écrire le calcul trois fois ?

La règle se trouve alors à un seul endroit : quand le taux de TVA change, une seule modification corrige tous les appelants, et les copies ne peuvent pas diverger.

Qu’est-ce que l’encapsulation empêche le reste du code de faire à un objet compte bancaire ?

Elle empêche le reste du code de modifier directement le solde, si bien que chaque changement doit passer par des méthodes comme withdraw, qui font respecter les règles.

Un framework web appelle votre fonction chaque fois qu’une requête pour /orders arrive. Comment s’appelle ce fonctionnement ?

L’inversion de contrôle : c’est le framework, et non votre code, qui contrôle le déroulement et appelle votre code au besoin.

À quoi sert un fichier de verrouillage, comme package-lock.json ?

Il enregistre la version exacte de chaque paquet installé, y compris les paquets indirects, afin que chaque machine de développeur et chaque build de production utilisent exactement le même code.

Suivant · 0.4Comment les ordinateurs communiquentComment les données traversent internet en paquets, à quoi servent les adresses IP, les ports, TCP, UDP et le DNS, comment fonctionnent les requêtes HTTP et HTTPS, pourquoi la latence a un plancher fixé par la physique et ce qui se passe quand vous tapez une adresse web.Continuer →

Sources et lectures complémentaires

Une question sur cette section ? ArchitectBot, l’assistant d’étude IA du cours, répond d’abord à partir du cours.

Demander : « Comment décider s’il faut bâtir une fonctionnalité sur un framework ou garder ma logique métier indépendante de celui-ci ? »