Comment les solutions FileMaker s’effondrent sous leur propre logique
Les violations de contraintes produisent des séquences de défaillances prévisibles et mécaniques. Cet article en montre le mécanisme.
L’article précédent a établi cinq contraintes. Pas des préférences — des exigences structurelles. Les conditions dans lesquelles un système reste compréhensible dans le temps.
Cet article montre ce qui se passe lorsqu’elles sont violées.
Pas comme une liste de mauvaises pratiques. Comme une séquence de mécanismes. Chaque défaillance suit une chaîne : une violation crée une condition, la condition produit un résultat, le résultat s’accumule. Le système ne se casse pas. Il devient quelque chose qu’on ne peut plus comprendre.
C’est ça, l’effondrement. Pas un crash. Pas une perte de données. Le point où le système tourne encore mais n’est plus compréhensible — et donc plus modifiable en toute sécurité.
Une clarification importante avant d’aborder les défaillances : aucun de ces schémas n’est immédiatement destructeur. Chacun est fonctionnel — parfois pendant des années — dans les conditions qui l’ont produit. Petits systèmes, équipes stables, évolution limitée. La défaillance n’est pas dans le schéma lui-même. Elle est dans l’hypothèse que les conditions qui le rendent sûr vont tenir. Elles ne tiennent pas. Les systèmes grandissent. Les équipes changent. Les besoins évoluent. Les schémas qui fonctionnaient à petite échelle deviennent des passifs structurels à plus grande échelle, et la transition se produit sans avertissement.
Chacune des défaillances ci-dessous est inévitable étant donné les conditions qui la produisent. Si vous reconnaissez ces conditions dans un système que vous avez construit, la défaillance est déjà en cours.
Défaillance 1 : Contexte implicite
Contrainte violée : Contrats explicites
La violation
Un script est écrit pour traiter une facture. Il navigue vers un modèle précis, lit l’enregistrement actif et opère dessus. Le développeur le teste depuis le modèle Facture. Il fonctionne parfaitement.
Nulle part dans le script il n’y a de déclaration de ce qu’il requiert. Pas de vérification que le modèle est correct. Pas de contrôle que l’enregistrement actif est bien une facture. Ces conditions sont simplement supposées — parce qu’au moment où il a été écrit, elles étaient toujours vraies.
Le mécanisme
Le script a des préconditions cachées. Il produira des résultats corrects uniquement dans le contexte précis où il a été écrit et testé. Appelez-le depuis n’importe quel autre endroit — un traitement par lot, une action de portail, un nouveau point d’entrée ajouté plus tard — et le contexte qu’il suppose divergera inévitablement. Le modèle est différent. L’enregistrement actif n’est pas une facture. Ou il n’y a pas d’enregistrement actif.
Le script s’exécute quand même. FileMaker n’impose pas les préconditions. Rien ne signale que les conditions ont été violées. Le script s’exécute dans le contexte qu’il trouve, et se termine.
La règle absolue : un script qui dépend d’un contexte qu’il ne déclare pas produira un comportement erroné dans tout contexte différent de celui pour lequel il a été écrit. Pas parfois. À chaque fois.
Le résultat
Ce qu’il fait dans le mauvais contexte est déterminé par ce que le contexte s’avère être — une information dont le script n’a jamais déclaré avoir besoin et n’a jamais vérifiée. Le comportement est erroné d’une façon que rien dans le script ne prédit ni n’explique. La défaillance est invisible jusqu’à ce qu’elle apparaisse en production, dans des données qu’on ne peut pas rattacher à une cause.
Pourquoi ça ne s’améliore pas
Chaque nouveau point d’appel est un pari que le contexte correspondra aux hypothèses cachées du script. Chaque nouveau développeur doit reconstruire indépendamment ces hypothèses à partir du code — ou à partir du bug qu’il vient d’introduire. Les préconditions ne deviennent pas visibles avec le temps. Elles deviennent plus structurantes. Chaque réutilisation du script étend la surface de la défaillance sans étendre la connaissance qu’on en a.
La défaillance ne se résout pas. Elle attend le prochain contexte qui ne correspond pas.
Défaillance 2 : Mutation distribuée
Contrainte violée : Mutation des données contrôlée
La violation
Un enregistrement Client a une rubrique Statut. Au début de la vie du système, un seul script la définit. C’est propre. Puis un deuxième chemin est ajouté — une action de portail qui met à jour Statut quand un enregistrement lié change. Puis un déclencheur. Puis une règle de saisie automatique qui le recalcule à la modification. Chaque ajout avait du sens à l’époque. Chacun était local, raisonnable, et testé isolément.
Le mécanisme
La rubrique a maintenant plusieurs points de mise à jour. Chacun applique sa propre logique, dans son propre contexte, sans connaissance des autres. Quand deux chemins s’exécutent en séquence — ou quand une seule action utilisateur déclenche plusieurs chemins — la valeur finale de Statut dépend de l’ordre d’exécution. Le dernier à écrire l’emporte. L’ordre d’exécution n’est pas garanti — et ce qui n’est pas garanti ne peut pas être reproduit, et ce qui ne peut pas être reproduit ne peut pas être débogué.
La règle absolue : une rubrique avec plusieurs points de mise à jour n’a pas d’état final déterministe. La logique de validation écrite dans un chemin ne s’exécute pas quand un autre chemin écrit dans la rubrique. Une piste d’audit devient impossible à implémenter correctement parce que les écritures sont dispersées et invisibles les unes pour les autres.
Le résultat
Aucune écriture individuelle n’est fausse. La rubrique contient une valeur. Le système semble fonctionner. Mais la valeur qu’elle contient après une opération donnée ne peut pas être prédite sans retracer chaque chemin de code qui touche la rubrique — ce qui nécessite de lire l’ensemble du système. La rubrique est active, mais son comportement n’est rattaché à aucun endroit.
Pourquoi ça ne s’améliore pas
Chaque nouvelle fonctionnalité qui touche la rubrique ajoute un nouveau point de mise à jour. Corriger une lacune de validation dans un chemin la laisse ouverte dans tous les autres. La seule résolution est de consolider la propriété — un chemin, un seul point de mise à jour, tous les autres supprimés. Ce refactoring devient plus coûteux à chaque point de mise à jour ajouté et à chaque mois qui passe. Le système ne vous dira pas combien d’auteurs existent. Vous devez les trouver.
Défaillance 3 : Couplage implicite
Contrainte violée : Séparation des responsabilités
La violation
Le Script A appelle le Script B. Le Script B appelle le Script C. La chaîne a été construite progressivement, chaque lien ajouté quand il était nécessaire. Personne n’a conçu la chaîne dans son ensemble. Chaque script avait du sens dans son contexte.
Le Script B dépend silencieusement d’une variable globale définie par le Script A. Le Script C dépend silencieusement d’un ensemble trouvé filtré par le Script B. Ces dépendances sont réelles et structurantes, mais elles ne sont exprimées nulle part — ni dans les scripts, ni dans la documentation. Elles n’existent que dans l’ordre d’exécution et dans la mémoire de celui qui a construit la chaîne.
Le mécanisme
La chaîne fonctionne — en tant que chaîne. Retirez un lien, réordonnez les appels, ou invoquez le Script C depuis un point d’entrée différent, et les préconditions cachées s’effondrent. Le Script C opère sur le mauvais ensemble trouvé. Le comportement est erroné d’une façon que rien dans le code du Script C n’explique, parce que la dépendance violée se trouve dans un script entièrement différent.
La règle absolue : un système dont les composants dépendent les uns des autres via des hypothèses non déclarées ne peut pas être modifié ou étendu en toute sécurité. Chaque modification nécessite de reconstruire un graphe de dépendances qui n’a jamais été écrit. Cette reconstruction est toujours incomplète parce que le système ne contient pas les informations nécessaires pour la réaliser.
Le résultat
La défaillance apparaît quand quelqu’un étend le système — un nouveau point d’entrée, un composant réutilisé, un flux réorganisé. Le développeur qui déclenche la défaillance ne l’a pas introduite. Elle a été encodée dans la chaîne dès le moment où la dépendance implicite a été créée. Elle allait toujours apparaître. La seule question était quand.
Pourquoi ça ne s’améliore pas
Le couplage implicite se dissimule lui-même. Les chaînes qui fonctionnent continuent de fonctionner, sans donner aucun signal qu’elles sont fragiles. Le coût du changement augmente à chaque lien ajouté. La réutilisation devient risquée. Le refactoring nécessite une reconstruction complète du système parce qu’il n’y a pas d’autre moyen de découvrir ce qui dépend de quoi. Le prochain développeur qui hérite du système repart de zéro.
Défaillance 4 : État ambiant
Contrainte violée : Gestion explicite de l’état
La violation
Les variables globales s’accumulent à travers une session. Un identifiant utilisateur défini à la connexion. Un indicateur de contexte défini au début d’une opération en plusieurs étapes. Une référence d’enregistrement transportée entre scripts sans paramètre. Chaque variable globale a été ajoutée parce que c’était pratique — disponible partout, ne nécessitant aucun passage explicite.
Le mécanisme
Le comportement d’un script dépend maintenant de l’état global au moment où il s’exécute. Cet état a été défini par celui qui l’a appelé, et par celui qui a appelé celui-là, en remontant jusqu’à l’action utilisateur originale. Le script ne déclare pas cette dépendance. Il ne la vérifie pas. Il lit la variable globale et continue.
Dans une session interactive avec une chaîne d’appels prévisible, la variable globale contient la bonne valeur. En exécution planifiée, le contexte de session est différent — la variable peut être vide, périmée, ou écrasée par un processus concurrent. Dans un nouveau flux ajouté plus tard, la variable globale est définie dans un ordre différent. Le script s’exécute et produit un résultat qui ne peut pas être expliqué à partir de son code.
La règle absolue : un script qui lit un état qu’il n’a pas reçu en entrée produira des résultats incohérents selon les contextes d’exécution. On ne peut pas déboguer ce qu’on ne peut pas reproduire.
Le résultat
La défaillance est intermittente. Les mêmes étapes produisent des résultats différents selon ce qui s’est passé plus tôt dans la session — ou dans une session concurrente côté serveur. Le bug n’existe dans aucun script en particulier, mais dans la dépendance invisible entre scripts. Il ne peut pas être trouvé en lisant le code. Il ne peut être trouvé qu’en reconstituant l’état exact qui entourait l’exécution — une information qui n’existe plus au moment de l’analyse.
Pourquoi ça ne s’améliore pas
Chaque variable globale ajoutée augmente la surface d’état invisible. Chaque script qui lit une variable globale sans la vérifier ajoute un nouveau point de défaillance. La résolution nécessite d’identifier chaque variable globale qui porte un sens comportemental, de retracer chaque script qui la lit, et de remplacer chaque dépendance par un passage explicite. Cet audit devient plus coûteux à chaque variable ajoutée et devient effectivement impossible une fois que le système atteint une complexité suffisante. L’état s’accumule. Les défaillances se multiplient. Ni l’un ni l’autre ne sont traçables à une cause unique.
Défaillance 5 : Logique dans l’interface
Contrainte violée : Séparation des couches
La violation
Un modèle a un bouton. Le bouton déclenche un script. Le script impose une règle métier : si le total de la facture dépasse un seuil, exiger une approbation avant que l’enregistrement puisse être sauvegardé. Le développeur le teste. Ça fonctionne.
La règle est maintenant imposée via un seul chemin d’exécution — le bouton. Chaque autre chemin pouvant écrire dans cet enregistrement en est exempté par défaut.
Le mécanisme
Dans FileMaker, un enregistrement peut être écrit depuis de nombreux endroits : un modèle interactif, un script d’importation, un script planifié côté serveur, ou un appel PSOS. Aucun de ces chemins ne passe par le bouton. Aucun ne déclenche la règle.
La règle n’est pas au mauvais endroit par préférence stylistique. Elle est au mauvais endroit parce que la seule chose qui l’exécute est une interaction avec l’interface. Tout le reste — chaque chemin d’exécution non interactif, chaque intégration future, chaque automatisation ajoutée plus tard — le contourne structurellement. Pas accidentellement. Par conception.
La règle absolue : une règle métier qui vit dans la couche interface n’est imposée que quand l’interface est impliquée. Chaque chemin d’exécution qui contourne l’interface contourne la règle. Dans un système avec plusieurs chemins d’exécution, c’est une garantie d’application incohérente des règles.
Le résultat
Des données invalides entrent dans le système par tout chemin qui ne passe pas par le bouton. La règle existe. Elle est exécutée. Mais ce que le système garantit réellement n’est pas « les factures au-dessus du seuil requièrent une approbation » — c’est « les factures au-dessus du seuil requièrent une approbation quand elles sont soumises via ce modèle précis ». Il s’agit de garanties différentes. La seconde sera violée dès que le système sera utilisé d’une façon qui n’avait pas été anticipée quand le bouton a été construit.
Pourquoi ça ne s’améliore pas
Chaque nouveau chemin d’exécution ajouté au système — une importation, un point d’entrée d’API, une automatisation côté serveur — est un autre chemin qui contourne la règle. Corriger ça en ajoutant la règle à chaque chemin produit N copies de la même logique, chacune étant un point de divergence potentiel. La seule solution viable est de déplacer la règle vers une couche par laquelle tous les chemins passent. Plus elle reste dans l’interface, plus il existe de chemins qui la contournent, et plus la consolidation devient coûteuse. L’écart d’application s’élargit automatiquement au fil de la croissance du système.
La structure de l’échec inévitable
Cinq défaillances. Cinq contraintes violées. Une même structure.
Chaque défaillance est invisible au moment de sa création. Le contexte implicite est sûr quand il n’y a qu’un seul point d’appel. La mutation non contrôlée est sûre quand les valeurs se trouvent être cohérentes. Le couplage est sûr quand tout le monde connaît le fonctionnement de la chaîne. L’état ambiant est sûr quand il est toujours défini avant d’être consommé. La règle dans l’interface est sûre quand il n’y a qu’un seul chemin d’exécution.
Aucune de ces conditions ne tient quand un système grandit.
Les points d’appel se multiplient. Les chemins d’exécution divergent. Les développeurs changent. La mémoire s’efface progressivement. Les hypothèses implicites qui étaient valides dans un système plus petit et plus jeune cessent de tenir — non pas parce que quelqu’un a fait une erreur, mais parce que les conditions qui les rendaient temporairement sûres n’existent plus.
Rien n’explose. Le système devient imprévisible.
L’imprévisibilité a une condition terminale précise. On cesse de pouvoir modifier le système avec confiance. Chaque modification nécessite de reconstruire les dépendances qui n’ont jamais été documentées. Les tests deviennent manuels et exhaustifs parce que le système ne peut pas indiquer ce qu’un changement affecte. Finalement, chaque changement coûte plus que le précédent, les nouvelles fonctionnalités introduisent des régressions ailleurs dans le système, et le développement ralentit jusqu’à un rythme qui ne justifie plus le risque de toucher à quoi que ce soit.
À ce stade, le système tourne. Il n’est plus développé. Il est maintenu en place, autour de sa propre fragilité, par des gens qui ont appris quelles parties ne pas toucher.
C’est ça, l’effondrement. Pas un événement. Une condition dont la sortie nécessite de repartir de zéro.
Ce que les défaillances ont en commun
Les cinq défaillances ne sont pas des problèmes indépendants. Elles sont la même condition structurelle, exprimée sous cinq formes différentes :
- le comportement dépend d’un contexte qu’il ne déclare pas
- l’état existe en dehors des limites de l’exécution
- la mutation se produit sans propriétaire unique
- la logique est liée à des points d’entrée qui ne sont pas universels
- les chemins d’exécution ne sont pas contrôlés et ne passent pas par un point d’entrée unique
Chaque violation introduit une dépendance que le système ne peut pas voir. Chaque dépendance réduit la capacité du système à être raisonné. Chaque étape rapproche le système d’un état où le comportement ne peut plus être prédit à partir du code seul.
Ce que les contraintes éliminent
Les contraintes établies dans l’article précédent n’améliorent pas ce modèle. Elles l’excluent.
- Si les contrats sont explicites, le contexte implicite ne peut pas exister.
- Si l’état est explicite, l’état ambiant ne peut pas s’accumuler.
- Si la mutation est contrôlée, les écritures distribuées ne peuvent pas se produire.
- Si les couches sont séparées, la logique dans l’interface ne peut pas imposer de règles.
- Si l’exécution est déterministe, les chaînes d’appels ne peuvent pas altérer le comportement.
Chaque contrainte supprime une classe de comportements possibles. Ensemble, elles suppriment les conditions qui rendent les défaillances ci-dessus possibles.
Ce qui reste
Ce qui reste n’est pas une amélioration du modèle par défaut. C’est ce qui subsiste une fois les structures invalides éliminées.
Un système qui satisfait les cinq contraintes simultanément doit avoir :
- des comportements isolés dans des unités qui déclarent leurs entrées et leurs sorties
- des chemins d’exécution explicites et contrôlés
- un seul point de propriété pour la mutation
- aucune dépendance envers un contexte ambiant ou un état hérité
- aucune logique liée à des points d’entrée spécifiques
Ce n’est pas une préférence de conception. C’est la seule forme compatible avec l’ensemble des contraintes.
Ce que cela ne définit pas
Les contraintes ne prescrivent pas une implémentation. Elles éliminent des classes entières de systèmes.
Elles définissent une structure requise que toute implémentation valide doit respecter.
Il existe plusieurs façons d’imposer cette structure. Des modèles d’exécution différents, des stratégies de routage différentes, des frontières physiques différentes — tous valides, à condition que les exigences structurelles soient respectées.
Le prochain article ne propose pas une nouvelle idée. Il décrit une façon d’imposer cette structure.
Ressources complémentaires
-
Référence d'article (PDF)
Fiche de référence pour cet article.
Cet article reflète ma propre expérience et ma perspective. De nombreuses idées ici ont été façonnées par des contributions venues de forums, de communautés, de blogues et de livres blancs. Des outils d'IA ont été utilisés comme assistant à la rédaction pour structurer et affiner le contenu.
Discussion
La discussion est ouverte pour cet article. Les questions, objections, corrections et contre-exemples sont bienvenus.
Envoyer un commentaire privé