Votre logique métier Delphi est-elle prisonnière des formulaires ?
Repérez et séparez la logique métier Delphi cachée dans les formulaires VCL, contrôles orientés données, événements de dataset et transactions.

Les formulaires Delphi sont souvent des spécifications exécutables recouvertes d'une interface utilisateur. Un clic sur un bouton calcule les remises, un TDBEdit.OnExit normalise un code de compte, BeforePost refuse une période clôturée et un événement de grille modifie le résultat de la requête suivante. Traitez ces gestionnaires comme du simple code de présentation et le remplacement semblera terminé tout en changeant discrètement le fonctionnement du métier.
Un portage direct aggrave ce risque. Recréer chaque formulaire dans un navigateur ou un autre framework de bureau conserve les anciennes frontières, puis vous oblige à redécouvrir leurs dépendances cachées dans un modèle événementiel moins tolérant. La voie la plus sûre consiste à identifier le comportement observable, à extraire les règles derrière des interfaces explicites, puis à faire appeler les mêmes opérations conceptuelles par l'ancienne application VCL et le nouveau système pendant la transition.
Comment la logique métier a-t-elle fini dans les formulaires Delphi ?
La logique métier a fini dans les formulaires Delphi parce que la VCL raccourcissait énormément le chemin vers un logiciel fonctionnel. On dépose un dataset, une TDataSource, quelques contrôles orientés données et un bouton sur un formulaire, puis on place la décision près de l'événement qui en a besoin. Ce choix était raisonnable lorsqu'un développeur possédait l'application et que les utilisateurs se trouvaient près de la base. Des années de modifications ont transformé cette proximité en architecture.
La classe du formulaire a ensuite accumulé plusieurs responsabilités. Elle lit l'état des contrôles, interprète l'intention de l'utilisateur, applique les règles, démarre les transactions, met les datasets à jour, met en forme les messages et décide quel écran ouvrir ensuite. Le fichier .dfm ajoute une couche, car les valeurs de propriétés et le câblage des composants modifient le comportement à l'exécution sans apparaître dans la méthode Pascal que vous lisez.
Voilà pourquoi le nombre de lignes sous-estime la migration. Une unité de 250 lignes peut dépendre de dizaines de propriétés héritées, de champs persistants, d'actions, de modules de données partagés et d'affectations d'événements conservées dans le DFM. Même un TDBEdit apparemment vide écrit dans le tampon d'un dataset via TDataSource. Son comportement dépend de l'état du dataset, des événements de champ, des masques de saisie et de ce que BeforePost fera plus tard.
Ne classez pas tout code événementiel comme logique métier. Afficher une boîte de dialogue, déplacer le focus et redimensionner des colonnes relèvent de la présentation. Décider qu'une facture ne peut pas être comptabilisée après la clôture d'une période est une règle. Transformer un niveau client en remise est un calcul. Appeler une procédure stockée peut relever de l'orchestration applicative ou de l'accès aux données, selon le contrat qu'elle expose. La distinction compte, car chaque type de code a besoin d'une destination différente.
Un test utile consiste à retirer mentalement le formulaire. Si la décision doit rester vraie pour un import, une requête API ou un traitement par lots, c'est de la logique métier. Si le comportement aide seulement une personne à utiliser cet écran précis, il peut rester dans l'interface. S'il coordonne un cas d'usage sans contenir lui-même de règle, il appartient à un service applicatif.
Le fichier du formulaire n'est que la moitié du programme
Vous avez besoin d'un inventaire du comportement exécutable avant toute extraction. Cet inventaire doit couvrir le Pascal, les ressources DFM, les formulaires hérités et les objets de base de données. Une recherche limitée aux gestionnaires de clic manque les modifications automatiques et les événements de cycle de vie. La seule lecture du DFM manque les gestionnaires affectés à l'exécution.
Commencez par une carte mécanique. Pour chaque formulaire, relevez tous les événements de composants et de datasets, les actions, les temporisateurs, les gestionnaires de messages et les appels qui passent dans une autre unité. Incluez OnCreate, OnShow, OnCloseQuery, OnChange, OnExit, OnClick, OnExecute, BeforeEdit, BeforePost, AfterPost, OnCalcFields et les gestionnaires d'exceptions. Recherchez des affectations comme Button.OnClick :=, car certaines applications recâblent leur comportement après la construction.
Ajoutez ensuite les chemins implicites. Notez quels contrôles pointent vers chaque TDataSource, quel dataset chaque source expose, si AutoEdit est activé et quels objets TField persistants possèdent des événements de validation ou de changement. La documentation de TDataSource d'Embarcadero décrit le composant comme le conduit entre un dataset et les contrôles orientés données. Ce mot discret, conduit, sert d'avertissement : une frappe peut franchir la frontière de l'interface avant l'exécution du moindre bouton Enregistrer.
Construisez une table avec une ligne par comportement observé, et non une ligne par méthode. Ces colonnes révèlent la plupart des pièges :
Trigger Reads Writes Rule owner
btnPostClick invoice fields, role invoice status posting policy
AmountFieldValidate amount, currency record buffer money rule
CustomerDataChange current customer filter params query orchestration
La colonne Evidence impose la précision. Le nom d'un gestionnaire ne constitue pas une preuve. Capturez les valeurs d'entrée, l'état du dataset, les appels SQL, les lignes renvoyées, les messages et les valeurs finalement stockées. Si un gestionnaire dépend de l'ordre des événements VCL, consignez cet ordre. Il fait partie du comportement tant que vous n'avez pas démontré que les utilisateurs et les intégrations ne peuvent pas l'observer.
Les formulaires hérités méritent un passage séparé. Un DFM enfant peut remplacer une propriété tout en héritant d'un événement d'un ancêtre situé dans un autre répertoire du projet. L'unité enfant peut sembler inoffensive alors que le formulaire de base ouvre des datasets ou modifie les droits dans OnShow. Dépliez la chaîne d'héritage et relevez la configuration effective des composants dans l'application compilée, pas seulement le texte d'un fichier.
Les actions cachent elles aussi la réutilisation. Un même TAction.OnExecute peut partir d'un menu, d'un bouton de barre d'outils et d'un raccourci, tandis que OnUpdate décide de sa disponibilité à partir d'un état global. Si le nouveau client copie seulement le bouton visible, les utilisateurs au clavier peuvent perdre un chemin et l'autorisation peut se réduire à un indicateur désactivé purement visuel. Faites du droit d'exécution une règle à la frontière de la commande et de l'état activé une vue de cette décision.
Extrayez les décisions avant de déplacer les contrôles
Extrayez d'abord les décisions et calculs purs, car ils offrent des points de séparation stables sans perturber l'écran. Laissez le gestionnaire d'événement en place, mais réduisez-le à la collecte des entrées, à l'appel d'une règle et à l'affichage du résultat. L'application en production reste utilisable tandis que la règle devient appelable sans formulaire.
Supposons qu'un formulaire de commande prenne une décision de crédit dans btnApproveClick. Le gestionnaire d'origine lit des champs, vérifie un indicateur client, compare les totaux, met des contrôles à jour, poste le dataset et affiche un message. Séparez la décision de ces effets :
type
TApprovalInput = record
OrderTotal: Currency;
CreditLimit: Currency;
AccountOnHold: Boolean;
end;
TApprovalDecision = record
Allowed: Boolean;
ReasonCode: string;
end;
function DecideApproval(const Input: TApprovalInput): TApprovalDecision;
begin
if Input.AccountOnHold then
Exit(TApprovalDecision.Create(False, 'ACCOUNT_HOLD'));
if Input.OrderTotal > Input.CreditLimit then
Exit(TApprovalDecision.Create(False, 'LIMIT_EXCEEDED'));
Result := TApprovalDecision.Create(True, 'APPROVED');
end;
La syntaxe exacte du record devra peut-être être adaptée à la version de Delphi du parc. C'est la conception qui compte : la fonction reçoit des valeurs, renvoie une décision et ne connaît ni TEdit, ni TField, ni résultat modal, ni transaction. Un test unitaire peut la couvrir, un import par lots peut appeler une opération équivalente et un service cible peut appliquer le même contrat.
Ne déplacez pas l'ancien gestionnaire intact dans une classe nommée TOrderService. Une méthode qui reçoit un formulaire ou traverse un module de données global reste couplée à l'interface. Passer vingt contrôles en paramètres ne fait que cacher ce couplage dans la signature. Définissez les entrées en termes métier et fournissez assez de contexte pour rendre la décision déterministe.
Résistez aussi à l'extraction trop précoce d'utilitaires partagés. Deux gestionnaires qui calculent une taxe peuvent différer parce que l'un traite les avoirs et l'autre les factures antérieures à un changement de règle. Caractérisez d'abord les deux comportements. Ne les fusionnez que lorsque les preuves montrent que la différence est accidentelle.
Les contrôles orientés données créent un chemin d'écriture invisible
Les contrôles orientés données exigent un modèle d'édition explicite dans le remplacement, car ils combinent affichage, navigation, mise en tampon, validation et persistance. Une saisie de navigateur liée à du JSON n'équivaut pas à un TDBEdit relié par TDataSource à un TDataSet actif.
Le premier comportement caché est l'entrée en mode édition. Embarcadero indique que TDataSource.AutoEdit vaut true par défaut et appelle la méthode Edit du dataset lorsqu'un utilisateur tente de modifier un contrôle lié. L'application d'origine peut donc verrouiller une ligne, marquer un enregistrement comme modifié ou activer les actions Post et Cancel dès la première frappe. Une nouvelle interface qui attend un enregistrement explicite a un autre modèle de concurrence, même si les champs semblent identiques.
Le deuxième comportement caché est la mise en tampon. Une valeur de champ affichée peut ne correspondre ni à la dernière valeur validée en base, ni à celle qu'un autre contrôle voit après un événement. Edit, Insert, Post, Cancel, les mises à jour en cache et les réglages du fournisseur déterminent le moment où les changements deviennent durables. Écrivez ces états sous forme d'une petite machine à états. Par exemple : View autorise la navigation ; Edit conserve un brouillon local ; Saving valide le brouillon et envoie une commande ; Conflict conserve le brouillon de l'utilisateur tout en affichant la version plus récente du serveur.
Le troisième comportement concerne l'emplacement de la validation. Un masque de saisie vérifie les caractères pendant la frappe. Le OnValidate d'un champ vérifie une valeur complète juste avant son entrée dans le tampon de l'enregistrement. BeforePost peut examiner l'enregistrement entier. Les contraintes de base de données agissent plus tard et peuvent couvrir des relations qu'aucun formulaire ne connaît. La documentation de TField.OnValidate d'Embarcadero précise qu'une affectation par programme contourne EditMask, tandis que OnValidate vérifie toujours le champ avant le post. C'est une bonne raison de déplacer les règles durables sous le niveau du widget.
Répartissez la validation en quatre catégories lors du déplacement :
- L'aide à la saisie, comme le formatage et le retour immédiat sur un caractère, reste dans le client.
- Les invariants de champ, comme un ensemble de codes admis, vivent dans l'opération métier et peuvent aussi tourner dans le client pour accélérer le retour.
- Les règles entre champs et les règles d'autorisation tournent sur le serveur ou à la frontière applicative qui possède la commande.
- Les contraintes référentielles et d'unicité restent imposées par Postgres, même si des contrôles plus aimables ont lieu plus tôt.
Dupliquer une règle pour obtenir un retour immédiat n'est acceptable que si une implémentation reste l'autorité. Le serveur doit refuser une commande invalide, quelle que soit la vérification du client. Sans cela, un import, une intégration ou un ancien client peut contourner le métier.
La liaison maître-détail ajoute un autre piège. Déplacer le curseur maître peut changer automatiquement des paramètres et actualiser les lignes de détail. Les utilisateurs peuvent y voir un seul espace de travail cohérent, mais l'implémentation repose sur la position d'un curseur plutôt que sur un identifiant explicite. Le remplacement doit demander les détails avec l'ID du maître, conserver la sélection dans le client et décider du sort d'un brouillon de détail non enregistré lorsque le maître change.
Les champs calculés et de recherche ont eux aussi besoin d'un propriétaire. Un champ calculé réservé à l'affichage appartient à une projection de requête ou à un modèle de vue. Si une autre règle le lit, déplacez le calcul dans l'opération métier et testez ses entrées. Un champ de recherche peut cacher un aller-retour vers la base ou une valeur de cache périmée ; notez donc si le comportement actuel voit les données en direct, celles de l'ouverture ou celles actualisées par un événement précis.
Les événements de dataset ne forment pas un modèle métier
Déplacer les datasets dans un TDataModule améliore l'organisation, mais ne sépare pas à lui seul la logique métier. Embarcadero présente TDataModule comme un emplacement central pour les composants non visuels et y autorise même les règles métier. Ce conseil désencombre les formulaires. Il ne crée ni frontières, ni entrées explicites, ni cas d'usage testables séparément.
Les événements de dataset mélangent souvent trois travaux. BeforePost peut valider un invariant, remplir des champs d'audit et lancer une autre requête. AfterScroll peut actualiser un dataset de détail et activer une action. OnCalcFields peut produire une valeur d'affichage qu'un autre gestionnaire considérera ensuite comme faisant autorité. Copier ces événements dans un repository ou un hook ORM recrée la même ambiguïté.
Classez chaque événement selon sa cause et sa garantie. Une opération métier doit s'exécuter parce qu'un appelant a demandé ApproveOrder, et non parce qu'un dataset quelconque vient de poster. Un repository doit stocker une commande approuvée, pas décider si l'approbation est permise. Un modèle de vue peut calculer du texte à afficher, mais les totaux persistés doivent venir de la règle qui possède les calculs monétaires.
Il existe un cas gênant : du code tiers peut appeler Post directement et compter sur BeforePost pour protéger l'enregistrement. Ne supprimez pas cette garde pendant l'extraction. Placez la règle dans une unité appelable, faites-la appeler par BeforePost et faites passer les nouvelles commandes par la même règle. Ne retirez l'ancien événement qu'après avoir établi par les traces que chaque chemin d'écriture emploie la nouvelle frontière.
Les modules de données globaux demandent une attention particulière. Un formulaire peut supposer que dmMain.qryCustomer est déjà ouvert, positionné sur le même client et inclus dans une transaction démarrée ailleurs. C'est un état mutable partagé. Transformez ces préconditions en identifiants et portées de transaction explicites. Passer un CustomerId est plus sûr que passer la ligne courante d'un dataset dont un autre événement peut déplacer le curseur.
Les transactions doivent suivre le cas d'usage
Les frontières de transaction doivent entourer une opération métier, et non un gestionnaire de bouton ou chaque post de dataset. L'ancien code peut commencer une transaction dans un événement, toucher plusieurs datasets au fil d'appels imbriqués, puis valider dans un autre événement. Découper cette séquence entre plusieurs requêtes HTTP peut laisser un travail partiel que l'application de bureau n'aurait jamais permis.
Tracez une opération réussie et chaque échec significatif. Relevez les instructions SQL, les appels de procédures stockées, les identifiants produits, le comportement des verrous et le point de commit ou de rollback. Nommez ensuite l'opération dans le vocabulaire métier. ClosePeriod peut mettre à jour l'enregistrement de période, créer des écritures de grand livre et refuser les brouillons en attente. Ces changements appartiennent à une seule commande applicative, même si la VCL les atteint par trois formulaires.
Définissez une requête et un résultat avant de choisir les détails du transport :
{
"operation": "ApproveOrder",
"order_id": 4812,
"expected_version": 17,
"actor_id": 204,
"decision_input": {
"order_total": "1250.00",
"currency": "EUR"
}
}
Un résultat réussi doit renvoyer la nouvelle version, le statut obtenu et des codes de motif stables. Un conflit doit renvoyer la version actuelle sans l'écraser en silence. La valeur décimale est une chaîne ici afin d'éviter qu'une décision monétaire ne devienne un accident de virgule flottante dans un client TypeScript.
N'exposez pas un endpoint générique UpdateOrder qui accepte toutes les colonnes. Il transporte l'abstraction du dataset sur le réseau et invite les appelants à créer des états que le formulaire empêchait. Des commandes comme ApproveOrder, ReleaseHold et ChangeDeliveryDate expriment l'intention et donnent à chaque transaction une frontière défendable.
Les procédures stockées compliquent la propriété, mais pas la méthode. Si une procédure contient des règles, caractérisez ses entrées, ses sorties, ses mutations et ses erreurs comme une partie du système actuel. Gardez-la d'abord derrière un adaptateur. Ne la réécrivez qu'une fois son comportement couvert par les tests de parité, surtout lorsque le code Delphi interprète les codes d'erreur d'un fournisseur ou dépend des effets de bord d'un trigger.
Les tests de caractérisation sont votre première spécification
Les tests de caractérisation doivent comparer les résultats observables de l'ancienne application avec ceux de l'opération extraite ou réécrite. Les tests unitaires tirés des exigences dont chacun se souvient seront utiles plus tard, mais ils ne peuvent pas révéler le comportement non documenté dont le métier dépend déjà.
Capturez un trafic de production représentatif lorsque les règles internes l'autorisent, puis retirez ou protégez les données sensibles. Transformez chaque opération en cas rejouable. Pour une application de bureau, le trafic ne se limite pas aux requêtes réseau. Consignez l'état initial de la base ou un fixture stable, les saisies de l'utilisateur, les permissions utiles, l'action invoquée, les messages ou codes de motif, les effets SQL et les lignes finales. Ajoutez l'annulation, le double clic, les enregistrements périmés, les valeurs nulles, les limites d'arrondi et les pannes de base de données.
Un fixture compact facilite la revue :
case: approve-order-over-limit
given:
order_id: 4812
order_total: "1250.00"
credit_limit: "1000.00"
account_on_hold: false
when: ApproveOrder
expect:
allowed: false
reason_code: LIMIT_EXCEEDED
order_status: DRAFT
committed_writes: 0
Exécutez le cas sur une instance contrôlée du chemin Delphi et sur le nouveau chemin. Comparez les résultats métier, l'état persisté et les effets de bord pertinents. Ne comparez pas des différences accessoires comme les horodatages produits, sauf si elles touchent un contrat. Normalisez les identifiants générés par la base lorsque l'identité elle-même n'a pas de sens.
Les tests par golden master ont des limites. L'ancien système peut se tromper, et préserver aveuglément chaque défaut le fige. Classez les écarts en parité attendue, correction approuvée ou différence non résolue. Une correction approuvée exige un responsable nommé et un test du comportement voulu. Sinon, les développeurs qualifieront les surprises de corrections et les relecteurs ne distingueront plus migration et reconception.
Le temps et les paramètres régionaux méritent des fixtures délibérés. Les applications Delphi convertissent souvent les dates selon les réglages du poste et arrondissent les monnaies à plusieurs endroits, à travers les types de base, les types de champ et les formats d'affichage. Incluez les fins de journée, les changements d'heure quand les horodatages comptent, les demi-unités décimales, les chaînes vides et les valeurs nulles. Vérifiez les valeurs stockées et les codes de motif plutôt que les libellés mis en forme, sauf si le libellé appartient lui-même à un document contractuel.
Testez aussi la suppression d'événements. Le code peut désactiver temporairement des contrôles, détacher un événement ou positionner un indicateur de chargement afin d'éviter des mises à jour récursives. La nouvelle opération ne doit pas reproduire ces artifices mécaniques, mais son résultat final doit correspondre. Un rejeu qui ne consigne que la bonne requête et la ligne finale peut manquer des effets de bord dupliqués au milieu de la séquence.
C'est ici que CodeHero emploie un banc de parité avec le trafic de production enregistré. Le principe utile ne dépend pas de notre plateforme : un remplacement se démontre par les faits, et la ressemblance des écrans est une preuve faible.
Un portage direct de l'interface conserve la frontière coûteuse
Le portage direct de l'interface est généralement la voie la plus chère, car il reconstruit les écrans avant de découvrir les opérations qu'ils recouvrent. Les équipes reproduisent les onglets, boîtes de dialogue modales, grilles et parcours, puis les raccordent à des endpoints CRUD génériques. Chaque règle cachée apparaît tard sous forme de défaut d'interface, d'exception API ou de désaccord sur l'ancien sens d'Enregistrer.
L'écran copié transporte aussi les hypothèses du poste de travail dans un système distribué. L'application VCL peut conserver un curseur de dataset actif, partager une connexion et répondre de façon synchrone aux événements de champ. Un client web affronte la latence, les nouvelles tentatives, les modifications concurrentes, les sessions expirées et les requêtes qui arrivent deux fois. Simuler un dataset avec état sur HTTP produit des API bavardes et une logique client fragile.
La parité des pixels constitue donc le mauvais critère de recette. Préservez la parité des tâches et du métier. L'utilisateur doit toujours pouvoir approuver la bonne commande, comprendre l'échec d'une décision, résoudre un conflit et terminer son travail avec les informations nécessaires. Le nouvel écran peut fusionner d'anciennes boîtes de dialogue ou retirer des étapes de navigation si l'opération et ses contrôles restent clairs.
Un portage léger de l'interface se justifie parfois. Si le besoin immédiat concerne la compatibilité avec le système d'exploitation, si la base et les intégrations resteront inchangées et si le formulaire contient peu de code métier, un remplacement de bureau compatible peut faire gagner du temps. Traitez-le comme une mesure de confinement avec une durée annoncée. Ne l'appelez pas séparation architecturale.
Pour la plupart des systèmes anciens, définissez la cible autour de commandes, de requêtes et d'un état explicite. Les services Go conviennent aux opérations applicatives transactionnelles et à l'accès aux données. Rust a sa place pour les noyaux numériques où le comportement exact et les performances méritent une frontière étroite. Les clients TypeScript doivent posséder l'état d'interaction et la logique d'affichage, tandis que Postgres impose les contraintes relationnelles durables. Ces choix viennent du contexte de projet fourni ; ils ne prescrivent pas les quatre technologies à chaque parc Delphi.
Basculez par opération, pas par formulaire
Basculez une opération métier à la fois, car les formulaires correspondent rarement à des frontières de service nettes. Un formulaire de commande peut réunir la recherche client, la tarification, l'approbation, l'impression et le paiement. Remplacer tout le formulaire oblige les cinq chemins à être prêts ensemble et crée une grande unité de retour arrière.
Choisissez une opération aux entrées claires, aux résultats mesurables et à l'état partagé limité. Placez une interface devant l'ancienne implémentation, puis ajoutez la nouvelle derrière le même contrat. Le formulaire VCL peut appeler cette frontière avant l'existence du nouveau client. Vous prouvez ainsi que la séparation fonctionne sans lier l'extraction du domaine à la reconstruction visuelle.
Une séquence pratique comporte cinq étapes :
- Enregistrez le comportement actuel et les échecs de l'opération choisie.
- Extrayez les entrées de ses règles et ses codes de résultat pendant que le formulaire possède encore la présentation.
- Placez la persistance derrière un adaptateur et définissez la frontière de transaction.
- Rejouez les cas de parité sur les deux implémentations et classez chaque différence.
- Orientez une part contrôlée des appels vers le nouveau chemin avec un commutateur de retour explicite.
Évitez les doubles écritures sauf si vous pouvez les rendre idempotentes et les réconcilier. Lire les deux systèmes pour les comparer est plus sûr que de laisser les deux modifier les données qui font autorité. Si une exécution fantôme déclenche des e-mails, paiements, impressions ou écritures d'audit, remplacez ces effets par des enregistreurs dans l'environnement de comparaison.
Faites du retour arrière une décision de routage par opération. Si le nouveau chemin d'approbation échoue, renvoyez l'approbation à l'ancienne implémentation sans annuler les recherches client déjà déplacées. Rendez la compatibilité de base explicite pendant que les deux chemins fonctionnent. Une modification de schéma qui empêche l'ancien exécutable de lire une ligne supprime le retour arrière même si le drapeau de fonctionnalité existe encore.
Observez les résultats métier pendant la bascule. Comptez les codes de motif, les conflits, les annulations et les opérations achevées, puis comparez leur forme à la référence enregistrée. Un taux d'erreur brut est trop grossier : le nouveau chemin peut renvoyer un succès HTTP tout en approuvant des commandes que les anciennes règles refusaient. Analysez les changements de décision avant d'étendre le trafic.
La dépendance la plus difficile doit fixer l'ordre. Si l'approbation dépend de la tarification, extrayez d'abord la tarification ou gardez-la derrière un adaptateur appelé par les deux versions. Ne migrez pas les écrans faciles en laissant la transaction centrale comme surprise finale. Cela crée du progrès visible sans réduire beaucoup le risque.
CodeHero lit toute la base de code Delphi, y compris les langages liés, et modernise l'architecture en maintenant le comportement de l'original. La même discipline vaut pour un programme manuel : cartographier globalement, découper localement et ne jamais déduire la sûreté d'un écran compilé.
Le remplacement doit rendre l'état caché impossible
La cible est prête lorsque les décisions métier ne dépendent plus d'un contrôle, de la ligne courante d'un dataset, de l'ordre de création des formulaires ou d'une transaction globale implicite. Vous devez pouvoir invoquer une opération avec une entrée sérialisée, observer un résultat stable et la tester sans construire de fenêtre.
Cette exigence révèle les extractions incomplètes. Un service qui lit Screen.ActiveForm, un repository qui exécute une règle dans un hook générique de sauvegarde ou un client TypeScript qui calcule le total de facture faisant autorité conserve l'ancien problème. Les noms ont changé, mais le comportement reste caché derrière des événements d'infrastructure.
Gardez une courte liste de contrôle des frontières pendant la revue de code :
- L'opération accepte-t-elle des valeurs et identifiants métier plutôt que des contrôles ou des curseurs de dataset ?
- Chaque règle durable peut-elle s'exécuter pour les appels de l'interface, des imports et de l'API ?
- Une seule transaction couvre-t-elle toute la modification métier ?
- Les résultats de concurrence et de nouvelle tentative sont-ils explicites ?
- Un cas enregistré peut-il prouver la parité sans comparer des pixels ?
Certains comportements resteront dans l'enveloppe VCL pendant la transition, et cela convient. La gestion du focus, les raccourcis clavier, la mise en page et la présentation locale du brouillon n'ont pas besoin d'une abstraction prématurée. La ligne à défendre concerne l'autorité : la présentation peut proposer et expliquer un changement, mais une opération applicative le décide et le valide.
Ne commencez pas par redessiner le plus grand formulaire. Choisissez l'opération qu'il contient dont l'échec coûte le plus cher, capturez les preuves de son comportement actuel et donnez-lui un nom. Dès que cette opération fonctionne sans le formulaire, le reste de la modernisation dispose d'une frontière sur laquelle construire.
FAQ
Comment savoir si un gestionnaire Delphi contient de la logique métier ?
Imaginez la même opération appelée par un import ou une API sans construire le formulaire. Les décisions qui doivent encore s'appliquer sont des règles métier ; le focus, les dialogues et la mise en page restent de la présentation.
Dois-je d'abord déplacer le code des formulaires dans un TDataModule ?
Un TDataModule peut alléger le formulaire, mais ne crée pas de frontière métier. Placez les règles dans des unités aux entrées et résultats explicites, puis faites-les appeler par le formulaire et le module.
Peut-on remplacer sans risque les contrôles VCL orientés données par des champs web ?
Seulement après avoir modélisé leur comportement caché d'édition, de tampon, de validation, de post et d'annulation. Des champs semblables à l'écran ne garantissent pas les mêmes règles de concurrence ou de persistance.
Où placer les règles Delphi OnValidate dans le nouveau système ?
Placez les invariants durables dans l'opération applicative ou métier employée par tous les appelants. Le client peut répéter un contrôle pour répondre vite, mais il ne doit pas faire autorité.
Comment migrer sans risque la logique de BeforePost ?
Extrayez la règle dans une unité appelable et gardez son appel par BeforePost tant que les anciens chemins d'écriture existent. Retirez l'événement uniquement lorsque les traces prouvent que chaque écriture passe par la nouvelle commande.
Pourquoi un portage direct de l'interface Delphi coûte-t-il si cher ?
Il reconstruit les écrans avant de révéler les opérations et l'état implicite qu'ils recouvrent. L'équipe paie la reproduction visuelle, puis les changements d'interface et d'API imposés par les règles cachées.
Faut-il conserver chaque bug pour obtenir la parité ?
Non. Classez chaque différence en parité requise, correction approuvée ou comportement non résolu. Une correction exige un responsable et un test pour que la migration ne devienne pas une reconception sans contrôle.
Les tests unitaires remplacent-ils les cas de production enregistrés ?
Les tests unitaires prouvent les règles que vous savez écrire. Les cas enregistrés révèlent un ordre d'événements, des formes de données et des effets oubliés ; utilisez les deux pour des objectifs différents.
La nouvelle API doit-elle exposer des endpoints CRUD génériques ?
Évitez les mises à jour génériques pour les enregistrements riches en règles. Des commandes comme ApproveOrder expriment l'intention, fixent la transaction et empêchent de construire un état invalide champ par champ.
Quelle est l'unité de bascule la plus sûre pour Delphi ?
Basculez une opération métier nommée avec des entrées, résultats et retour arrière clairs, même si elle traverse plusieurs formulaires. Un formulaire entier est généralement trop large et un seul gestionnaire de champ trop étroit.