ITSkillsCenter
Développement Web

Logique booléenne pour développeurs : tables de vérité, simplification et code

13 min de lecture

📍 Guide principal : Mathématiques appliquées à l’informatique : le socle pratique
Ce tutoriel approfondit la partie logique du guide principal — à lire d’abord pour situer ce que la logique apporte au quotidien d’un développeur.

Les bugs les plus pernicieux en production ne viennent presque jamais d’un algorithme exotique mal compris : ils viennent d’une condition mal écrite. Un if qui devait protéger un appel sensible ne se déclenche jamais, ou se déclenche au mauvais moment. Une requête SQL filtre les mauvaises lignes parce que les AND et les OR se mélangent. Un test unitaire reste vert parce que sa condition tautologique passe toujours. Tous ces incidents ont une racine commune : une maîtrise approximative de la logique booléenne. Ce tutoriel pose, étape par étape, les outils indispensables pour écrire des conditions justes du premier coup, et pour simplifier celles qu’on hérite d’un code legacy.

Prérequis

  • Python 3.10 ou plus récent (pour les exemples exécutables)
  • Un éditeur de texte ou un IDE (VS Code, PyCharm, Vim)
  • Niveau attendu : intermédiaire — vous savez écrire une condition simple en Python ou JavaScript
  • Temps estimé : 90 minutes pour parcourir et exécuter chaque étape

Étape 1 — Comprendre les cinq opérateurs fondamentaux

La logique booléenne ne manipule que deux valeurs : vrai (True, 1) et faux (False, 0). Toute la richesse vient des opérateurs qui combinent ces valeurs. Avant de plonger dans les simplifications, il faut savoir lire instantanément la table de vérité de chaque opérateur — c’est l’alphabet du domaine. Les cinq opérateurs à mémoriser sont la conjonction, la disjonction, la négation, l’implication et l’OU exclusif. La conjonction et la disjonction sont commutatives et associatives ; l’implication n’est pas commutative et l’OU exclusif a des propriétés algébriques particulières (il forme un groupe avec la négation). Cette gymnastique mentale paraît scolaire, mais elle se rentabilise dès la première condition à trois variables.

# operators_truth.py — table de vérité des cinq opérateurs
def implies(a, b):
    return (not a) or b

def xor(a, b):
    return a != b

print(f"{'A':>5} {'B':>5} {'A AND B':>8} {'A OR B':>7} {'NOT A':>6} {'A→B':>5} {'A XOR B':>8}")
for a in (False, True):
    for b in (False, True):
        print(f"{int(a):>5} {int(b):>5} {int(a and b):>8} {int(a or b):>7} {int(not a):>6} {int(implies(a,b)):>5} {int(xor(a,b)):>8}")

L’exécution de python operators_truth.py doit afficher quatre lignes — les quatre combinaisons possibles de deux booléens. Lisez chaque colonne lentement : A AND B ne vaut 1 que si les deux entrées sont vraies, A OR B vaut 0 seulement si les deux sont fausses, et l’implication A→B est fausse uniquement quand A est vraie et B fausse — c’est le seul cas où une promesse est trahie. Si l’une de ces colonnes vous surprend, refaites-la à la main avant la suite : c’est la fondation de tout ce qui suit.

Étape 2 — Construire la table de vérité d’une expression complexe

Dès qu’une condition mélange trois variables ou plus, l’intuition lâche. Le réflexe sain consiste à dresser systématiquement la table de vérité de l’expression, ligne par ligne. Avec n variables, la table compte 2n lignes : 8 lignes pour trois variables, 16 pour quatre. Au-delà de six variables, on passe à des outils automatiques (sympy, solveurs SAT) parce que la taille devient inhumaine. Ce que la table apporte, ce n’est pas seulement la valeur finale : c’est aussi la possibilité de comparer deux expressions ligne par ligne pour vérifier qu’elles sont équivalentes — autrement dit, que la simplification qu’on s’apprête à faire ne change rien au comportement.

# truth_table.py — génère la table de vérité d'une expression Python à n variables
import itertools

def truth_table(expr, var_names):
    n = len(var_names)
    rows = []
    for combo in itertools.product([False, True], repeat=n):
        env = dict(zip(var_names, combo))
        result = eval(expr, {}, env)
        rows.append((*combo, result))
    return rows

variables = ["a", "b", "c"]
expression = "a and (b or not c)"

header = " ".join(f"{v:>5}" for v in variables) + f" | {expression}"
print(header); print("-" * len(header))
for row in truth_table(expression, variables):
    *vals, res = row
    cells = " ".join(f"{int(v):>5}" for v in vals)
    print(f"{cells} | {int(res)}")

La sortie de ce script affiche les huit lignes de la table de vérité pour a AND (b OR NOT c). Comparez chaque ligne à votre intuition initiale : combien de combinaisons rendaient l’expression vraie selon vous, combien d’après le programme ? L’écart, quand il existe, mesure exactement la dette technique cachée dans les if que vous avez écrits sans table de vérité. eval est utilisé ici uniquement parce que les expressions viennent de votre propre code — ne l’utilisez jamais sur une chaîne fournie par un utilisateur en production.

Étape 3 — Simplifier avec les lois algébriques

L’algèbre de Boole obéit à un petit corpus de lois qui permettent de réécrire une expression sans changer sa table de vérité. Les lois fondamentales sont la commutativité (A ∧ B ≡ B ∧ A, idem pour ), l’associativité ((A ∧ B) ∧ C ≡ A ∧ (B ∧ C)), la distributivité (A ∧ (B ∨ C) ≡ (A ∧ B) ∨ (A ∧ C)), l’absorption (A ∨ (A ∧ B) ≡ A), et surtout les lois de De Morgan, énoncées au XIXe siècle par Augustus De Morgan : ¬(A ∧ B) ≡ ¬A ∨ ¬B et ¬(A ∨ B) ≡ ¬A ∧ ¬B. Ces deux dernières sont les plus rentables au quotidien : elles permettent de retourner une condition pour la rendre plus lisible.

# de_morgan_check.py — vérifier l'équivalence par énumération exhaustive
import itertools

def equivalent(expr1, expr2, var_names):
    for combo in itertools.product([False, True], repeat=len(var_names)):
        env = dict(zip(var_names, combo))
        if eval(expr1, {}, env) != eval(expr2, {}, env):
            return False, combo
    return True, None

ok, counter = equivalent(
    "not (a and b)",
    "(not a) or (not b)",
    ["a", "b"],
)
print("Équivalentes :", ok, "contre-exemple :", counter)

Le script doit afficher Équivalentes : True contre-exemple : None. La fonction equivalent est un mini-vérificateur : si elle trouve une combinaison où les deux expressions divergent, elle renvoie le contre-exemple. Gardez ce snippet sous la main quand vous refactorez une condition complexe — il vous évitera d’introduire un bug en croyant simplifier. C’est le genre de filet de sécurité de cinq lignes qui transforme un refactoring risqué en opération routinière.

Étape 4 — Visualiser avec une carte de Karnaugh

Les lois algébriques sont puissantes mais demandent de l’expérience. Pour les expressions à deux, trois ou quatre variables, la carte de Karnaugh — inventée par Maurice Karnaugh en 1953 chez Bell Labs — fournit une approche graphique. On dispose les minterms (combinaisons où l’expression vaut 1) sur un quadrillage où les cases adjacentes diffèrent d’une seule variable, puis on regroupe les 1 par blocs rectangulaires de tailles 1, 2, 4 ou 8. Chaque bloc se traduit par un terme simplifié de la forme normale disjonctive minimale.

Karnaugh — F(A,B,C) AB \ C 0 1 00011110 0 1 0 0 0 1 0 1
Le bloc rouge regroupe deux 1 adjacents en colonne C=1 → terme dépendant de B et C ; le bloc bleu regroupe la dernière paire → terme dépendant de A et C.

La carte se lit par paires adjacentes : un bloc de deux cases adjacentes élimine la variable qui change entre elles. Un bloc de quatre cases élimine deux variables. La règle d’or : choisir les blocs les plus grands possibles, puis les recouvrir tous, sans nécessairement les rendre disjoints. Pour quatre variables, on passe à un quadrillage 4×4. Au-delà, l’algorithme de Quine-McCluskey prend le relais — c’est lui qu’utilisent les outils automatiques de minimisation.

Étape 5 — Simplifier automatiquement avec SymPy

Pour les expressions importantes, on délègue le travail à un solveur. SymPy, la bibliothèque de calcul symbolique de Python, propose simplify_logic, qui applique automatiquement les lois booléennes et renvoie une forme minimale équivalente. C’est l’outil que tout développeur devrait avoir dans sa boîte à outils pour valider ou produire des conditions complexes — typiquement les règles métier, les permissions, les filtres de feature flags.

pip install sympy

L’installation est rapide (quelques secondes) et n’apporte que des dépendances pures Python — sympy n’a pas besoin de compilateur natif. Vérifiez que la commande renvoie Successfully installed sympy-... avant de passer à la suite.

# sympy_simplify.py
from sympy import symbols
from sympy.logic.boolalg import simplify_logic, to_dnf, to_cnf

a, b, c = symbols("a b c")
expr = (a & b) | (a & ~b) | (~a & b & c)

print("Original :", expr)
print("Simplifiée :", simplify_logic(expr))
print("DNF       :", to_dnf(simplify_logic(expr)))
print("CNF       :", to_cnf(simplify_logic(expr)))

L’exécution affiche la forme simplifiée — typiquement quelque chose comme a | (b & c) — accompagnée de ses formes normales disjonctive (DNF) et conjonctive (CNF). La DNF se lit comme un OU de ET (« l’une de ces situations »), la CNF comme un ET de OU (« toutes ces contraintes »). En production, la DNF est plus naturelle pour exprimer des règles métier en termes de cas, alors que la CNF correspond mieux aux solveurs SAT et aux contraintes formelles.

Étape 6 — Refactoriser une condition réelle

Mettons les outils en pratique sur un cas concret. Imaginez une fonction d’autorisation qui s’est progressivement complexifiée au fil des évolutions produit. Ce genre d’expression devient illisible et truffée de bugs latents — l’objectif est de la ramener à une forme minimale équivalente que les revues de code peuvent valider d’un coup d’œil.

# refactor_auth.py
from sympy import symbols
from sympy.logic.boolalg import simplify_logic

is_admin, is_active, has_2fa, is_blocked = symbols("is_admin is_active has_2fa is_blocked")

# Condition héritée — illisible mais à préserver côté comportement
legacy = ((is_admin & is_active & has_2fa) |
          (is_admin & is_active & ~has_2fa & ~is_blocked) |
          (is_active & has_2fa & ~is_blocked))

simplified = simplify_logic(legacy)
print("Condition simplifiée :", simplified)

Le résultat affiche une forme bien plus courte que l’expression d’origine — typiquement quelque chose qui se lit immédiatement : un utilisateur actif, non bloqué, et soit administrateur soit muni d’une 2FA. Avant de remplacer le code legacy, exécutez la fonction equivalent de l’étape 3 sur l’ancienne et la nouvelle expression pour vous assurer qu’elles sont strictement équivalentes — c’est cette comparaison exhaustive qui vous protège des régressions.

Étape 7 — Vérifier la couverture des tests

Une condition à n variables a 2n combinaisons possibles. Une suite de tests qui n’en couvre qu’une fraction laisse passer des bugs. La technique de la table de vérité s’applique aussi à la rédaction de tests : on génère programmatiquement les 2n combinaisons et on vérifie le comportement attendu sur chacune. C’est l’équivalent d’un test exhaustif sur l’espace des entrées booléennes.

# exhaustive_test.py
import itertools

def can_access(is_admin, is_active, has_2fa, is_blocked):
    return is_active and not is_blocked and (has_2fa or is_admin)

expected = {}
for combo in itertools.product([False, True], repeat=4):
    is_admin, is_active, has_2fa, is_blocked = combo
    expected[combo] = is_active and not is_blocked and (has_2fa or is_admin)

assert all(can_access(*k) == v for k, v in expected.items())
print(f"OK — {len(expected)} combinaisons couvertes (2^4)")

Le script doit afficher OK — 16 combinaisons couvertes (2^4). Cette technique se généralise à toute logique métier qui se ramène à un nombre raisonnable de variables booléennes. Au-delà de six ou sept entrées, l’explosion combinatoire (128 combinaisons) reste gérable mais commence à peser sur la durée des tests : on bascule alors vers du property-based testing avec Hypothesis, qui échantillonne intelligemment l’espace plutôt que de l’énumérer entièrement.

Erreurs fréquentes

Erreur Cause Solution
Confondre or et and dans une négation Distraction sous De Morgan Toujours appliquer la loi de De Morgan explicitement avant d’inverser une condition
Court-circuit inattendu en JavaScript ou Python and/&& évaluent paresseusement Vérifier que les expressions de droite n’ont pas d’effet de bord crucial
Comparer des chaînes ou des nombres avec and au lieu de booléens Coercition implicite Toujours convertir en booléen explicite quand le résultat est consommé comme tel
Tester partiellement une condition à 4 variables ou plus Manque de discipline Générer 2n combinaisons et asserter sur chacune
Simplifier à la main et casser le sens Pas de vérification d’équivalence Utiliser simplify_logic de sympy ou un mini-vérificateur exhaustif

Tutoriels associés

Pour aller plus loin

FAQ

Quand utiliser une carte de Karnaugh plutôt que SymPy ?

Pour deux à quatre variables et un travail de compréhension manuel, la carte de Karnaugh reste plus pédagogique : on voit les regroupements à l’œil. Au-delà, ou pour intégrer la simplification dans un outil automatique (générateur de code, vérificateur de règles métier), SymPy est plus rapide et moins sujet à erreur humaine.

Pourquoi la simplification logique compte-t-elle pour la sécurité ?

Une condition d’autorisation simplifiée est plus facile à auditer. Une expression à 12 termes peut cacher un cas d’usage non couvert — un attaquant cherche précisément ce trou. Réduire à la forme minimale, puis valider exhaustivement les combinaisons, ferme la porte à ce type de faille logique.

Les langages à typage faible (PHP, JavaScript) ont-ils des pièges spécifiques ?

Oui. La coercition implicite (0, "", null, undefined traités comme falsy) crée des comportements surprenants. Préférez les comparaisons strictes (===) et les conversions explicites (Boolean(x)) pour rester dans une logique purement booléenne.

Y a-t-il un lien avec les solveurs SAT ?

Oui. Un solveur SAT répond à la question « existe-t-il une affectation des variables qui rend cette expression vraie ? ». Le problème SAT est NP-complet (théorème de Cook-Levin, 1971) — c’est l’archétype des problèmes durs en informatique. Les solveurs modernes (Z3, MiniSat, Glucose) résolvent en pratique des instances à des centaines de milliers de variables, ce qui les rend utilisables pour la vérification formelle de logiciels critiques.

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité