Ce que des décennies de génie logiciel peuvent apprendre à un développeur FileMaker
Pourquoi certains systèmes FileMaker s’effondrent sous le poids de leur propre complexité — et ce que le génie logiciel a déjà résolu — est ce dont cet article traite.
Quand un système FileMaker devient difficile à maintenir, les suspects habituels sont :
- le projet a trop grossi
- il y a trop de tables
- trop de scripts
- pas assez de temps pour bien faire
Ce diagnostic est rarement le bon.
Ce ne sont pas des causes. Ce sont des symptômes. Et ils désignent le mauvais problème.
Les systèmes qui s’effondrent sous la complexité ne s’effondrent pas parce qu’ils sont larges. Ils s’effondrent parce qu’ils ont été construits sans les règles qui rendent les grands systèmes gérables. Ces règles ne sont pas nouvelles. Elles ne sont pas spécifiques à FileMaker. Elles ont été documentées, affinées et enseignées dans le domaine du génie logiciel depuis des décennies.
FileMaker ne les enseigne pas. Il ne les impose pas. Il n’y fait même pas allusion.
Ce n’est pas la faute de la plateforme. C’est le problème du développeur.
Nouveaux concepts introduits
- Méthode
- Script
- Séparation des responsabilités
- Contrats explicites
- Déterminisme
- Couplage et cohésion
- Gestion explicite de l’état
Ce ne sont pas des problèmes FileMaker
Les schémas d’échec qui apparaissent dans les systèmes FileMaker vieillissants ont des noms en génie logiciel. Des causes. Des conséquences. Et des solutions connues.
Le vocabulaire a été développé dans des contextes qui n’ont rien à voir avec FileMaker — langages compilés, systèmes distribués, grandes bases de code maintenues par des équipes pendant des années. Mais les problèmes sous-jacents sont les mêmes.
Un script qui suppose qu’il s’exécute sur un modèle précis est un problème de dépendance au contexte. Une logique écrite à l’intérieur d’un composant d’interface est un problème de séparation des couches. Un ensemble trouvé supposé contenir les bons enregistrements sans être explicitement contrôlé est un problème de gestion d’état. Une variable globale qui transporte du sens à travers des chemins d’exécution est un problème d’état ambiant.
Ce sont exactement les choses qui font hésiter avant de toucher au code qu’on a écrit il y a deux ans.
Le génie logiciel a résolu ces problèmes pour quiconque construit des systèmes qui doivent survivre au temps. Ce qui suit est l’application de ces solutions à FileMaker.
Les contraintes
Ce qui suit ne sont pas des observations sur ce qui rend les systèmes élégants. Ce sont des exigences structurelles — les conditions qu’un système doit satisfaire pour rester correct dans le temps. Elles existent pour préserver un comportement prévisible à mesure que le système évolue, le rendant ainsi compréhensible et modifiable en toute confiance. Chacune élimine une classe de comportement implicite. Ensemble, elles ferment les degrés de liberté que la plateforme laisse ouverts par défaut.
C’est intentionnel. Les contraintes ne sont pas des limitations. Elles sont le mécanisme par lequel un système devient prévisible.
Avant d’aborder les contraintes, une distinction de vocabulaire s’impose — elle traversera toute la série.
Un script est un détail d’implémentation : une séquence d’étapes que FileMaker exécute. Une méthode est un contrat : une unité de comportement avec une entrée déclarée, une sortie déclarée, et aucune dépendance envers le contexte qui l’entoure. La méthode n’est pas une façon d’organiser les scripts. C’est la seule façon dont la logique est autorisée à s’exécuter à l’intérieur d’un système qui satisfait ces contraintes. Un script qui ne satisfait pas ce contrat n’appartient pas au modèle — il en est un résidu.
Séparation des responsabilités
On a besoin de modifier quelque chose dans l’interface, et on hésite — pas parce que le changement est complexe, mais parce qu’on ne sait pas quelle logique en dépend silencieusement.
C’est ce qu’on appelle un problème de séparation des responsabilités.
Edsger W. Dijkstra l’a nommé en 1974 : la capacité de considérer un aspect d’un problème isolément, en mettant de côté tous les autres, est ce qui rend les systèmes complexes gérables.1 Ousterhout le formule en termes de ce qu’un module doit cacher : une bonne conception enfouit la complexité derrière une interface simple, de sorte que le reste du système n’ait jamais à la gérer.2
En pratique, ça se traduit par une règle simple :
- L’interface gère l’interaction et la présentation.
- La couche logique gère les règles métier.
- La couche données gère le stockage.
Quand ces responsabilités se mélangent — quand un modèle écrit directement dans une table, quand un script prend des décisions d’affichage, quand des règles métier vivent à l’intérieur d’un portail — le système devient ingérable. On ne peut pas modifier l’interface sans se demander quelle logique pourrait casser. On ne peut pas refactoriser la logique sans savoir de quoi l’interface dépend silencieusement.
Quand un modèle FileMaker accède directement à une table, rien n’est caché. Chaque appelant est exposé à tout.
FileMaker rend facile le fait de tout mélanger. Un script peut, en quatre étapes, écrire directement dans une rubrique, appeler un sous-script, naviguer vers un modèle et envoyer un courriel. Sans aucune indirection. Sans aucune séparation. C’est pratique. C’est aussi le début d’un système qu’on aura peur de toucher.
La séparation n’est pas une question de discipline. C’est une question de contraindre ce qui peut appeler quoi.
L’objectif est un système où l’interface ne sait rien de la façon dont les données sont écrites, et où la couche logique ne sait rien de la façon dont elles sont affichées. Chaque couche remplaçable sans perturber les autres.
La séparation établit la frontière. Mais une frontière sans point de passage défini n’est qu’un mur troué.
Contrats explicites
On a exécuté un script et obtenu un résultat inattendu — pas parce qu’il était incorrect, mais parce que le contexte dans lequel il s’est exécuté n’était pas ce qu’il supposait.
Un système fiable ne dépend pas de suppositions.
Bertrand Meyer a formalisé ça sous le nom de programmation par contrat : chaque composant logiciel définit les préconditions qu’il exige de son appelant, les postconditions qu’il garantit au retour, et les invariants qu’il maintient tout au long de son exécution.3 Le contrat n’est pas de la documentation — c’est la seule interface autorisée entre les parties du système.
Quand un script est exécuté sans contrat, on parie que le contexte est correct. Que l’enregistrement courant est le bon. Que l’ensemble trouvé n’a pas été modifié en amont. Qu’une variable globale définie trois étapes plus tôt dans la chaîne d’appels contient encore la valeur dont on a besoin — et que rien entre-temps ne l’a écrasée.
Parfois ce pari est gagnant. Pour un temps.
Un contrat élimine le pari. On définit l’entrée. On définit la sortie. Le comportement est autonome. Peu importe d’où on l’appelle, parce qu’il ne dépend pas de l’endroit où il est appelé.
C’est la condition dans laquelle une méthode peut être fiable de façon isolée.
L’objectif est un système où chaque méthode peut être appelée depuis n’importe où — parce qu’elle porte avec elle tout ce dont elle a besoin, et déclare tout ce qu’elle retourne.
Un contrat définit le point de passage. Mais un contrat qui dépend de conditions ambiantes n’est pas un contrat — c’est une négociation. C’est là que le déterminisme devient une exigence, pas une préférence.
Déterminisme
On n’arrive pas à reproduire un bug de façon fiable. Mêmes étapes, résultats différents. Le problème n’est pas dans la logique — il est dans l’état qui l’entourait quand elle s’est exécutée.
Un système est déterministe si les mêmes entrées produisent toujours les mêmes sorties — quel que soit l’endroit depuis lequel l’exécution a été déclenchée.
Ça semble évident. C’est remarquablement rare dans l’architecture FileMaker par défaut.
Si le comportement dépend du modèle actif — il n’est pas déterministe. S’il dépend de l’ensemble trouvé courant — pas déterministe. S’il dépend de quel enregistrement se trouve être actif — pas déterministe. S’il repose sur une variable globale définie quelque part plus tôt dans une chaîne d’appels — pas déterministe. Et si le résultat change selon le chemin d’exécution emprunté pour l’atteindre — pas déterministe non plus.
Un comportement non déterministe ne plante pas bruyamment. Il plante conditionnellement. Il fonctionne dans le contexte qu’on a testé, et casse dans le contexte qu’on n’a pas pensé à tester.
Le déterminisme exige deux choses : que les entrées soient explicites, et que le chemin d’exécution soit fixe. L’une sans l’autre ne suffit pas.
Le déterminisme est ce qui rend un système testable. Et un système qu’on ne peut pas tester de façon fiable est un système qu’on ne peut pas modifier avec confiance.
L’objectif est un système où le comportement dépend uniquement de ce qu’on lui passe — pas de l’endroit où on se trouve, ni du chemin emprunté pour l’appeler.
Atteindre le déterminisme exige d’éliminer les dépendances cachées qui corrompent les entrées avant même qu’elles soient nommées. C’est le problème du couplage.
Couplage et cohésion
On modifie un script et quelque chose casse ailleurs — dans une partie du système qui semblait sans rapport.
La cohésion mesure à quel point une unité de code fait une seule chose. Le couplage mesure à quel point elle dépend de tout le reste.
On veut une cohésion élevée. On veut un couplage faible.
Constantine et Yourdon ont défini ces concepts dans Structured Design (1979) et les ont établis comme les critères principaux d’évaluation de la qualité des modules.4 En pratique : les comportements qui vont ensemble doivent être regroupés, et les composants d’un système doivent dépendre les uns des autres le moins possible, et uniquement via des interfaces explicites et définies.
La règle des dépendances de Martin rend ça concret : les dépendances du code source doivent pointer dans une seule direction — vers l’intérieur, vers la politique de haut niveau, jamais vers l’extérieur vers les détails d’implémentation.5 Quand un composant d’interface accède directement à la couche données, cette règle est violée. La dépendance va dans le mauvais sens, et le système devient impossible à modifier d’un côté sans toucher à l’autre.
Dans FileMaker, le couplage prend trois formes, souvent simultanément. Le couplage de données : le comportement dépend d’une valeur qui n’est pas déclarée comme entrée. Le couplage d’état : le comportement dépend d’une condition ambiante — un enregistrement actif, un ensemble trouvé, une variable globale. Le couplage d’exécution : le résultat dépend du chemin emprunté pour l’atteindre — de qui l’a appelé, depuis quel contexte, dans quel ordre.
C’est le couplage d’exécution qui rend les bugs impossibles à reproduire. On a emprunté le même chemin en apparence, mais pas exactement le même chemin — et le système était dans un état différent à l’arrivée.
Un couplage fort ne casse pas les systèmes immédiatement. Il les rend progressivement plus difficiles à modifier. Chaque modification nécessite une reconstruction mentale du graphe de dépendances — que personne n’a documenté, parce que personne ne l’a planifié.
L’objectif est un système où les dépendances sont visibles, déclarées et intentionnelles — pas héritées silencieusement de l’ordre d’exécution ou du contexte du modèle.
Les dépendances invisibles ne voyagent pas dans le vide. Elles transitent par l’état — des valeurs accumulées, transportées et consommées sans jamais être déclarées. C’est la dernière condition à éliminer, et celle qui rend toutes les autres possibles à violer.
Gestion explicite de l’état
« Le contexte est roi. » Quiconque a passé du temps dans la communauté FileMaker a lu cette phrase. C’est une reconnaissance du fait que presque tout dans la plateforme dépend de l’endroit où on se trouve, de quel enregistrement est actif, de ce que contient l’ensemble trouvé. La communauté traite le contexte comme une condition à gérer. Cette série le traite comme une condition à éliminer.
Ce que la communauté appelle « contexte », le génie logiciel l’appelle état implicite. Le contexte est une forme d’état implicite — pas sa totalité.
L’état, c’est toute information que le système transporte entre les opérations. Un ensemble trouvé est de l’état. L’enregistrement actif est de l’état. Une variable globale est de l’état. Le modèle courant est de l’état.
La distinction qui compte n’est pas entre « avec état » et « sans état ». C’est entre état implicite et état explicite.
Quand l’état est explicite — déclaré comme entrée, passé intentionnellement, possédé par la méthode qui le produit — on sait toujours dans quelle condition se trouve le système. Quand il est implicite — accumulé à travers l’exécution, défini comme effet de bord, hérité d’un contexte antérieur — on ne sait pas.
Le modèle par défaut de FileMaker est plein d’état implicite. Des variables globales qui accumulent du sens à travers les scripts. Des modèles qui portent un contexte dont les scripts dépendent. Des ensembles trouvés qui existent comme précondition silencieuse pour la logique qui s’exécute ensuite. Rien n’est déclaré. Tout est porteur de sens.
Les problèmes les plus fréquents sur les forums de la communauté Claris — mauvais enregistrement, ensemble trouvé inattendu, script se comportant différemment selon l’endroit d’où il a été appelé — remontent à cette même cause. Le problème de surface change. La condition structurelle en dessous, non.
La gestion explicite de l’état signifie : si une valeur est importante, elle est déclarée comme entrée. Rien d’important ne voyage de façon invisible.
L’objectif est un système où le contexte n’est pas roi — où la logique fonctionne indépendamment de l’endroit où elle s’exécute, de quel enregistrement se trouve être actif, ou de ce que contient l’ensemble trouvé. Sans contexte, à dessein.
Pourquoi ces principes comptent
Pas pour écrire du code plus propre. Pas pour satisfaire une norme d’ingénierie abstraite.
Ils comptent parce qu’ils préservent un comportement prévisible — le fondement qui rend un système compréhensible, maintenable et transmissible à un autre développeur.
Un système compréhensible est celui qu’on peut maintenir. Celui qu’on peut confier à un autre développeur sans un briefing de six heures. Celui où un changement à un endroit ne casse pas silencieusement quelque chose ailleurs. Celui vers lequel on peut revenir six mois plus tard et comprendre encore ce qu’il fait et pourquoi.
Un système incompréhensible a une trajectoire prévisible. Chaque changement prend plus de temps que le précédent. Les bugs se multiplient parce que les corrections introduisent de nouvelles surfaces. Finalement, quelqu’un décide qu’il est plus facile de tout réécrire que de continuer. Et le cycle recommence. Netscape a vécu exactement cette trajectoire — et ne s’en est jamais remis.6
Ce n’est pas une hypothèse. C’est une trajectoire familière pour les solutions FileMaker auxquelles ces règles n’ont jamais été appliquées.
Ce que ces contraintes imposent
Ces contraintes ne décrivent pas une préférence. Elles excluent des conditions.
Le contexte implicite doit être éliminé. L’état ambiant doit être éliminé. La mutation pilotée par l’interface doit être éliminée. Les chaînes d’appels sans contrat doivent être éliminées. Les chemins d’exécution non contrôlés doivent être éliminés.
Un système qui satisfait ces contraintes a une forme précise. Les comportements sont exprimés via des méthodes avec des entrées et sorties déclarées. L’exécution ne passe que par des chemins contrôlés. Une couche logique possède toutes les règles métier et fonctionne indépendamment de l’interface. Les écritures n’ont lieu que via cette couche. L’état est passé comme entrée — jamais hérité.
Ce n’est pas une option parmi d’autres. C’est ce vers quoi les contraintes convergent. Tout système qui les applique de façon cohérente sera forcé vers cette structure — non pas parce qu’il a été conçu autour d’une philosophie, mais parce qu’il n’avait nulle part ailleurs où aller.
FileMaker n’impose rien de tout cela. La plateforme autorise chacune des conditions listées ci-dessus. Ce que les contraintes établissent, c’est ce qui doit être vrai — pas ce que la plateforme garantit. La distance entre ces deux choses est précisément ce que la suite de cette série traite.
Chaque défaillance dans un système FileMaker est un principe absent. Le prochain article les relie — une à une — à la contrainte qu’elles violent.
Ressources complémentaires
-
Référence d'article (PDF)
Fiche de référence pour cet article.
Références
-
Edsger W. Dijkstra, On the role of scientific thought (1974). L’essai introduit le terme « separation of concerns » et soutient que la capacité de se concentrer sur un seul aspect à la fois — en mettant tous les autres de côté — est l’outil intellectuel fondamental pour gérer la complexité dans la conception de systèmes. https://www.cs.utexas.edu/~EWD/transcriptions/EWD04xx/EWD447.html ↩
-
John Ousterhout, A Philosophy of Software Design, 2e éd. (2021), chapitre 4 : Modules Should Be Deep. Ousterhout soutient qu’un bon module cache autant de complexité que possible derrière une interface simple — la profondeur d’un module est une mesure directe de la complexité du système qu’il absorbe pour le compte de ses appelants. ↩
-
Bertrand Meyer, Object-Oriented Software Construction, 2e éd. (1997), chapitre 11 : Design by Contract. Meyer définit la métaphore du contrat pour les composants logiciels : les préconditions que l’appelant doit satisfaire, les postconditions que le composant garantit au retour, et les invariants maintenus tout au long. Le chapitre établit les contrats comme outil de spécification, pas comme convention de documentation. https://bertrandmeyer.com/wp-content/upLoads/OOSC2.pdf ↩
-
Larry L. Constantine et Edward Yourdon, Structured Design: Fundamentals of a Discipline of Computer Program and Systems Design (1979), chapitres 6–7. Le chapitre 6 (Coupling) définit les types de couplage, du plus destructeur — le couplage de contenu — au plus gérable — le couplage de données. Le chapitre 7 (Cohesion) définit la cohésion et son spectre, de coïncidentelle à fonctionnelle. https://vtda.org/books/Computing/Programming/StructuredDesign_EdwardYourdonLarryConstantine.pdf ↩
-
Robert C. Martin, Clean Architecture: A Craftsman’s Guide to Software Structure and Design (2017), chapitre 5 : The Dependency Rule. Martin soutient que dans un système bien structuré, toutes les dépendances du code source doivent pointer vers l’intérieur, vers la politique de haut niveau. Les couches externes — interface, bases de données, frameworks — dépendent des couches internes. Les couches internes ne connaissent rien des couches externes. https://www.oreilly.com/library/view/clean-architecture-a/9780134494272/ ↩
-
Joel Spolsky, « Things You Should Never Do, Part I », Joel on Software, 6 avril 2000. Spolsky décrit la décision de Netscape de réécrire son navigateur de zéro — une des erreurs stratégiques les plus coûteuses de l’histoire du logiciel. https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/ ↩
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é